[
  {
    "path": ".github/pull_request_template.md",
    "content": "# PR Description: \nreplace this with your description\n\n# Pull Request Checklist\n\n## Overview\n- [x] Put an x inside of the square brackets to check each item.\n- [ ] I have read and understood the [CONTRIBUTING.md](CONTRIBUTING.md) guidelines\n- [ ] My pull request has a descriptive title that accurately reflects the changes and the description has been filled in above.\n- [ ] I've included only files relevant to the changes described in the PR title and description\n- [ ] I've created a new branch in my forked repository for this contribution\n\n## Code Quality\n- [ ] My code is relevant to ServiceNow developers\n- [ ] My code snippets expand meaningfully on official ServiceNow documentation (if applicable)\n- [ ] I've disclosed use of ES2021 features (if applicable)\n- [ ] I've tested my code snippets in a ServiceNow environment (where possible)\n\n## Repository Structure Compliance\n- [ ] I've placed my code snippet(s) in one of the required top-level categories:\n  - `Core ServiceNow APIs/`\n  - `Server-Side Components/`\n  - `Client-Side Components/`\n  - `Modern Development/`\n  - `Integration/`\n  - `Specialized Areas/`\n- [ ] I've used appropriate sub-categories within the top-level categories\n- [ ] Each code snippet has its own folder with a descriptive name\n\n## Documentation\n- [ ] I've included a README.md file for each code snippet\n- [ ] The README.md includes:\n  - Description of the code snippet functionality\n  - Usage instructions or examples\n  - Any prerequisites or dependencies\n  - (Optional) Screenshots or diagrams if helpful\n\n## Restrictions\n- [ ] My PR does not include XML exports of ServiceNow records\n- [ ] My PR does not contain sensitive information (passwords, API keys, tokens)\n- [ ] My PR does not include changes that fall outside the described scope\n"
  },
  {
    "path": ".github/scripts/validate-structure.js",
    "content": "#!/usr/bin/env node\n\nconst { execSync } = require('child_process');\n\nconst allowedCategories = new Set([\n  'Core ServiceNow APIs',\n  'Server-Side Components',\n  'Client-Side Components',\n  'Modern Development',\n  'Integration',\n  'Specialized Areas'\n]);\n\nfunction resolveDiffRange() {\n  if (process.argv[2]) {\n    return process.argv[2];\n  }\n\n  const inCI = process.env.GITHUB_ACTIONS === 'true';\n  if (!inCI) {\n    return 'origin/main...HEAD';\n  }\n\n  const base = process.env.GITHUB_BASE_REF ? `origin/${process.env.GITHUB_BASE_REF}` : 'origin/main';\n  const head = process.env.GITHUB_SHA || 'HEAD';\n  return `${base}...${head}`;\n}\n\nfunction getChangedFiles(diffRange) {\n  let output;\n  try {\n    output = execSync(`git diff --name-only --diff-filter=ACMR ${diffRange}`, {\n      encoding: 'utf8',\n      stdio: ['ignore', 'pipe', 'pipe']\n    });\n  } catch (error) {\n    console.error('Failed to collect changed files. Ensure the base branch is fetched.');\n    console.error(error.stderr?.toString() || error.message);\n    process.exit(1);\n  }\n\n  return output\n    .split('\\n')\n    .map((line) => line.trim())\n    .filter(Boolean);\n}\n\nfunction validateFilePath(filePath) {\n  const normalized = filePath.replace(/\\\\/g, '/');\n  const segments = normalized.split('/');\n\n  // Check for invalid characters that break local file systems\n  for (let i = 0; i < segments.length; i++) {\n    const segment = segments[i];\n\n    // Check for trailing periods (invalid on Windows)\n    if (segment.endsWith('.')) {\n      return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a period (.) as this breaks local file system sync on Windows.`;\n    }\n\n    // Check for trailing spaces (invalid on Windows)\n    if (segment.endsWith(' ')) {\n      return `Invalid folder/file name '${segment}' in path '${normalized}': Names cannot end with a space as this breaks local file system sync on Windows.`;\n    }\n\n    // Check for reserved Windows names\n    const reservedNames = ['CON', 'PRN', 'AUX', 'NUL', 'COM1', 'COM2', 'COM3', 'COM4', 'COM5', 'COM6', 'COM7', 'COM8', 'COM9', 'LPT1', 'LPT2', 'LPT3', 'LPT4', 'LPT5', 'LPT6', 'LPT7', 'LPT8', 'LPT9'];\n    const nameWithoutExt = segment.split('.')[0].toUpperCase();\n    if (reservedNames.includes(nameWithoutExt)) {\n      return `Invalid folder/file name '${segment}' in path '${normalized}': '${nameWithoutExt}' is a reserved name on Windows and will break local file system sync.`;\n    }\n\n    // Check for invalid characters (Windows and general file system restrictions)\n    const invalidChars = /[<>:\"|?*\\x00-\\x1F]/;\n    if (invalidChars.test(segment)) {\n      return `Invalid folder/file name '${segment}' in path '${normalized}': Contains characters that are invalid on Windows file systems (< > : \" | ? * or control characters).`;\n    }\n  }\n\n  if (!allowedCategories.has(segments[0])) {\n    return null;\n  }\n\n  // Files must live under: Category/Subcategory/SpecificUseCase/<file>\n  if (segments.length < 4) {\n    return `Move '${normalized}' under a valid folder hierarchy (Category/Subcategory/Use-Case/your-file). Files directly inside '${segments[0]}' or its subcategories are not allowed.`;\n  }\n\n  return null;\n}\n\nfunction main() {\n  const diffRange = resolveDiffRange();\n  const changedFiles = getChangedFiles(diffRange);\n\n  if (changedFiles.length === 0) {\n    console.log('No relevant file changes detected.');\n    return;\n  }\n\n  const problems = [];\n\n  for (const filePath of changedFiles) {\n    const issue = validateFilePath(filePath);\n    if (issue) {\n      problems.push(issue);\n    }\n  }\n\n  if (problems.length > 0) {\n    console.error('Folder structure violations found:');\n    for (const msg of problems) {\n      console.error(` - ${msg}`);\n    }\n    process.exit(1);\n  }\n\n  console.log('Folder structure looks good.');\n}\n\nmain();\n"
  },
  {
    "path": ".github/workflows/hacktrack.yml",
    "content": "#This file is for ServiceNow Dev Program Hacktoberfest Tracking and can be ignored or deleted.\n\nname: Record Hacktrack Event\non:\n  push:\n    branches: main\n  fork:\n    branches: main\n  issues:\n    types: [opened, closed]\n    branches: main\n  pull_request_target:\n    types: [opened, closed]\n    branches: main\njobs:\n  deployment:\n    if: github.repository == 'ServiceNowDevProgram/code-snippets'\n    runs-on: ubuntu-latest\n    steps:\n#     - name: Log payload\n#       env:\n#         GITHUB_CONTEXT: ${{ toJson(github) }}\n#       run: |\n#         echo \"$GITHUB_CONTEXT\"\n    - name: Contact DPR\n      id: myRequest\n      uses: fjogeleit/http-request-action@v1.8.1\n      with:\n        url: ${{ format('https://{0}.service-now.com/api/x_snc_hacktrack/hacktrack', secrets.HT_INSTANCE_NAME) }}\n        method: 'POST'\n        contentType: application/json\n        data: ${{ toJson(github) }}\n        username: ${{ secrets.DPRD_USERNAME }}\n        password: ${{ secrets.DPRD_PASSWORD }}\n    - name: Show Response\n      run: echo ${{ steps.myRequest.outputs.response }}\n"
  },
  {
    "path": ".github/workflows/pages.yml",
    "content": "# GitHub Pages deployment workflow\nname: Deploy GitHub Pages\n\non:\n  workflow_dispatch:\n\n# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages\npermissions:\n  contents: read\n  pages: write\n  id-token: write\n\n# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.\n# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.\nconcurrency:\n  group: pages-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n  # Build and optimize job\n  build:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Setup Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: '18'\n          cache: 'npm'\n\n      - name: Install build dependencies\n        run: |\n          npm init -y\n          npm install --save-dev html-minifier-terser clean-css-cli terser html-validate\n\n      - name: Validate HTML files\n        run: |\n          echo \"Validating HTML files...\"\n          npx html-validate *.html pages/*.html || echo \"HTML validation completed with warnings\"\n\n      - name: Optimize assets\n        run: |\n          echo \"Optimizing HTML files...\"\n          # Create backup directory\n          mkdir -p .backup\n          \n          # Minify HTML files (preserve original structure)\n          find . -name \"*.html\" -not -path \"./node_modules/*\" -not -path \"./.backup/*\" | while read file; do\n            echo \"Minifying: $file\"\n            npx html-minifier-terser \\\n              --collapse-whitespace \\\n              --remove-comments \\\n              --remove-optional-tags \\\n              --remove-redundant-attributes \\\n              --remove-script-type-attributes \\\n              --remove-style-link-type-attributes \\\n              --minify-css \\\n              --minify-js \\\n              \"$file\" -o \"$file.tmp\" && mv \"$file.tmp\" \"$file\"\n          done\n          \n          # Minify CSS files if any exist\n          if find . -name \"*.css\" -not -path \"./node_modules/*\" -not -path \"./.backup/*\" | grep -q .; then\n            echo \"Optimizing CSS files...\"\n            find . -name \"*.css\" -not -path \"./node_modules/*\" -not -path \"./.backup/*\" | while read file; do\n              echo \"Minifying: $file\"\n              npx cleancss \"$file\" -o \"$file\"\n            done\n          fi\n          \n          # Remove build dependencies from final artifact\n          rm -rf node_modules package*.json\n\n      - name: Setup Pages\n        id: pages\n        uses: actions/configure-pages@v4\n\n      - name: Upload artifact\n        uses: actions/upload-pages-artifact@v3\n        with:\n          path: '.'\n\n  # Deployment job\n  deploy:\n    environment:\n      name: github-pages\n      url: ${{ steps.deployment.outputs.page_url }}\n    runs-on: ubuntu-latest\n    needs: build\n    steps:\n      - name: Deploy to GitHub Pages\n        id: deployment\n        uses: actions/deploy-pages@v4"
  },
  {
    "path": ".github/workflows/pr-auto-unassign-stale.yml",
    "content": "name: Auto-unassign stale PR assignees\n\non:\n  schedule:\n    - cron: \"*/15 * * * *\"   # run every 15 minutes\n  workflow_dispatch:\n    inputs:\n      enabled:\n        description: \"Enable this automation\"\n        type: boolean\n        default: true\n      max_age_minutes:\n        description: \"Unassign if assigned longer than X minutes\"\n        type: number\n        default: 60\n      dry_run:\n        description: \"Preview only; do not change assignees\"\n        type: boolean\n        default: false\n\npermissions:\n  pull-requests: write\n  issues: write\n\nenv:\n  # Defaults (can be overridden via workflow_dispatch inputs)\n  ENABLED: \"true\"\n  MAX_ASSIGN_AGE_MINUTES: \"60\"\n  DRY_RUN: \"false\"\n\njobs:\n  sweep:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Resolve inputs into env\n        run: |\n          # Prefer manual run inputs when present\n          if [ \"${{ github.event_name }}\" = \"workflow_dispatch\" ]; then\n            echo \"ENABLED=${{ inputs.enabled }}\" >> $GITHUB_ENV\n            echo \"MAX_ASSIGN_AGE_MINUTES=${{ inputs.max_age_minutes }}\" >> $GITHUB_ENV\n            echo \"DRY_RUN=${{ inputs.dry_run }}\" >> $GITHUB_ENV\n          fi\n          echo \"Effective config: ENABLED=$ENABLED, MAX_ASSIGN_AGE_MINUTES=$MAX_ASSIGN_AGE_MINUTES, DRY_RUN=$DRY_RUN\"\n\n      - name: Exit if disabled\n        if: ${{ env.ENABLED != 'true' && env.ENABLED != 'True' && env.ENABLED != 'TRUE' }}\n        run: echo \"Disabled via ENABLED=$ENABLED. Exiting.\" && exit 0\n\n      - name: Unassign stale assignees\n        uses: actions/github-script@v7\n        with:\n          script: |\n            const owner = context.repo.owner;\n            const repo  = context.repo.repo;\n\n            const MAX_MIN = parseInt(process.env.MAX_ASSIGN_AGE_MINUTES || \"60\", 10);\n            const DRY_RUN = [\"true\",\"True\",\"TRUE\",\"1\",\"yes\"].includes(String(process.env.DRY_RUN));\n            const now = new Date();\n\n            core.info(`Scanning open PRs. Threshold = ${MAX_MIN} minutes. DRY_RUN=${DRY_RUN}`);\n\n            // List all open PRs\n            const prs = await github.paginate(github.rest.pulls.list, {\n              owner, repo, state: \"open\", per_page: 100\n            });\n\n            let totalUnassigned = 0;\n\n            for (const pr of prs) {\n              if (!pr.assignees || pr.assignees.length === 0) continue;\n\n              const number = pr.number;\n              core.info(`PR #${number}: \"${pr.title}\" — assignees: ${pr.assignees.map(a => a.login).join(\", \")}`);\n\n              // Pull reviews (to see if an assignee started a review)\n              const reviews = await github.paginate(github.rest.pulls.listReviews, {\n                owner, repo, pull_number: number, per_page: 100\n              });\n\n              // Issue comments (general comments)\n              const issueComments = await github.paginate(github.rest.issues.listComments, {\n                owner, repo, issue_number: number, per_page: 100\n              });\n\n              // Review comments (file-level)\n              const reviewComments = await github.paginate(github.rest.pulls.listReviewComments, {\n                owner, repo, pull_number: number, per_page: 100\n              });\n\n              // Issue events (to find assignment timestamps)\n              const issueEvents = await github.paginate(github.rest.issues.listEvents, {\n                owner, repo, issue_number: number, per_page: 100\n              });\n\n              for (const a of pr.assignees) {\n                const assignee = a.login;\n\n                // Find the most recent \"assigned\" event for this assignee\n                const assignedEvents = issueEvents\n                  .filter(e => e.event === \"assigned\" && e.assignee && e.assignee.login === assignee)\n                  .sort((x, y) => new Date(y.created_at) - new Date(x.created_at));\n\n                if (assignedEvents.length === 0) {\n                  core.info(`  - @${assignee}: no 'assigned' event found; skipping.`);\n                  continue;\n                }\n\n                const assignedAt = new Date(assignedEvents[0].created_at);\n                const ageMin = (now - assignedAt) / 60000;\n\n                // Has the assignee commented (issue or review comments) or reviewed?\n                const hasIssueComment = issueComments.some(c => c.user?.login === assignee);\n                const hasReviewComment = reviewComments.some(c => c.user?.login === assignee);\n                const hasReview = reviews.some(r => r.user?.login === assignee);\n\n                const eligible =\n                  ageMin >= MAX_MIN &&\n                  !hasIssueComment &&\n                  !hasReviewComment &&\n                  !hasReview &&\n                  pr.state === \"open\";\n\n                core.info(`  - @${assignee}: assigned ${ageMin.toFixed(1)} min ago; commented=${hasIssueComment || hasReviewComment}; reviewed=${hasReview}; open=${pr.state==='open'} => ${eligible ? 'ELIGIBLE' : 'skip'}`);\n\n                if (!eligible) continue;\n\n                if (DRY_RUN) {\n                  core.notice(`Would unassign @${assignee} from PR #${number}`);\n                } else {\n                  try {\n                    await github.rest.issues.removeAssignees({\n                      owner, repo, issue_number: number, assignees: [assignee]\n                    });\n                    totalUnassigned += 1;\n                    // Optional: leave a gentle heads-up comment\n                    await github.rest.issues.createComment({\n                      owner, repo, issue_number: number,\n                      body: `👋 Unassigning @${assignee} due to inactivity (> ${MAX_MIN} min without comments/reviews). This PR remains open for other reviewers.`\n                    });\n                    core.info(`    Unassigned @${assignee} from #${number}`);\n                  } catch (err) {\n                    core.warning(`    Failed to unassign @${assignee} from #${number}: ${err.message}`);\n                  }\n                }\n              }\n            }\n\n            core.summary\n              .addHeading('Auto-unassign report')\n              .addRaw(`Threshold: ${MAX_MIN} minutes\\n\\n`)\n              .addRaw(`Total unassignments: ${totalUnassigned}\\n`)\n              .write();\n\n          result-encoding: string\n"
  },
  {
    "path": ".github/workflows/validate-structure.yml",
    "content": "﻿name: Validate Folder Structure\n\non:\n  pull_request_target:\n    branches:\n      - main\n\npermissions:\n  contents: read\n  pull-requests: write\n\nconcurrency:\n  group: folder-structure-${{ github.event.pull_request.number || github.run_id }}\n  cancel-in-progress: true\n\njobs:\n  structure:\n    runs-on: ubuntu-latest\n    steps:\n      - name: Checkout base repository\n        uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n\n      - name: Cache validation script\n        run: cp .github/scripts/validate-structure.js \"$RUNNER_TEMP/validate-structure.js\"\n\n      - name: Fetch pull request head\n        id: fetch_head\n        env:\n          PR_REMOTE_URL: https://github.com/${{ github.event.pull_request.head.repo.full_name }}.git\n          PR_HEAD_REF: ${{ github.event.pull_request.head.ref }}\n        run: |\n          git remote remove pr >/dev/null 2>&1 || true\n          git remote add pr \"$PR_REMOTE_URL\"\n          if git fetch pr \"$PR_HEAD_REF\":pr-head --no-tags; then\n            git checkout pr-head\n            git fetch origin \"${{ github.event.pull_request.base.ref }}\"\n            echo \"fetched=true\" >> \"$GITHUB_OUTPUT\"\n          else\n            echo \"::warning::Unable to fetch fork repository. Skipping structure validation.\"\n            echo \"fetched=false\" >> \"$GITHUB_OUTPUT\"\n          fi\n\n      - name: Use Node.js 18\n        uses: actions/setup-node@v4\n        with:\n          node-version: 18\n\n      - name: Validate folder layout\n        if: ${{ steps.fetch_head.outputs.fetched == 'true' }}\n        id: validate\n        run: |\n          set -euo pipefail\n\n          tmp_output=$(mktemp)\n          tmp_error=$(mktemp)\n\n          set +e\n          node \"$RUNNER_TEMP/validate-structure.js\" origin/${{ github.event.pull_request.base.ref }}...HEAD >\"$tmp_output\" 2>\"$tmp_error\"\n          status=$?\n          set -e\n\n          cat \"$tmp_output\"\n          cat \"$tmp_error\" >&2\n\n          if grep -q 'Folder structure violations found' \"$tmp_output\" \"$tmp_error\"; then\n            # Save validation output for use in PR comment\n            cat \"$tmp_output\" \"$tmp_error\" > \"$RUNNER_TEMP/validation_output.txt\"\n            echo \"status=failed\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n\n          if [ $status -ne 0 ]; then\n            echo \"::warning::Structure validation skipped because the diff could not be evaluated (exit code $status).\"\n            echo \"status=skipped\" >> \"$GITHUB_OUTPUT\"\n            exit 0\n          fi\n\n          echo \"status=passed\" >> \"$GITHUB_OUTPUT\"\n\n      - name: Close pull request on failure\n        if: ${{ steps.validate.outputs.status == 'failed' }}\n        uses: actions/github-script@v6\n        with:\n          github-token: ${{ github.token }}\n          script: |\n            const pullNumber = context.payload.pull_request.number;\n            const owner = context.repo.owner;\n            const repo = context.repo.repo;\n\n            const fs = require('fs');\n            const output = fs.readFileSync(process.env.RUNNER_TEMP + '/validation_output.txt', 'utf8');\n\n            let commentBody = `Thank you for your contribution. However, it doesn't comply with our contributing guidelines.\\n\\n`;\n\n            // Check if the error is about invalid file/folder names\n            if (output.includes('Names cannot end with a period') ||\n                output.includes('Names cannot end with a space') ||\n                output.includes('is a reserved name on Windows') ||\n                output.includes('Contains characters that are invalid')) {\n              commentBody += `**❌ Invalid File/Folder Names Detected**\\n\\n`;\n              commentBody += `Your contribution contains file or folder names that will break when syncing to local file systems (especially Windows):\\n\\n`;\n              commentBody += `\\`\\`\\`\\n${output}\\n\\`\\`\\`\\n\\n`;\n              commentBody += `**Common issues:**\\n`;\n              commentBody += `- Folder/file names ending with a period (.) - not allowed on Windows\\n`;\n              commentBody += `- Folder/file names ending with spaces - not allowed on Windows\\n`;\n              commentBody += `- Reserved names like CON, PRN, AUX, NUL, COM1-9, LPT1-9 - not allowed on Windows\\n`;\n              commentBody += `- Invalid characters: < > : \" | ? * or control characters\\n\\n`;\n              commentBody += `Please rename these files/folders to be compatible with all operating systems.\\n\\n`;\n            } else {\n              commentBody += `As a reminder, the general requirements (as outlined in the [CONTRIBUTING.md file](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md)) are the following: follow the folder+subfolder guidelines and include a README.md file explaining what the code snippet does.\\n\\n`;\n              commentBody += `**Validation errors:**\\n\\`\\`\\`\\n${output}\\n\\`\\`\\`\\n\\n`;\n            }\n\n            commentBody += `Review your contribution against the guidelines and make the necessary adjustments. Closing this for now. Once you make additional changes, feel free to re-open this Pull Request or create a new one.`;\n\n            await github.rest.issues.createComment({\n              owner,\n              repo,\n              issue_number: pullNumber,\n              body: commentBody.trim()\n            });\n\n            await github.rest.pulls.update({\n              owner,\n              repo,\n              pull_number: pullNumber,\n              state: 'closed'\n            });\n\n      - name: Mark job as failed if validation failed\n        if: ${{ steps.validate.outputs.status == 'failed' }}\n        run: exit 1\n"
  },
  {
    "path": ".gitignore",
    "content": ".DS_Store\r\n\r\n# Claude Code settings\r\n.claude/\r\nsettings.local.json\r\n"
  },
  {
    "path": "AGENTS.md",
    "content": "# AGENTS.md\n\nThis file provides guidance to AI Coding Agents when working with code in this repository.\n\n## Repository Overview\n\nThis is the **ServiceNow Developer Program's Code Snippets Repository** - a community-driven collection of ServiceNow development code examples and utilities. The repository contains 900+ code snippets organized into 50+ categories covering all aspects of ServiceNow platform development.\n\n## Repository Structure and Organization\n\n### Directory Structure\nAll code snippets follow a standardized four-level structure:\n```\nTop-Level Category/\n├── Sub-Category/\n│   ├── Specific-Use-Case/\n│   │   ├── README.md           # Description and usage instructions\n│   │   ├── script.js           # Main code implementation\n│   │   └── variant.js          # Optional code variations\n```\n\n### Top-Level Categories (REQUIRED Structure)\nThe repository is organized into **6 major categories**. All contributions MUST use these categories:\n\n- **Core ServiceNow APIs/**: Essential ServiceNow JavaScript APIs\n  - `GlideRecord/`, `GlideAjax/`, `GlideSystem/`, `GlideDate/`, `GlideDateTime/`, `GlideElement/`, `GlideFilter/`, `GlideAggregate/`, `GlideHTTPRequest/`, `GlideModal/`, `GlideQuery/`, `GlideTableDescriptor/`\n\n- **Server-Side Components/**: Server-executed code\n  - `Background Scripts/`, `Business Rules/`, `Script Includes/`, `Script Actions/`, `Scheduled Jobs/`, `Transform Map Scripts/`, `Server Side/`, `Inbound Actions/`, `Processors/`\n\n- **Client-Side Components/**: Browser-executed code\n  - `Client Scripts/`, `Catalog Client Script/`, `UI Actions/`, `UI Scripts/`, `UI Pages/`, `UI Macros/`, `UX Client Scripts/`, `UX Client Script Include/`, `UX Data Broker Transform/`\n\n- **Modern Development/**: Modern ServiceNow frameworks\n  - `Service Portal/`, `Service Portal Widgets/`, `NOW Experience/`, `GraphQL/`, `ECMASCript 2021/`\n\n- **Integration/**: External systems and data exchange\n  - `Integration/` (original), `RESTMessageV2/`, `Import Set API/`, `Scripted REST Api/`, `Mail Scripts/`, `MIDServer/`, `Attachments/`\n\n- **Specialized Areas/**: Domain-specific functionality\n  - `CMDB/`, `ITOM/`, `Performance Analytics/`, `ATF Steps/`, `Agile Development/`, `Advanced Conditions/`, `Browser Bookmarklets/`, `Browser Utilities/`, `Dynamic Filters/`, `Fix scripts/`, `Flow Actions/`, `Formula Builder/`, `Notifications/`, `On-Call Calendar/`, `Record Producer/`, `Regular Expressions/`, `Styles/`\n\n## Development Guidelines\n\n### Code Quality Standards\n- Each snippet must include comprehensive README.md documentation\n- Code should be relevant to ServiceNow developers\n- ES2021 features are allowed but should be clearly documented\n- Examples should expand meaningfully on official ServiceNow documentation\n- Quality over quantity - low-effort submissions are rejected\n\n### Contribution Requirements\n- **Mandatory Category Structure**: All contributions MUST use the 6 top-level categories. PRs with incorrect structure will be rejected.\n- **Descriptive Structure**: Changes must match pull request scope exactly\n- **No XML Exports**: Avoid ServiceNow record exports in favor of JavaScript code\n- **Documentation**: Every code snippet requires accompanying README.md\n- **Proper Categorization**: Place snippets in appropriate existing sub-categories within the required top-level structure\n\n### File Organization\n- Use descriptive folder names that clearly indicate functionality\n- Group related code variations in the same folder\n- Include screenshots or examples when helpful for understanding\n- Maintain consistent naming conventions across similar snippets\n\n## Common ServiceNow Development Patterns\n\n### GlideRecord Usage\n- Most snippets demonstrate proper GlideRecord query patterns\n- Security considerations with ACL enforcement examples\n- Performance optimization through proper query construction\n- Reference field handling and relationship traversal\n\n### Integration Patterns\n- REST API consumption and production examples\n- Authentication handling for external systems\n- Data transformation and mapping utilities\n- Error handling and logging best practices\n\n### Business Logic Implementation\n- Business Rules for event-driven processing\n- Background Scripts for administrative tasks\n- Client Scripts for form behavior and validation\n- UI Actions for custom user interactions\n\n## Testing and Validation\n\n### Code Verification\n- Test all code snippets in development environments before submission\n- Validate against multiple ServiceNow versions when possible\n- Consider security implications and access controls\n- Document any prerequisites or dependencies\n\n### Community Review Process\n- All contributions undergo peer review by Developer Advocates\n- Changes outside described scope result in rejection\n- Multiple submissions require separate branches in forked repositories\n\n## Security Considerations\n\n- Never include sensitive information (passwords, API keys, tokens)\n- Consider security implications of all code examples\n- Demonstrate proper access control patterns where applicable\n- Include warnings for potentially dangerous operations\n\n## Repository Maintenance\n\n### Branch Management\n- Main branch contains all approved code snippets\n- Use descriptive branch names for contributions\n- Create separate branches for different feature additions\n\n### File Management\n- No build processes or compilation required\n- Direct GitHub editing supported\n- Git-enabled IDEs like VS Code recommended for larger contributions\n- Standard .gitignore excludes only .DS_Store files\n\nThis repository serves as a comprehensive reference for ServiceNow developers at all skill levels, emphasizing practical, tested solutions for real-world development scenarios.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# How to Contribute\r\n\r\nWe welcome contributions to the **ServiceNow Developer Program's Code Snippets Repository**! Follow these steps to get involved:\r\n\r\n## Steps to Contribute\r\n\r\n1. **Fork the Repository**: Click the \"Fork\" button on the top right of this page to create your own copy of the repository.\r\n\r\n2. **Create a New Branch**: \r\n   - Name your branch according to the functionality you are adding (e.g., `feature/new-snippet` or `bugfix/fix-issue`).\r\n   - Switch to your new branch from the main branch dropdown.\r\n\r\n3. **Add or Edit Code Snippets**:\r\n   - Navigate to the appropriate folders and files to add, edit, or reorganize code snippets.\r\n   - Commit your changes to your forked repository.\r\n\r\n4. **Submit a Pull Request**:\r\n   - Go to the original repository and click on the \"Pull Requests\" tab.\r\n   - Click \"New Pull Request\" and select your branch.\r\n   - Ensure your pull request has a descriptive title and comment that outlines what changes you made.\r\n   - Only include files relevant to the changes described in the pull request title and description.\r\n   - Avoid submitting XML exports of ServiceNow records.\r\n\r\nThat's it! A Developer Advocate or a designated approver from the ServiceNow Dev Program will review your pull request. If approved, it will be merged into the main repository for everyone's benefit!\r\n\r\n### Note on Multiple Submissions\r\nIf you plan to submit another pull request while your original is still pending, make sure to create a new branch in your forked repository first.\r\n\r\n## General Requirements\r\n\r\n- **Descriptive Pull Request Titles**: Your pull request must have explicit and descriptive titles that accurately represent the changes made.\r\n- **Scope Adherence**: Changes that fall outside the described scope will result in the entire pull request being rejected.\r\n- **Quality Over Quantity**: Low-effort or spam pull requests will be marked accordingly.\r\n- **Expanded Snippets**: Code snippets reused from the [ServiceNow Documentation](https://docs.servicenow.com/) or [API References](https://developer.servicenow.com/dev.do#!/reference/) are acceptable only if they are expanded in a meaningful way (e.g., with additional context, documentation, or variations). Remember: *\"QUANTITY IS FUN, QUALITY IS KEY.\"*\r\n- **Relevance**: Code should be relevant to ServiceNow Developers.\r\n- **ES2021 Compatibility**: While ES2021 is allowed, we encourage you to disclose if your code is using ES2021 features, as not everyone may be working with ES2021-enabled applications.\r\n\r\n## Core Documentation File Changes\r\n\r\n**IMPORTANT**: For changes to core documentation files (README.md, CONTRIBUTING.md, LICENSE, etc.), contributors must:\r\n\r\n1. **Submit an Issue First**: Before making any changes to core documentation files, create an issue describing:\r\n   - What you intend to edit\r\n   - Why the change is needed\r\n   - Your proposed approach\r\n\r\n2. **Get Assignment**: Wait to be assigned to the issue by a maintainer before submitting a PR.\r\n\r\n3. **Reference the Issue**: Include the issue number in your PR title and description.\r\n\r\nThis process helps prevent merge conflicts when multiple contributors want to update the same documentation files and ensures all changes align with the project's direction.\r\n\r\n## Repository Structure\r\n\r\n**IMPORTANT**: The repository has been reorganized into major categories. All new contributions MUST follow this structure for PR approval.\r\n\r\nPlease follow this directory structure when organizing your code snippets:\r\n\r\n- **Top-Level Categories**: These are fixed categories that represent major areas of ServiceNow development:\r\n  - `Core ServiceNow APIs/` - GlideRecord, GlideAjax, GlideSystem, GlideDate, etc.\r\n  - `Server-Side Components/` - Background Scripts, Business Rules, Script Includes, etc.\r\n  - `Client-Side Components/` - Client Scripts, Catalog Client Scripts, UI Actions, etc.\r\n  - `Modern Development/` - Service Portal, NOW Experience, GraphQL, ECMAScript 2021\r\n  - `Integration/` - RESTMessageV2, Import Sets, Mail Scripts, MIDServer, etc.\r\n  - `Specialized Areas/` - CMDB, ITOM, Performance Analytics, ATF Steps, etc.\r\n\r\n- **Sub-Categories**: Each top-level category contains sub-folders for specific ServiceNow technologies or use cases.\r\n- **Snippet Folders**: Each sub-category contains folders for **each code snippet**.\r\n- **Snippet Folder Contents**: Within each snippet folder, include:\r\n  - A `README.md` file that describes the code snippet.\r\n  - Individual files for each variant of the code snippet.\r\n\r\n### New Structure Example\r\n\r\n```\r\nCore ServiceNow APIs/\r\n  ├── GlideRecord/\r\n  │   ├── Query Performance Optimization/\r\n  │   │   ├── README.md         # Description of the optimization snippet\r\n  │   │   ├── basic_query.js    # Basic query example\r\n  │   │   └── optimized_query.js # Performance-optimized version\r\n  │   └── Reference Field Handling/\r\n  │       ├── README.md         # Description of reference handling\r\n  │       └── reference_query.js # Reference field query example\r\n  └── GlideAjax/\r\n      ├── Async Data Loading/\r\n      │   ├── README.md         # Description of async loading\r\n      │   ├── client_script.js  # Client-side implementation\r\n      │   └── script_include.js # Server-side Script Include\r\nServer-Side Components/\r\n  ├── Business Rules/\r\n  │   ├── Auto Assignment Logic/\r\n  │   │   ├── README.md         # Description of auto assignment\r\n  │   │   └── assignment_rule.js # Business rule implementation\r\n```\r\n\r\n### Category Placement Guidelines\r\n\r\n- **Core ServiceNow APIs**: All Glide* APIs and core ServiceNow JavaScript APIs\r\n- **Server-Side Components**: Code that runs on the server (Business Rules, Background Scripts, etc.)\r\n- **Client-Side Components**: Code that runs in the browser (Client Scripts, UI Actions, etc.)\r\n- **Modern Development**: Modern ServiceNow development approaches and frameworks\r\n- **Integration**: External system integrations, data import/export, and communication\r\n- **Specialized Areas**: Domain-specific functionality (CMDB, ITOM, Testing, etc.)\r\n\r\n## Final Checklist\r\n\r\nBefore submitting your pull request, ensure that:\r\n- All code snippet files are in the appropriate folders.\r\n- Each folder is correctly placed within its category.\r\n- Your code snippet is accompanied by a `readme.md` file that describes it.\r\n\r\nThank you for contributing! Your efforts help create a richer resource for the ServiceNow development community.\r\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Add Label For Attachment/README.md",
    "content": "Code Snippet to add a label to the attachment for a Catalog Item on the Portal."
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Add Label For Attachment/add_label_for_attachment.js",
    "content": "function onLoad() {\n    var attachmentElement = top.document.querySelectorAll('[ng-if=\"c.showAttachments()\"]');\n    \n\tif (attachmentElement[0]) {\n\t\tvar label = top.document.createElement('label');\n\t\tlabel.innerHTML = 'LABEL COMES HERE';\n\t\tattachmentElement[0].prepend(label);\n\t}\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Add Rows in MRVS/README.md",
    "content": "With this simple script you can through a client script add rows in the MRVS if you like. Ex. you want to prefill a few rows that you get from a GlideAjax call depending on what the user choosed in another variable\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Add Rows in MRVS/addrows.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n\tif (isLoading)\n\t\treturn;\n\n\tvar obj = (g_form.getValue('multi_test').length != 0) ? JSON.parse(g_form.getValue('multi_test')): [];//Get the MRVS\n\n  //Push in what you want\n\tobj.push({var_one: 'test1',\n\t\t\t  var_two: 'test2'});\n\tg_form.setValue('multi_test', JSON.stringify(obj));//And set the value again\n\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Auto Save Draft Feature/README.md",
    "content": "# Auto Save Draft Feature for Catalog Items\r\n\r\nThis snippet provides automatic draft saving functionality for ServiceNow Catalog Items, helping prevent data loss by automatically saving form data at regular intervals.\r\n\r\n## Overview\r\n\r\nThe feature includes two implementations:\r\n1. Basic Implementation (`basic_implementation.js`)\r\n2. Advanced Implementation (`advanced_implementation.js`)\r\n\r\n## Basic Implementation\r\n\r\n### Features\r\n- Auto-saves form data every minute\r\n- Stores single draft in sessionStorage\r\n- Provides draft restoration on form load\r\n- Basic error handling and user feedback\r\n\r\n### Usage\r\n```javascript\r\n// Apply in Catalog Client Script\r\n// Select \"onLoad\" for \"Client script runs\"\r\n// Copy content from basic_implementation.js\r\n```\r\n\r\n## Advanced Implementation\r\n\r\n### Enhanced Features\r\n- Multiple draft support (keeps last 3 drafts)\r\n- Advanced draft management\r\n- Draft selection dialog\r\n- Detailed metadata tracking\r\n- Improved error handling\r\n- User-friendly notifications\r\n\r\n### Usage\r\n```javascript\r\n// Apply in Catalog Client Script\r\n// Select \"onLoad\" for \"Client script runs\"\r\n// Copy content from advanced_implementation.js\r\n```\r\n\r\n## Technical Details\r\n\r\n### Dependencies\r\n- ServiceNow Platform UI Framework\r\n- GlideForm API\r\n- GlideModal (advanced implementation only)\r\n\r\n### Browser Support\r\n- Modern browsers with sessionStorage support\r\n- ES5+ compatible\r\n\r\n### Security Considerations\r\n- Uses browser's sessionStorage (cleared on session end)\r\n- No sensitive data transmission\r\n- Instance-specific storage\r\n\r\n## Implementation Guide\r\n\r\n1. Create a new Catalog Client Script:\r\n   - Table: Catalog Client Script [catalog_script_client]\r\n   - Type: onLoad\r\n   - Active: true\r\n\r\n2. Choose implementation:\r\n   - For basic needs: Copy `basic_implementation.js`\r\n   - For advanced features: Copy `advanced_implementation.js`\r\n\r\n3. Apply to desired Catalog Items:\r\n   - Select applicable Catalog Items\r\n   - Test in dev environment first\r\n\r\n## Best Practices\r\n\r\n1. Testing:\r\n   - Test with various form states\r\n   - Verify draft restoration\r\n   - Check browser storage limits\r\n\r\n2. Performance:\r\n   - Default 60-second interval is recommended\r\n   - Adjust based on form complexity\r\n   - Monitor browser memory usage\r\n\r\n3. User Experience:\r\n   - Clear feedback messages\r\n   - Confirmation dialogs\r\n   - Error notifications\r\n\r\n## Limitations\r\n\r\n- Browser session dependent\r\n- Storage size limits\r\n- Form field compatibility varies\r\n\r\n## Troubleshooting\r\n\r\nCommon issues and solutions:\r\n1. Draft not saving\r\n   - Check browser console for errors\r\n   - Verify sessionStorage availability\r\n   - Check form modification detection\r\n\r\n2. Restoration fails\r\n   - Validate stored data format\r\n   - Check browser storage permissions\r\n   - Verify form field compatibility\r\n\r\n## Version Information\r\n\r\n- Compatible with ServiceNow: Rome and later\r\n- Browser Requirements: Modern browsers with ES5+ support\r\n- Last Updated: October 2025"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Auto Save Draft Feature/advanced_implementation.js",
    "content": "/**\r\n * Advanced Auto-save Draft Implementation with Enhanced Features\r\n * This version adds multi-draft support and advanced error handling\r\n */\r\n\r\nfunction onLoad() {\r\n    var autosaveInterval = 60000; // 1 minute\r\n    var maxDrafts = 3; // Maximum number of drafts to keep\r\n    \r\n    // Initialize draft manager\r\n    initializeDraftManager();\r\n    \r\n    // Try to restore previous draft\r\n    restoreLastDraft();\r\n    \r\n    // Set up auto-save interval\r\n    setInterval(function() {\r\n        if (g_form.isModified()) {\r\n            saveAdvancedDraft();\r\n        }\r\n    }, autosaveInterval);\r\n}\r\n\r\nfunction initializeDraftManager() {\r\n    window.draftManager = {\r\n        maxDrafts: 3,\r\n        draftPrefix: 'catalogDraft_' + g_form.getUniqueValue() + '_',\r\n        \r\n        getAllDrafts: function() {\r\n            var drafts = [];\r\n            for (var i = 0; i < sessionStorage.length; i++) {\r\n                var key = sessionStorage.key(i);\r\n                if (key.startsWith(this.draftPrefix)) {\r\n                    drafts.push({\r\n                        key: key,\r\n                        data: JSON.parse(sessionStorage.getItem(key))\r\n                    });\r\n                }\r\n            }\r\n            return drafts.sort((a, b) => b.data.timestamp - a.data.timestamp);\r\n        },\r\n        \r\n        cleanup: function() {\r\n            var drafts = this.getAllDrafts();\r\n            if (drafts.length > this.maxDrafts) {\r\n                drafts.slice(this.maxDrafts).forEach(function(draft) {\r\n                    sessionStorage.removeItem(draft.key);\r\n                });\r\n            }\r\n        }\r\n    };\r\n}\r\n\r\nfunction saveAdvancedDraft() {\r\n    try {\r\n        var draftData = {};\r\n        g_form.serialize(draftData);\r\n        \r\n        // Add metadata\r\n        var draftKey = window.draftManager.draftPrefix + new Date().getTime();\r\n        var draftInfo = {\r\n            timestamp: new Date().getTime(),\r\n            data: draftData,\r\n            user: g_user.userName,\r\n            catalog_item: g_form.getTableName(),\r\n            fields_modified: g_form.getModifiedFields()\r\n        };\r\n        \r\n        sessionStorage.setItem(draftKey, JSON.stringify(draftInfo));\r\n        window.draftManager.cleanup();\r\n        \r\n        // Show success message with draft count\r\n        var remainingDrafts = window.draftManager.getAllDrafts().length;\r\n        g_form.addInfoMessage('Draft saved. You have ' + remainingDrafts + ' saved draft(s).');\r\n        \r\n    } catch (e) {\r\n        console.error('Error saving draft: ' + e);\r\n        g_form.addErrorMessage('Failed to save draft: ' + e.message);\r\n    }\r\n}\r\n\r\nfunction restoreLastDraft() {\r\n    try {\r\n        var drafts = window.draftManager.getAllDrafts();\r\n        \r\n        if (drafts.length > 0) {\r\n            // If multiple drafts exist, show selection dialog\r\n            if (drafts.length > 1) {\r\n                showDraftSelectionDialog(drafts);\r\n            } else {\r\n                promptToRestoreDraft(drafts[0].data);\r\n            }\r\n        }\r\n    } catch (e) {\r\n        console.error('Error restoring draft: ' + e);\r\n        g_form.addErrorMessage('Failed to restore draft: ' + e.message);\r\n    }\r\n}\r\n\r\nfunction showDraftSelectionDialog(drafts) {\r\n    var dialog = new GlideModal('select_draft_dialog');\r\n    dialog.setTitle('Available Drafts');\r\n    \r\n    var html = '<div class=\"draft-list\">';\r\n    drafts.forEach(function(draft, index) {\r\n        var date = new Date(draft.data.timestamp).toLocaleString();\r\n        html += '<div class=\"draft-item\" onclick=\"selectDraft(' + index + ')\">';\r\n        html += '<strong>Draft ' + (index + 1) + '</strong> - ' + date;\r\n        html += '<br>Modified fields: ' + draft.data.fields_modified.join(', ');\r\n        html += '</div>';\r\n    });\r\n    html += '</div>';\r\n    \r\n    dialog.renderWithContent(html);\r\n}\r\n\r\nfunction promptToRestoreDraft(draftInfo) {\r\n    var timestamp = new Date(draftInfo.timestamp);\r\n    if (confirm('A draft from ' + timestamp.toLocaleString() + ' was found. Would you like to restore it?')) {\r\n        Object.keys(draftInfo.data).forEach(function(field) {\r\n            g_form.setValue(field, draftInfo.data[field]);\r\n        });\r\n        g_form.addInfoMessage('Draft restored from ' + timestamp.toLocaleString());\r\n    }\r\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Auto Save Draft Feature/basic_implementation.js",
    "content": "/**\r\n * Basic Auto-save Draft Implementation\r\n * This version provides core functionality for auto-saving catalog item form data\r\n */\r\n\r\nfunction onLoad() {\r\n    var autosaveInterval = 60000; // 1 minute\r\n    \r\n    // Try to restore previous draft\r\n    restoreLastDraft();\r\n    \r\n    // Set up auto-save interval\r\n    setInterval(function() {\r\n        if (g_form.isModified()) {\r\n            saveDraft();\r\n        }\r\n    }, autosaveInterval);\r\n}\r\n\r\nfunction saveDraft() {\r\n    try {\r\n        var draftData = {};\r\n        g_form.serialize(draftData);\r\n        \r\n        var draftKey = 'catalogDraft_' + g_form.getUniqueValue();\r\n        sessionStorage.setItem(draftKey, JSON.stringify({\r\n            timestamp: new Date().getTime(),\r\n            data: draftData\r\n        }));\r\n        \r\n        g_form.addInfoMessage('Draft saved automatically');\r\n    } catch (e) {\r\n        console.error('Error saving draft: ' + e);\r\n    }\r\n}\r\n\r\nfunction restoreLastDraft() {\r\n    try {\r\n        var draftKey = 'catalogDraft_' + g_form.getUniqueValue();\r\n        var savedDraft = sessionStorage.getItem(draftKey);\r\n        \r\n        if (savedDraft) {\r\n            var draftData = JSON.parse(savedDraft);\r\n            var timestamp = new Date(draftData.timestamp);\r\n            \r\n            if (confirm('A draft from ' + timestamp.toLocaleString() + ' was found. Would you like to restore it?')) {\r\n                Object.keys(draftData.data).forEach(function(field) {\r\n                    g_form.setValue(field, draftData.data[field]);\r\n                });\r\n                g_form.addInfoMessage('Draft restored from ' + timestamp.toLocaleString());\r\n            } else {\r\n                sessionStorage.removeItem(draftKey);\r\n            }\r\n        }\r\n    } catch (e) {\r\n        console.error('Error restoring draft: ' + e);\r\n    }\r\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Auto Save Draft Feature/script.js",
    "content": "/**\r\n * Auto-save draft feature for Catalog Client Script\r\n * \r\n * This script automatically saves form data as a draft in the browser's sessionStorage\r\n * every minute if changes are detected. It also provides functionality to restore\r\n * the last saved draft when the form is loaded.\r\n */\r\n\r\n// Executes when the form loads\r\nfunction onLoad() {\r\n    var autosaveInterval = 60000; // 1 minute\r\n    \r\n    // Try to restore previous draft\r\n    restoreLastDraft();\r\n    \r\n    // Set up auto-save interval\r\n    setInterval(function() {\r\n        if (g_form.isModified()) {\r\n            saveDraft();\r\n        }\r\n    }, autosaveInterval);\r\n}\r\n\r\n// Saves the current form state as a draft\r\nfunction saveDraft() {\r\n    try {\r\n        var draftData = {};\r\n        g_form.serialize(draftData);\r\n        \r\n        var draftKey = 'catalogDraft_' + g_form.getUniqueValue();\r\n        sessionStorage.setItem(draftKey, JSON.stringify({\r\n            timestamp: new Date().getTime(),\r\n            data: draftData\r\n        }));\r\n        \r\n        g_form.addInfoMessage('Draft saved automatically');\r\n    } catch (e) {\r\n        console.error('Error saving draft: ' + e);\r\n    }\r\n}\r\n\r\n// Restores the last saved draft if available\r\nfunction restoreLastDraft() {\r\n    try {\r\n        var draftKey = 'catalogDraft_' + g_form.getUniqueValue();\r\n        var savedDraft = sessionStorage.getItem(draftKey);\r\n        \r\n        if (savedDraft) {\r\n            var draftData = JSON.parse(savedDraft);\r\n            var timestamp = new Date(draftData.timestamp);\r\n            \r\n            // Ask user if they want to restore the draft\r\n            if (confirm('A draft from ' + timestamp.toLocaleString() + ' was found. Would you like to restore it?')) {\r\n                Object.keys(draftData.data).forEach(function(field) {\r\n                    g_form.setValue(field, draftData.data[field]);\r\n                });\r\n                g_form.addInfoMessage('Draft restored from ' + timestamp.toLocaleString());\r\n            } else {\r\n                // Clear the draft if user chooses not to restore\r\n                sessionStorage.removeItem(draftKey);\r\n            }\r\n        }\r\n    } catch (e) {\r\n        console.error('Error restoring draft: ' + e);\r\n    }\r\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Auto-populate field from URL/README.md",
    "content": "This piece of code is designed for an usecase where you might want to populate a field value that you're passing as a query in the URL which redirects to a catalog item.\nIn this case, a custom field 'u_date' is chosen as an example to be shown:\n\n1. You open a catalog item record via a URL that carries a date in the query string.\nExample:\nhttps://your-instance.service-now.com/your_form.do?sysparm_u_date=2025-10-31\n-(This URL includes a parameter named sysparm_u_date with the value 2025-10-31.)\n\n\n2. The catalog client script reads the page URL and extracts that specific parameter which returns the value \"2025-10-31\".\n\n3. If the parameter is present, the script populates the form field.\nCalling g_form.setValue('u_date', '2025-10-31') sets the date field on the form to 31 October 2025.\n\n\nResult:\nThe date field in the form is prefilled from the URL\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Auto-populate field from URL/popdatefromurl.js",
    "content": "//Logic to fetch the u_date field value passed in the url and setting it in the actual field.\n\n\nvar fetchUrl = top.location.href; //get the URL\n\nvar setDate = new URLSearchParams(gUrl).get(\"sysparm_u_date\");  //fetch the value of date from the query parameter\n\ng_form.setValue('u_date', setDate);   //set the value to the actual field\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autofilling the request details from previous request/Auto fill script include.JS",
    "content": "var GetRecentRequestValues = Class.create();\nGetRecentRequestValues.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  getValues: function() {\n    var userID = this.getParameter('sysparm_user');\n    var itemID = this.getParameter('sysparm_item');\n    var result = { found: false, values: {} };\n\n    var gr = new GlideRecord('sc_req_item');\n    gr.addQuery('requested_for', userID);\n    gr.addQuery('cat_item', itemID);\n    gr.orderByDesc('sys_created_on');\n    gr.setLimit(1);\n    gr.query();\n\n    if (gr.next()) {\n      result.found = true;\n\n     \n      var vars = gr.variables;\n      result.values = {\n        'requested_for': vars.requested_for + '',\n        'location': vars.location + '',\n        'department': vars.department + ''\n      };\n    }\n\n    return JSON.stringify(result);\n  }\n});\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autofilling the request details from previous request/Client script Autofill.js",
    "content": "function onLoad() {\n  var user = g_user.userID;\n  var itemID = g_form.getUniqueValue();\n\n  var ga = new GlideAjax('GetRecentRequestValues');\n  ga.addParam('sysparm_name', 'getValues');\n  ga.addParam('sysparm_user', user);\n  ga.addParam('sysparm_item', itemID);\n  ga.getXMLAnswer(function(response) {\n    var data = JSON.parse(response);\n    if (data && data.found) {\n      var confirmFill = confirm(\"We found a similar request. Do you want to autofill fields?\");\n      if (confirmFill) {\n        for (var field in data.values) {\n          if (g_form.getControl(field)) {\n            g_form.setValue(field, data.values[field]);\n            console.log(\"Set \" + field + \" to \" + data.values[field]);\n          } else {\n            console.log(\"Field not found: \" + field);\n          }\n        }\n      }\n    } else {\n      console.log(\"No previous request found.\");\n    }\n  });\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autofilling the request details from previous request/Readme.md",
    "content": "Recent Request Autofill for ServiceNow Catalog.it automatically offers to fill in fields based on the user's most recent similar request.\n Features\n- Detects previous requests for the same catalog item\n- Prompts user to reuse values from their last submission\n- Autofills fields like location, department, and justification\n\n<img width=\"878\" height=\"395\" alt=\"image\" src=\"https://github.com/user-attachments/assets/33ceabf5-2bbc-43e3-8792-f1f9a99699d2\" />\n\n\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autopopulate Department/README.md",
    "content": "# Autopopulate Department Catalog Client Script\n\nUse this onChange catalog client script to populate a **department** variable in a catalog item based on a modifiable **requested_for**. Both variables must be reference type pointing to their respective tables.\n\n## Use case\n\nThe GlideUser API (g_user) does not provide a way to retrieve a user's department in a client-side script. Here is a small snippet using `g_form.getReference()` to retrieve the user from the **requested_for** variable and populate the **department** variable in a catalog item while allowing the submitter to still change the department if the data is incorrect.\n\nAttach this client script to a variable set for easy reuse. It can be augmented with a number of other fields from the user record such as email, phone number, manager, etc. Just be mindful of field types and whether the desired field will return a sys_id or display text.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autopopulate Department/autopopulateDepartment.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n  g_form.getReference(\"requested_for\", function (gr) {\n    g_form.setValue(\"department\", gr.department);\n    g_form.setValue(\"email\", gr.email);\n    g_form.setValue(\"phone\", gr.phone);\n  });\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autopopulate user information fields/ClientCallableScriptInclude.js",
    "content": "/*\n* The following is a client callable script include. This can be used with the onChange Client script to be able to gather the data on the server side\n*/\n\nvar ReferenceQualifierAjaxHelper = Class.create();\nReferenceQualifierAjaxHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  getUserInformation : function() {\n    var userID = this.getParameter('sysparm_user');\n    var userRec = new GlideRecord('sys_user');\n\n    if(userRec.get(userID)) {\n      var results = {\n        \"email\" : userRec.getValue('email'),\n        \"department\" : userRec.getValue('department'),\n        \"title\" : userRec.getValue('title'),\n        \"phone\" : userRec.getValue('phone')\n      };\n\n      return JSON.stringify(results)\n    }\n\n  },\n  \n  type: 'ReferenceQualifierAjaxHelper'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autopopulate user information fields/README.md",
    "content": "## Overview\nThis onchange catalog client script and script inlcude work together autopopulate the user fields that might show up on a catalog item. In the \nglobal scope you will have to create the client callable script include to be able to use the Ajax call that is in the on change client script.\nIn this example we use the OOB Requested For field that already auto populates the user that is logged in then we go to the server to get that \nusers information. The fields that are brough back are the ones that are in the code but you can modify to bring back more or less fields if needed.\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Autopopulate user information fields/onChangeClientScript.js",
    "content": "/*\n* In order for this to work make sure to have an onChange catalog client script on a variable that is type Requested For. This variable\n* already autopopulates the logged in user with its OOB functionality. In the updateUserFields function you can add any other user fields\n* that you might need. \n*/\n\nfunction onChange(control, oldValue, newValue, isLoading) {\n  //This variable will store the sys_id of the user that populates in your requested for variable\n  var userID = newValue;\n\n  var ga = new GlideAjax(ReferenceQualifierAjaxHelper);\n  ga.addParam('sysparm_name', 'getUserInformation');\n  ga.addParam('sysparm_user', userID);\n  ga.getXMLAnswer(updateUserFields);\n\n  function updateUserFields(response) {\n    var returnedData = JSON.parse(response);\n    g_form.setValue(\"email\", returnedData.email);\n    g_form.setValue(\"department\", returnedData.department);\n    g_form.setValue(\"title\", returnedData.title);\n    g_form.setValue(\"phone\", returnedData.phone);\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Block Submit/README.md",
    "content": "Code Snippet to block submission of catalog item based on answer to other yes/no variable.\n\n#update\nTo fix a task from issue #745\nReplace JavaScript alert() method to GlideModal() API.\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Block Submit/block_submit.js",
    "content": "//Block the user from submitting the form based on variable answer\nfunction onSubmit() {\n  var someVariable = g_form.getValue(\"someVariable\");\n  if(someVariable == 'No'){\n\tvar gm = new GlideModal('glide_warn',false);\n        gm.setTitle(\"Submit Blocked! You can only use this form for someReason.  Review someInstructions\");\n        gm.render();\n    \treturn false; // this stops user from submitting the form\n    }\n  return true; // allow form submit\n}\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Calculate age on based on date of birth/Calculate age based on dob.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n       return;\n    }\n \n function calculateAge(dateOfBirth) {\n     if (!dateOfBirth || isNaN(Date.parse(dateOfBirth))) {\n         alert('Invalid date of birth provided.');\n         return;\n     }\n     var dob = new Date(dateOfBirth);\n     var currentDate = new Date();\n     var age = calculateAgeDifference(dob, currentDate);\n     var ageString = '';\n \n     if (age.years > 0) {\n         ageString += age.years + ' years';\n         if (age.months > 0 || age.days > 0) {\n             ageString += ', ';\n         }\n     }\n \n     if (age.months > 0) {\n         ageString += age.months + ' months';\n         if (age.days > 0) {\n             ageString += ', ';\n         }\n     }\n \n     if (age.days > 0) {\n         ageString += age.days + ' days';\n     }\n      return ageString;\n }\n \n function calculateAgeDifference(startDate, endDate) {\n     var years = endDate.getFullYear() - startDate.getFullYear();\n     var months = endDate.getMonth() - startDate.getMonth();\n     var days = endDate.getDate() - startDate.getDate();\n \n     if (days < 0) {\n         months--;\n         days += new Date(endDate.getFullYear(), endDate.getMonth(), 0).getDate();\n     }\n \n     if (months < 0) {\n         years--;\n         months += 12;\n     }\n \n     return { years: years, months: months, days: days };\n }\n \n var dateOfBirth = newValue;\n var age = calculateAge(dateOfBirth);\n g_form.setValue('age', age);\n }"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Calculate age on based on date of birth/README.md",
    "content": "# Age Calculator - On Change Client Script\n\nThis script is designed to calculate a person's age based on their date of birth.\n\n## Functions\n\n- `calculateAge(dateOfBirth)`: Calculates the age in years, months, and days based on the provided date of birth.\n- `calculateAgeDifference(startDate, endDate)`: Computes the difference in years, months, and days between two dates.\n\n## Sample Input and Output\n\n![age_calculator](image.png)"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Catalog Approval/Readme.md",
    "content": "This project adds a dynamic preview feature to Service Catalog items, allowing users to see the full approval chain before submitting a request. It improves transparency, reduces confusion, and helps users understand who will be involved in the approval process based on their selections.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Catalog Approval/client script.js",
    "content": "function onLoad() {\n  var ga = new GlideAjax('ApprovalChainHelper');\n  ga.addParam('sysparm_name', 'getApprovers');\n  ga.addParam('sysparm_item_id', g_form.getUniqueValue());\n  ga.getXMLAnswer(function(response) {\n    var approvers = JSON.parse(response);\n    var message = 'This request will be approved by: ' + approvers.join(', ');\n    g_form.showFieldMsg('requested_for', message, 'info');\n  });\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Catalog Approval/script include.js",
    "content": "var ApprovalChainHelper = Class.create();\nApprovalChainHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  getApprovers: function() {\n    var itemId = this.getParameter('sysparm_item_id');\n    var userId = gs.getUserID();\n\n    var approvers = [];\n\n    // Example logic: fetch approval rules based on item and user\n    var ruleGR = new GlideRecord('sysapproval_approver');\n    ruleGR.addQuery('document_id', 80f8920bc3e4b2105219daec050131e3);\n    ruleGR.query();\n\n    while (ruleGR.next()) {\n      approvers.push(ruleGR.approver.name.toString());\n    }\n\n    return JSON.stringify(approvers);\n  }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Clear all fields/README.md",
    "content": "# Clear all fields on a catalog item form\n\nThis function clears all editable fields on a form, except those explicitly excluded.  \nIt works on both the native platform (Classic UI) and Service Portal / Mobile.  \nTypically used with an OnChange catalog client script when you want to clear all fields after a certain variable changes.\n\nThe function returns an array of the field names that were cleared, which can be used for logging or further processing.\n\n### Exclusion Support\n\nYou can pass an array of field names to exclude from being cleared.  \nThis is useful when you want to preserve the value of the field that triggered the change or other important fields.\n\n### Example\n```\nclearFields(['short_description', 'priority']);\n```\n// Clears all fields except 'short_description' and 'priority'\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Clear all fields/script.js",
    "content": "/**\n * Clears or resets all editable fields on a form, except those explicitly excluded.\n * Compatible with Classic UI and Service Portal/Mobile.\n * Intended for use in onChange client scripts.\n *\n * @function clearFields\n * @param {Array} dontClearFieldsArray - Array of field names to exclude from clearing.\n * @returns {Array} - Array of field names that were cleared.\n *\n * @example\n * // Clears all fields except 'short_description' and 'priority'\n * clearFields(['short_description', 'priority']);\n */\nfunction clearFields(dontClearFieldsArray) {\n    // Ensure the exclusion list is defined and is an array\n    dontClearFieldsArray = Array.isArray(dontClearFieldsArray) ? dontClearFieldsArray : [];\n\n    // Helper function to check if a field should be cleared\n    function shouldClear(fieldName) {\n        return dontClearFieldsArray.indexOf(fieldName) === -1;\n    }\n\n    var clearedFields = [];\n\n    try {\n        // Classic UI: use g_form.nameMap to get all fields\n        var allFields = g_form.nameMap;\n        allFields.forEach(function(field) {\n            var fieldName = field.prettyName;\n            if (shouldClear(fieldName)) {\n                g_form.clearValue(fieldName);\n                clearedFields.push(fieldName);\n            }\n        });\n    } catch (e) {\n        // Service Portal or Mobile: use getEditableFields()\n        var editableFields = g_form.getEditableFields();\n        editableFields.forEach(function(fieldName) {\n            if (shouldClear(fieldName)) {\n                g_form.clearValue(fieldName);\n                clearedFields.push(fieldName);\n            }\n        });\n    }\n\n    return clearedFields;\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Combine variables into Description/README.md",
    "content": "OnSUbmit Catalog Client script is created to Combine all variable values required and display in Description field.\nSteps:\n1. Navigate to your instance open catalog client script table [catalog_script_client]\n2. Create new catalog client script -> click new\n3. Provide following values:\n      - Name: Any relevant to your script\n      - Applies to: A catalog Item\n      - UI Type: All\n      - Isolated script: checked\n      - Application: Application scope applies to\n      - Type: onSubmit\n      - Catalog item: select your catalog item\n 4. create the script as per script.js file.  \n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Combine variables into Description/script.js",
    "content": "// Combine variables into Description\n// Type: onSubmit\n\nfunction onSubmit()\n{\n  \n  // Combine provided all fields to Description field\n  \n  var description = g_form.getValue ('description');\n  var first = g_form.getDisplayValue ('first_variable');\n  var second = g_form.getDisplayValue ('second_variable');\n  var third = g_form.getDisplayValue ('third_variable');\n  \n  g_form.setValue('description', ''+ description + '\\n\\nType: ' + first + '\\n Text: ' + second + '\\nText: ' + third);\n  return true;\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Control all RITM variables in one go/README.md",
    "content": "# Control all RITM variables in one go\n\nRequirement : We need to make all the variables on the RITM (sc_req_item) form read only or editable\n\nProblem : If there are so many variables then it becomes difficult to write multiple UI policies or writing multiple lines of code in catalog client script for each variable seperately.\n\nSolution : With the above code snippet you can control(make read-only or editable) all the variables in the RITM form with very minimal code)\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Control all RITM variables in one go/script.js",
    "content": "function onLoad()\n{\n  \n   g_form.setVariablesReadOnly(true); //if you want to make all variables read-only\n  \n   g_form.setVariablesReadOnly(false); //if you want to make all variables editable\n  \n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Currency Validation/README.md",
    "content": "## Currency Validation \n\nUse this catalog client script to validate the value of a variable used to get currency. As of now 3 things are being checked in the script but you can make changes as per requirement.\n\n1) Characters after the $ sign should be numerics.\n2) Entered value should have a decimal point.\n3) There must be 2 digits only after the decimal.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Currency Validation/currency_validation.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n\n    var cost = g_form.getValue('variable_name'); //update variable name used for currency\n    cost = cost.trim();\n    // first character should be dollar sign\n    var firstChar = cost.substring(0, 1);\n    if (firstChar != '$') {\n        validationAlert(oldValue);\n    }\n\n    // characters after the $ sign should be numerics\n    var costType = isNaN(cost.substring(1));\n    if (costType == true) {\n        validationAlert(oldValue);\n    }\n\n    // entered value should have a decimal point\n    var num = cost.substring(1);\n    if (num.indexOf('.') == -1) {\n        validationAlert(oldValue);\n    }\n\n    // there must be 2 digits only after the decimal\n    var decNum = num.substring(num.indexOf('.') + 1, num.length);\n    if (decNum.length != 2) {\n        validationAlert(oldValue);\n    }\n}\n\nfunction validationAlert(oldValue) {\n    g_form.setValue(\"variable_name\", oldValue);\n    var gm = new GlideModal(\"glide_warn\");\n    gm.setTitle(\"Currency formatting problem\");\n    gm.setPreference(\"title\", \"Please enter cost in $0.00 format\");\n    gm.render();\n    return;\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/CustomAlert/README.md",
    "content": "These scripts helps you to create custom popup easily. follow the below steps to implement it on your instance.\n\nSteps\n1. Create UI page with name \"custom_alert_box\"\n2. Copy HTML section from customn_alert_box.js to HTML field of UI Page\n3. Copy Client Script section from customn_alert_box.js to Client Script field of UI Page\n4. UI Page is ready to be used.\n5. Refer the \"custom_alert.js\" to create client script or catalog client script to alert HTML messages or links in custom alert format.\n6. Refer the screenshots folder for quick look.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/CustomAlert/Screenshots/README.md",
    "content": "Please see the example Screenshots\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/CustomAlert/custom_alert.js",
    "content": "function onLoad() {\n    // Function to show a custom link in a GlideModal\n    function showCustomLinkInGlideModal() {\n        // Create an instance of GlideModal using the 'custom_alert_box' UI page\n        // The second parameter 'true' indicates that the modal should be a dialog,\n        // and '600' sets the width of the modal to 600 pixels.\n        var gm = new GlideModal(\"custom_alert_box\", true, 600);\n\n        // Set the modal's title to anything you want\n        gm.setTitle('Important Information'); //for e.g. Important Information\n        \n        // Set a preference for the modal indicating the type of alert\n        // This can be used to style the modal or control its behavior.\n        // available choices {info, danger, warning, success}\n        gm.setPreference('alertType', 'danger');\n\n        // Custom HTML content to be displayed in the modal\n        // This includes a paragraph and a link to an external website.\n        var htmlContent = '<p>Please visit the following link:</p>' +\n            '<a href=\"https://example.com\" target=\"_blank\">Click here to go to Example.com</a>';\n\n        // Set the HTML content of the modal using the 'infoText' preference.\n        // We disable escaping since we're providing our own HTML.\n        gm.setPreference('infoText', htmlContent);\n\n        // Render the modal on the screen\n        gm.render();\n    }\n\n    // Call the function to display the modal when the form loads\n    showCustomLinkInGlideModal();\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/CustomAlert/custom_alert_box.js",
    "content": "*****************HTML Section Start*****************\n<style>\n    /* dialog styles */\n    .dialog_content {\n        width: 100%;\n        height: 100px;\n        vertical-align: middle;\n        min-width: 300px;\n        padding: 0 10px 10px 10px;\n    }\n\n    .dialog_buttons {\n        display: inline;\n        text-align: right;\n        vertical-align: bottom;\n        white-space: nowrap;\n    }\n\n    .modal-header {\n        background-color: #d9edf7;\n        color: #31708f;\n    }\n\n    .modal-content {\n        border-color: #bce8f1;\n        border-width: medium !important;\n    }\n</style>\n<g:ui_form onsubmit=\"return invokePromptCallBack();\">\n    <g2:evaluate>\n        var infoText = \"${RP.getWindowProperties().get('infoText')}\";\n        infoText = new GlideStringUtil().unEscapeHTML(infoText);\n        var warning = \"${RP.getWindowProperties().get('warning')}\";\n        warning = new GlideStringUtil().unEscapeHTML(warning);\n        var alertType = \"${RP.getWindowProperties().get('alertType')}\";\n    </g2:evaluate>\n    <j2:if test=\"$[alertType == 'warning']\">\n        <style>\n            .modal-header {\n                background-color: #fcf8e3;\n                color: #8a6d3b;\n            }\n\n            .modal-content {\n                border-color: #faebcc;\n                border-width: medium !important;\n            }\n        </style>\n    </j2:if>\n\t<j2:if test=\"$[alertType == 'danger']\">\n        <style>\n            .modal-header {\n                background-color: #f2dede;\n                color: #a94442;\n            }\n\n            .modal-content {\n                border-color: #ebccd1;\n                border-width: medium !important;\n            }\n        </style>\n    </j2:if>\n\t<j2:if test=\"$[alertType == 'success']\">\n        <style>\n            .modal-header {\n                background-color: #dff0d8;\n                color: #3c763d;\n            }\n\n            .modal-content {\n                border-color: #d6e9c6;\n                border-width: medium !important;\n            }\n        </style>\n    </j2:if>\n    <table border=\"0\" width=\"100%\">\n        <tr>\n            <td>\n                <table border=\"0\" width=\"100%\">\n                    <tr>\n                        <td class=\"dialog_content\" id=\"bodycell\"></td>\n                    </tr>\n                    <tr>\n                        <td class=\"dialog_buttons\">\n                            <g:dialog_button_ok ok=\"invokePromptCallBack();\" ok_type=\"button\" />\n                        </td>\n                    </tr>\n                </table>\n            </td>\n        </tr>\n    </table>\n</g:ui_form>\n*****************HTML Section End*****************\n\n*****************Client Script Start*****************\nfunction unescapeHTML(html) {\n    var textarea = document.createElement('textarea');\n    textarea.innerHTML = html; // Set the HTML content\n    return textarea.value; // Return the unescaped text\n}\n\nvar infoText = \"${RP.getWindowProperties().get('infoText')}\";\ninfoText = unescapeHTML(infoText); // Unescape the HTML\n\n// Now set the title to your dialog or display it\ndocument.getElementById('bodycell').innerHTML = infoText; // Assuming there's a titleCell in your HTML\n\nfunction invokePromptCallBack() {\n    var gdw = GlideDialogWindow.get();\n    gdw.destroy();\n    return false;\n}\n\nvar gdw = GlideDialogWindow.get();\ngel('ok_button').focus();\n*****************Client Script Start*****************\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Date Management/Date Management.js",
    "content": "//Various useful ways to interact with dates on the client without needing to make a trip to the server\n\n\n//Use the following to take a date / date time variable and turn it to a JS Date value\n/*newValue = date(/time) variable value, i.e from an onChange script\n/*getDateFromFormat = This is a ServiceNow provided function loaded into the browser\n/*g_user_date_format = a variable loaded into the browser on session load that stores the users date format\n*/\nvar date = new Date(getDateFromFormat(newValue, g_user_date_format));\n\n\n//Validate a value is a date\n//This is a function loaded into the browser by ServiceNow\n//i.e isDate(newValue , g_user_date_format);\n//Returns a Boolean\n\nvar checkDate = isDate(value , format);\n\n\n//Compare two dates\n//This is a function loaded into the browser by ServiceNow\n//i.e compareDates(\"29-10-2021\" , \"dd-MM-yyyy\" , \"20-10-2021\" , \"dd-MM-yyyy\");\n//Returns -1 if either date value is not a valid date\n//Returns 1 if date1 is greater than date2\n//Returns 0 otherwise\n\nvar isSecondDateLarger = compareDates(\"29-10-2021\" , \"dd-MM-yyyy\" , \"20-10-2021\" , \"dd-MM-yyyy\");\n\n\n//Format a date value to the user session format to save in a variable/field\n\nvar date = formatDate(date,format);\n\n/*Exampe of the above in use*/\nvar dateNumber = getDateFromFormat(newValue , g_user_date_format);\nvar date = new Date(dateNumber);\ndate.setDate(date.getDate() - 1);\ng_form.setValue('date' , formatDate(date , g_user_date_format));\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Date Management/README.md",
    "content": "#### Useful ways to interact with variable date/time values without needing to make a trip to the server\n\nThe following functions and variables are defined by ServiceNow and loaded at run-time. They are accessible from within the client script without the need to turn off script isolation.\n\n- g_user_date_format\n- g_user_date_time_format\n- isDate()\n- compareDates()\n- formatDate()\n- getDateFromFormat()\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Document validation/Client script.JS",
    "content": "function onSubmit() {\n  var ga = new GlideAjax('DocumentValidationHelper');\n  ga.addParam('sysparm_name', 'validateAttachments');\n  ga.addParam('sysparm_item_id', g_form.getUniqueValue());\n  ga.getXMLAnswer(function(response) {\n    if (response !== 'valid') {\n      alert('Document validation failed: ' + response);\n      return false;\n    }\n  });\n  return true;\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Document validation/Readme.md",
    "content": "This project enhances a Service Catalog item by allowing users to upload supporting documents (e.g., ID proof, approval letters) and validating them before the request is submitted. It ensures compliance, completeness, and proper documentation for sensitive or regulated requests.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Document validation/Script include.js",
    "content": "var DocumentValidationHelper = Class.create();\nDocumentValidationHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  validateAttachments: function() {\n    var itemId = this.getParameter('sysparm_item_id');\n    var attachmentGR = new GlideRecord('sys_attachment');\n    attachmentGR.addQuery('table_name', 'sc_req_item');\n    attachmentGR.addQuery('table_sys_id', itemId);\n    attachmentGR.query();\n\n    while (attachmentGR.next()) {\n      var fileName = attachmentGR.file_name.toLowerCase();\n      if (!fileName.endsWith('.pdf') && !fileName.endsWith('.docx')) {\n        return 'Only PDF or DOCX files are allowed.';\n      }\n      if (attachmentGR.size_bytes > 5 * 1024 * 1024) {\n        return 'File size exceeds 5MB limit.';\n      }\n    }\n\n    return 'valid';\n  }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Dynamically Update Reference Qualifier/Catalog Item onLoad.js",
    "content": "function onLoad() {\n\tvar filterString = 'sys_class_name=cmdb_ci_ip_router^ORsys_class_name=cmdb_ci_ip_switch^ORsys_class_name=cmdb_ci_vpn' //Reference qualifier for this Catalog Item\n\t//alternate method for Service Portal only\n\t// if (window == null){ //Service Portal method\n\t// \tvar setfilter = g_list.get('v_configuration_item');\n\t// \tsetfilter.setQuery(filterString);\n\t// } else { //native UI method\n\t\tvar ga = new GlideAjax('refQualUtils'); //Client callable Script Include Name\n\t\tga.addParam('sysparm_name', 'setSysProp'); //Function in Script Include\n\t\tga.addParam('sysparm_sys_prop_name', 'sr.ref_qual.ci'); //System Property Name used in Reference qualifier \n\t\tga.addParam('sysparm_sys_prop_value', filterString);\n\t\tga.getXML(getResponse);\n\t\t\t\n\t\tfunction getResponse(response) { //to avoid Service Portal 'There is a JavaScript error in your browser console'\n\t\t\tvar answer = response.responseXML.documentElement.getAttribute(\"answer\"); \n\t\t}\n\t//}\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Dynamically Update Reference Qualifier/README.md",
    "content": "When we have a reference variable that is used in a (single row) variable set, sometimes we want to update the Reference qualifier only for specific Catalog Item(s).\n\nIn this example, I have a Configuration item (named v_configuration_item) reference variable (cmdb_ci table) in a single row variable set that has been included in a number of Catalog Items.  The simple Reference qualifier for this variable is:\nsys_class_name=cmdb_ci_ip_router^ORsys_class_name=cmdb_ci_ip_switch\n\nLet's say for one particular Catalog Item, I also want to include the class of VPN (cmdb_ci_vpn).\n\nTo do this without having to create another variable with the new qualifier and hiding the variable set variable, there are some preparation steps, including those which ensure that the other Catalog Items using the variable set are not disrupted:\n\n1) Change the advanced reference qualifier on the variable to: **javascript: gs.getProperty(\"sr.ref_qual.ci\");**\nusing a System Property Name of your choice\n\n2) Create a System Property with the same Name used in the Reference qualifier, leaving the Value empty.\n\n3) Add the included 'Variable Set onLoad' Catalog Client Script that applies to the Variable set with...\n\n4) The included Client Callable Script Include to update the System Property Value to the Reference Qualifier that was replaced.  This Script Include uses parameters for the System Property Name and Value, so it can be re-used in every instance of this solution.\n\nNow that the other Catalog Items using the variable set are still working as they were, all you need to do to update the Reference qualifier on certain Catalog Item(s) is:\n\n5) Add the included 'Catalog Item onLoad' Catalog Client Script that applies to the Catalog Item. Set the Order of this script to a high number (10,000) so that it runs after the variable set one.\n\nThis solution works in both the Native UI and Service Portal. The Catalog Client scripts contain an alternate Service Portal only approach in the commented if block that can be used in conjunction with the native UI approach in the else block. This alternate Service Portal solution was developed in collaboration with Chris Perry.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Dynamically Update Reference Qualifier/Script Include.js",
    "content": "var refQualUtils = Class.create();\nrefQualUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n   \n   \tsetSysProp: function(){\n\t\tvar propertyName = this.getParameter('sysparm_sys_prop_name');\n\t\tvar propertyValue =  this.getParameter('sysparm_sys_prop_value');\n\t\tvar property = gs.getProperty(propertyName);\n\t\tgs.setProperty(propertyName, propertyValue);\n\t\treturn;\n\t},\n\t\t\n    type: 'refQualUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Dynamically Update Reference Qualifier/Variable Set onLoad.js",
    "content": "function onLoad() {\n\tvar filterString = 'sys_class_name=cmdb_ci_ip_router^ORsys_class_name=cmdb_ci_ip_switch' //Reference qualifier that was replaced\n\t//alternate method for Service Portal only\n\t// if (window == null){ //Service Portal method\n\t// \tvar setfilter = g_list.get('v_configuration_item');\n\t// \tsetfilter.setQuery(filterString);\n\t// } else { //native UI method\n\t\tvar ga = new GlideAjax('refQualUtils'); //Client callable Script Include Name\n\t\tga.addParam('sysparm_name', 'setSysProp'); //Function in Script Include\n\t\tga.addParam('sysparm_sys_prop_name', 'sr.ref_qual.ci'); //System Property Name used in Reference qualifier \n\t\tga.addParam('sysparm_sys_prop_value', filterString);\n\t\tga.getXML(getResponse);\n\t\t\t\n\t\tfunction getResponse(response) { //to avoid Service Portal 'There is a JavaScript error in your browser console'\n\t\t\tvar answer = response.responseXML.documentElement.getAttribute(\"answer\"); \n\t\t}\n\t//}\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Get Display Value of MRVS/README.md",
    "content": "## Get Display Value of MultiRow Variableset (MRVS)\n\nWhile there are different ways to do this, the easiest of them is to leverage an out of box script named **'VariableUtil'**. \n\nThe script is present in the global scope and contains an function named getDisplayValue aptly. This function seems to work on both the normal variable as well as multirow variableset. You just need to pass the sysid of the variable or the multirow variableset and its corresponding value.\n\n**new VariableUtil().getDisplayValue('sys_id of variable or MRVS','value of variable/MRVS');**\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Get Display Value of MRVS/mrvs.js",
    "content": "\nvar script = new global.VariableUtil();\nvar gr = new GlideRecord(\"sc_req_item\");\ngr.addEncodedQuery(\"sys_id=<Copy & Paste RITM Sys ID>\");\ngr.query();\nif (gr.next()) {\n    gs.info(script.getDisplayValue('<MRVS Variableset SYSID>', gr.variables.MRVSName)); //MRVS Display Value\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Get MRVS Values from Parent/README.md",
    "content": "# Get Multi-row Variable Set Values from parent form\n\nSometimes you need to query the current set of values for a MRVS from the actual MRVS or another MRVS. \nThis requires getting the data from the parent form, the method to retrieve and the format of the data is different\nwhen running on the platform or portal.\n\nThis script gives a way of getting the values regardless of the platform in use.\n\nOn the platform (backend) this could be moved to a global UI Script, but that is not available to portal scripts.\n\nMake sure the UI Type is All, and Isolate Script is false (unchecked).\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Get MRVS Values from Parent/onload.js",
    "content": "function onLoad() {\n\n    /*\n     * work out the parent form based on platform and return the given MRVS data\n     * This could go in a global UI script but that wouldn't work for Portal, \n     * would love anyone to suggest an alternative for portal :)\n     */\n    function getMRVSDataFromParent(mrvsName) {\n        var parent_g_form = g_service_catalog.parent; // default for backend/platform\n        if (parent.angular) {\n            // this is portal so get a different way\n            var parentItem = parent.angular.element(parent.$('#sc_cat_item').find('sp-variable-layout')[0]).scope();\n            parent_g_form = parentItem.getGlideForm();\n        }\n        var vmData = parent_g_form.getValue(mrvsName);\n        // on portal we get back an empty string rather than an empty array so convert\n        return vmData == '' ? [] : JSON.parse(vmData);\n    }\n\n    var vmJSONData = getMRVSDataFromParent('virtual_machine');\n    console.log(\"JSON \" + JSON.stringify(vmJSONData, '', 3));\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Hide Variables of Catalog Item on Order Guide/Hide Variables.js",
    "content": "function onLoad(){\n  if(g_service_catalog.isOrderGuide()){\n    //variable_name1, varaible_name2 are the fields already present on the Order guide, hence hiding below fields on the catalog form when the catalog form is used through an order guide.\n    g_form.setDisplay('varible_name1',false);\n    g_form.setDisplay('varible_name2',false);\n  }\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Hide Variables of Catalog Item on Order Guide/README.md",
    "content": "1. The onLoad catalog client script can be used to hide the catalog varaibles on catalog form when the catalog item is being used on OrderGuide and cascade varaibles field is enabled on Order guide.\n2. Cascading enables the transfer of values entered for variables in the initial order form to their corresponding variables in the catalog items that have been ordered.\n3. Assume that the variables variable_name1, varaible_name2 are already present on the order guide form, hence hiding these variables on the catalog form when the catalog form is opened through an order guide using the function isOrderGuide().\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Hide attachment icon/Hide Attachment icon.js",
    "content": "//Hide attachment icon on catalog item\nfunction onLoad() {\n    var document = document || top.document;\n    (jQuery || top.jQuery)(\"#sc_attachment_button, #catItemTop > div > div.wrapper-md.row.no-margin.ng-scope > label\").hide();\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Hide attachment icon/README.md",
    "content": "# Hide Attachment Icon on Catalog Items\n\n## Use Case / Requirement\nHide the attachment icon on a specific catalog item when the end user should not submit supporting documents. This can reduce confusion and prevent oversized uploads.\n\n## Solution\nUse an onLoad catalog client script to target the attachment button rendered on the Service Portal form and hide it with jQuery. The snippet works for both classic and Service Portal experiences.\n\n## Implementation\n1. Create a new catalog client script with Type set to onLoad.\n2. Copy the contents of Hide Attachment icon.js into the script field.\n3. Adjust the selector if your catalog item uses a custom portal or markup.\n\n## Notes\n- Requires jQuery, which is available on standard Service Portal forms.\n- The DOM can change between releases; retest after theme or layout updates.\n- Remove the script if the catalog item later requires attachments.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Incident Sentiment Detector (Using Simple Word Matching, No AI)/SentimentAnalyzer.js",
    "content": "var SentimentAnalyzer = Class.create();\nSentimentAnalyzer.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getSentiment: function() {\n        var text = (this.getParameter('sysparm_text') || '').toLowerCase();\n        var positive = ['thanks', 'great', 'resolved', 'appreciate'];\n        var negative = ['issue', 'error', 'not working', 'fail', 'problem'];\n\n        var score = 0;\n        positive.forEach(function(word) { if (text.includes(word)) score++; });\n        negative.forEach(function(word) { if (text.includes(word)) score--; });\n\n        if (score > 0) return 'Positive';\n        if (score < 0) return 'Negative';\n        return 'Neutral';\n    }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Incident Sentiment Detector (Using Simple Word Matching, No AI)/onChangeClientscript.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || !newValue) return;\n\n    var ga = new GlideAjax('SentimentAnalyzer');\n    ga.addParam('sysparm_name', 'getSentiment');\n    ga.addParam('sysparm_text', newValue);\n    ga.getXMLAnswer(function(sentiment) {\n        g_form.addInfoMessage('Sentiment: ' + sentiment);\n        g_form.setValue('u_sentiment', sentiment);\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Incident Sentiment Detector (Using Simple Word Matching, No AI)/readme.md",
    "content": "Incident Sentiment Detector (No AI, Pure JavaScript)\n\nA lightweight ServiceNow utility that detects sentiment (Positive / Negative / Neutral) of an Incident’s short description or comments using simple keyword matching — no AI APIs or external libraries required.\n\nUseful for support teams to auto-tag sentiment and analyze user frustration or satisfaction trends without expensive integrations.\n\n🚀 Features\n\n✅ Detects sentiment directly inside ServiceNow ✅ Works without external APIs or ML models ✅ Instant classification on form update ✅ Adds detected sentiment to a custom field (u_sentiment) ✅ Simple to extend — just add more positive/negative keywords\n\n🧩 Architecture Overview\n\nThe solution consists of two main scripts:\n\nComponent Type Purpose SentimentAnalyzer Script Include Processes text and returns sentiment Client Script (onChange) Client Script Calls SentimentAnalyzer via GlideAjax on short description change 🧱 Setup Instructions 1️⃣ Create Custom Field\n\nCreate a new field on the Incident table:\n\nName: u_sentiment\n\nType: Choice\n\nChoices: Positive, Neutral, Negative\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Email Validation with Mutation Observer/EmailValidationOnCatalogUI.js",
    "content": "/**\n * This is a client script that validates the email address in the MRVS field.\n * It uses MutationObserver to observe the changes in the MRVS field.\n * UseCase: Validate the email address in the catalog whenever user is going to add another email, \n * and show an error message if a duplicate email address is found. \n */\nfunction onLoad() {\n    var document = document || top.document;\n\n    var duplicateErrorMessageStatus = false; //adding this as corner case as observer will be called multiple times\n\n    setTimeout(function () {\n        var tbody = document.querySelector(\"#user_details > div > tbody\");\n\n        if (tbody) {\n            var observer = new MutationObserver(function (m, o) {\n\n                var users = g_form.getValue('user_details');\n\n                var hasDuplicateEmails = validateUserDetails();\n\n                if (!users || hasDuplicateEmails) {\n                    if (hasDuplicateEmails && !duplicateErrorMessageStatus) {\n                        g_form.addErrorMessage('Duplicate email address found');\n                        duplicateErrorMessageStatus = true;\n                    }\n                    //disable the submit button\n                } else {\n                    duplicateErrorMessageStatus = false;\n                    g_form.clearMessages();\n                    //enable the submit button\n                }\n\n            });\n            observer.observe(tbody, {\n                attributes: true,\n                childList: true,\n                subtree: true\n            });\n        }\n    }, 3000);\n\n}\n\n// MRVS contains the user details in the form of JSON\nfunction validateUserDetails() {\n    var userDetailsMRVS = g_form.getValue('user_details');\n    if (userDetailsMRVS) {\n        var multiRowData = JSON.parse(userDetailsMRVS);\n        var emailSet = new Set();\n\n        for (var i = 0; i < multiRowData.length; i++) {\n            var row = multiRowData[i];\n\n            var email = row.email.trim().toLowerCase();\n\n            if (emailSet.has(email)) {\n\n                return true;\n            }\n            emailSet.add(email);\n        }\n    }\n    return false;\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Email Validation with Mutation Observer/README.md",
    "content": "# Email Validation on Catalog UI\n\nThis file contains a client script that validates email addresses in the MRVS (Multi-Row Variable Set) field using a MutationObserver. The script ensures that duplicate email addresses are not allowed in the catalog.\n\n## File Structure\n\n- `/code-snippets/Catalog Client Script/MRVS Email Validation with Mutation Observer/EmailValidationOnCatalogUI.js`\n\n## Description\n\nThe script observes changes in the MRVS field and validates the email addresses whenever a user attempts to add another email. If a duplicate email address is found, an error message is displayed, and the submit button is disabled.\n\n## Usage\n\n1. **onLoad Function**: This function initializes the MutationObserver to watch for changes in the MRVS field.\n2. **validateUserDetails Function**: This function checks for duplicate email addresses in the MRVS field.\n\n## How It Works\n\n1. The `onLoad` function sets up a MutationObserver on the MRVS field.\n2. When changes are detected, the `validateUserDetails` function is called to check for duplicate email addresses.\n3. If duplicates are found, an error message is displayed, and the submit button is disabled.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Interact With Parent Form/Portal onLoad.js",
    "content": "//Using this Catalog Client Script that Applies to the Catalog Item will enable g_form methods like setValue and clearValue which affect Catalog Item variables from a Catalog Client Script that applies to the multi-row variable set\nfunction onLoad() {\n\tif (this) {//we only need to do this for Service Portal\n\t\t//We need to make the g_form object for the parent item available from the MRVS window\n\t\tthis.cat_g_form = g_form;\n\t}\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Interact With Parent Form/README.md",
    "content": "Use this code snippet to interact with the parent form (i.e the main Cataloge Item) from within a Catalog Client Script that applies to a multi-row variable set.\n\nThe g_service_catalog object allows for accessing the \"parent\" GlideForm (g_form) object for getValue only (for now?)\nTo affect a variable in the main catalogue item using setValue, clearValue, etc. parent.g_form... is available in the native UI, and this.cat_g_form... can be used for Service Portal, etc if an additional Catalog Client Script is used, this one onLoad that Applies to the Catalog Item (not the MRVS).\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Interact With Parent Form/Write to Parent Form.js",
    "content": "//These methods can be used onLoad, onChange, or onSubmit in a script that Applies to the MRVS \n\n//Retrieve a variable value from the parent form - works in native UI as well as Service Portal, etc.\ng_service_catalog.parent.getValue('variable_name');\n\n//With this approach, you can set a variable value on the parent form - use similar code for other g_form methods like clearValue\n//Service Portal method requires an additional Catalog Client Script onLoad that Applies to the Catalog Item\nif (this) { //Service Portal method\n\t\tthis.cat_g_form.clearValue('variable_name');\n\t} else { //native UI method\n\t\tparent.g_form.clearValue('variable_name');\n\t}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Loop Rows/README.md",
    "content": "Use this to loop through a Multi Row Variable Set and create an array of objects with the variables in it.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Loop Rows/loopRows.js",
    "content": "var mrvsObj = [];\nvar multiRow = variable.mrvsName; //replace with the actual name of the mrvs\nif (multiRow.getRowCount()) { //if there are any entries on the MRVS loop through it\n\tvar eachRow = multiRow.getRowCount();\n\tfor (var i = 0; i < eachRow; i++) {\n\t\tvar row = multiRow.getRow(i);\n\t\tvar rowVars = {};\n\t\trowVars.var1 = row.var1; //replace with the variables in the mrvs\n\t\trowVars.var2 = row.var2;\n\t\trowVars.var3 = row.var3;\n\t\t// ... add rows as appropriate for your mrvs\n\t\tmrvsObj.push(rowVars); // creates an array of objects of the above fields from the MRVS\n\t}\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Reference Qualifier from Catalog Item Variable/Catalog Client Script.js",
    "content": "function onLoad() {\n\tvar mgr = g_service_catalog.parent.getValue('v_manager'); //if using this script onLoad of the MRVS\n\t//var mgr = newValue; // if using this script onChange of the Catalog Item variable\n\tvar filterString = 'active=true^manager=' + mgr; //Reference qualifier following the 'javascript:'\n\t//alternate method for Service Portal only\n\t// if (window == null){ //Service Portal method\n\t// \tvar setfilter = g_list.get('v_employee');\n\t// \tsetfilter.setQuery(filterString);\n\t// } else { //native UI method\n\t\tvar ga = new GlideAjax('refQualUtils'); //Client callable Script Include Name\n\t\tga.addParam('sysparm_name', 'setSysProp'); //Function in Script Include\n\t\tga.addParam('sysparm_sys_prop_name', 'sr.mrvs.ref_qual.emp'); //System Property Name used in MRVS variable Reference qualifier \n\t\tga.addParam('sysparm_sys_prop_value', filterString);\n\t\tga.getXML(getResponse);\n\t\t\t\n\t\tfunction getResponse(response) { //to avoid Service Portal 'There is a JavaScript error in your browser console'\n\t\t\tvar answer = response.responseXML.documentElement.getAttribute(\"answer\"); \n\t\t}\n\t//}\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Reference Qualifier from Catalog Item Variable/README.md",
    "content": "On a reference variable in a multi-row variable set, sometimes you want the reference qualifier to include the value of a variable that is part of the Catalog Item, not within the MRVS.\n\nIn this simplified example, I have a Manager (v_manager) reference variable (sys_user table) that belongs to the Catalog Item.  In the MRVS, I have an Employee (v_employee) reference variable (sys_user table).  I only want to be able to select user records that are active, and the Manager is the user I selected on the Catalog Item variable.\n\n1) Set the advanced reference qualifier on the MRVS variable to\n   javascript: gs.getProperty(\"sr.mrvs.ref_qual.emp\");\n   using a System Property Name of your choice\n\n2) Use the included onLoad Catalog Client Script that applies to the Variable set, or you can also use this onChange of the Catalog Item variable in a script that applies to the Catalog Item.\n\n3) Add the included Client callable Script Include for the Catalog Client Script to call via GlideAjax.  This Script Include uses parameters for the System Property Name and Value, so it can be re-used in every instance of this solution.\n\nThis solution works in both the Native UI and Service Portal.  The script contains an alternate Service Portal only approach in the commented if block that can be used in conjunction with the native UI approach in the else block.  This alternate Service Portal solution was developed in collaboration with Chris Perry.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS Reference Qualifier from Catalog Item Variable/Script Include.js",
    "content": "var refQualUtils = Class.create();\nrefQualUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n   \n   \tsetSysProp: function(){\n\t\tvar propertyName = this.getParameter('sysparm_sys_prop_name');\n\t\tvar propertyValue =  this.getParameter('sysparm_sys_prop_value');\n\t\tvar property = gs.getProperty(propertyName);\n\t\tgs.setProperty(propertyName, propertyValue);\n\t\treturn;\n\t},\n\t\t\n    type: 'refQualUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS dependent ref qual 1st row/AccountUtils.js",
    "content": "var AccountUtils = Class.create();\nAccountUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    //Populate the department name from the account in the session data for the reference qualifier to use:\n    \n    setSessionData: function() {\n        var acct = this.getParameter('sysparm_account');\n\t\t    var dept = '';\n\t\t    var acctGR = new GlideRecord('customer_account'); //reference table for Account variable\n\t\t    if (acctGR.get(acct)) {\n\t\t\t      dept = '^dept_name=' + acctGR.dept_name; //department field name on account table\n\t\t    }\n\t\t\n\t\t    var session = gs.getSession().putClientData('selected_dept', dept);\n        return;\n    },\n\n    type: 'AccountUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS dependent ref qual 1st row/README.md",
    "content": "This Catalog Client Script and Script Include are used with a reference qualifier similar to\njavascript: 'disable=false' + session.getClientData('selected_dept');\n\nThe scenario is a MRVS with a reference variable to the customer account table.  When an (active) account is selected in the first row, subsequent rows should only be able to select active accounts in the same department as the first account.\n\nThe Catalog Client Script will pass the first selected account (if there is one) to a Script Include each time a MRVS row is added or edited.  The Script Include will pass the department name to the reference qualifier.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/MRVS dependent ref qual 1st row/onLoad.js",
    "content": "function onLoad() {\n    //applies to MRVS, not Catalog Item. This will pass the first selected account (if there is one) to a Script Include each time a MRVS row is added or edited\n    var mrvs = g_service_catalog.parent.getValue('my_mrvs'); //MRVS internal name\n\tvar acct = '';\n\tif (mrvs.length > 2) { //MRVS is not empty\n\t\tvar obj = JSON.parse(mrvs);\n       \tacct = obj[0].account_mrvs;\n\t}\n\tvar ga = new GlideAjax('AccountUtils');\n    ga.addParam('sysparm_name', 'setSessionData');\n    ga.addParam('sysparm_account', acct);\n    ga.getXMLAnswer(getResponse);\n}\n\nfunction getResponse(response) { \n    //do nothing \n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Make OOB Attachment Mandatory/README.md",
    "content": "# Make out of the box attahcment mandatory onChange of a field in Catalog Item\n\nThis scripts makes the out of the box attachments to mandatory on the Service Portal for any Catalog Item.\n\nUsage:\n\nStep #1 - Create the UI Script \n* [Click here for script](UI Scripts/Make OOB Attachment Mandatory/setAttachmentMandatory.js)\n\nStep #2 - Include the UI Script in the Portal Theme under JS Includes\n\nStep #3 - Create a Catalog Client Script for the respective Catalog Item where you want to make the OOB attachment mandatory for certain criteria\n\n* [Click here for script](Catalog Client Script/Make OOB Attachment Mandatory/onChange.js)"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Make OOB Attachment Mandatory/onChange.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == 'No') {\n\t\tsetAttachmentMandatory(false);\n        return;\n    }\n\n    if (newValue == 'Yes') setAttachmentMandatory(true);\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Mandatory Attachments with 'n' numbers/README.md",
    "content": "# Enable Mandatory Attachments Count wtih 'n' numbers on Service Catalog item\n**Problem:** Check if exact 'n' number of attachments are added or not when user submit a request through service catalog from Portal/Platform UI.\n\n**For example:** We need to ensure there are exact 3 attachments added before submission\n\n**Note:** Ensure Isolate Script field is set to False for this Catalog Client Script to ensure DOM manipulation works\n\n* [Click here for script](onSubmitClientScript.js)\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Mandatory Attachments with 'n' numbers/onSubmitClientScript.js",
    "content": "function onSubmit() {\n\t//Type appropriate comment here, and begin script below\n\nvar count = 3; //Pass the number to ensure given number of attachments are added\nvar alertMsg=\"You must add \"+count+\" attachments before submitting this request.\";\n\tif(window == null){\n\t\t// Service portal validation, Make sure Isolate Script is set to False\n\t\tif(this.document.getElementsByClassName('get-attachment').length != count) {\n\t\t\tspModal.alert(alertMsg);\n\t\t\treturn false;\n\t\t}\n\t}\n\telse{\n\t\t// Platform View\n         var length = $j(\"li.attachment_list_items\").find(\"span\").length;\n\t\tif(length != count){\n\t\t\talertWindow(alertMsg);\n\t\t\treturn false;\n\t\t}\n\t}\n}\nfunction alertWindow(message) {\n    var modal = new GlideModal(\"glide_warn\");\n    modal.setTitle(\"Attachment issue\");\n    modal.setPreference(\"title\", message);\n    modal.render();\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Multi-User Collaboration/Readme.md",
    "content": "This project introduces a collaboration feature for Service Catalog requests, allowing multiple users to contribute to a single request. It’s ideal for scenarios like team onboarding, shared resource provisioning, or cross-functional workflows.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Multi-User Collaboration/Script include.JS",
    "content": "var CollaboratorHandler = Class.create();\nCollaboratorHandler.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  addCollaborators: function() {\n    var requestId = this.getParameter('sysparm_request_id');\n    var users = this.getParameter('sysparm_users').split(',');\n\n    users.forEach(function(userId) {\n      var gr = new GlideRecord('x_your_scope_collaborators');\n      gr.initialize();\n      gr.request = requestId;\n      gr.collaborator = userId;\n      gr.status = 'Pending';\n      gr.insert();\n    });\n\n    return 'Collaborators added successfully';\n  }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Multi-User Collaboration/client script.JS",
    "content": "function onSubmit() {\n  var collaborators = g_form.getValue('collaborators'); // Multi-user reference field\n  if (collaborators) {\n    var ga = new GlideAjax('CollaboratorHandler');\n    ga.addParam('sysparm_name', 'addCollaborators');\n    ga.addParam('sysparm_request_id', g_form.getUniqueValue());\n    ga.addParam('sysparm_users', collaborators);\n    ga.getXMLAnswer(function(response) {\n      alert('Collaborators added: ' + response);\n    });\n  }\n  return true;\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Multi-User Collaboration/widget client controller.JS",
    "content": "function($scope, $http) {\n  $scope.approve = function(sysId) {\n    $http.post('/api/x_your_scope/collab_action', {\n      action: 'approve',\n      sys_id: sysId\n    }).then(function(response) {\n      $scope.server.update();\n    });\n  };\n\n  $scope.reject = function(sysId) {\n    $http.post('/api/x_your_scope/collab_action', {\n      action: 'reject',\n      sys_id: sysId\n    }).then(function(response) {\n      $scope.server.update();\n    });\n  };\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Multi-User Collaboration/widget server script.JS",
    "content": "(function() {\n  var collabs = [];\n  var gr = new GlideRecord('x_your_scope_collaborators');\n  gr.addQuery('request', $sp.getParameter('request_id'));\n  gr.query();\n  while (gr.next()) {\n    collabs.push({\n      sys_id: gr.getUniqueValue(),\n      name: gr.collaborator.name.toString(),\n      status: gr.status.toString(),\n      comments: gr.comments.toString()\n    });\n  }\n  data.collaborators = collabs;\n})();\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Normalise and Reset a MRVS based on Variable Changes/README.md",
    "content": "# MRVS - Normalise and Reset Rows on Change\n\n## What this solves\nWhen a controlling variable changes (for example, Environment), existing MRVS rows may no longer be valid. This client script:\n- Clears or normalises specific MRVS columns\n- Deduplicates rows\n- Optionally sorts rows for a cleaner UX\n- Works entirely client-side using MRVS JSON\n\n## Where to use\nCatalog Item → OnChange client script on your controlling variable.\n\n## How it works\n- Reads the MRVS value as JSON via `g_form.getValue('my_mrvs')`\n- Applies transforms (clear columns, unique by key, sort)\n- Writes back the JSON with `g_form.setValue('my_mrvs', JSON.stringify(rows))`\n\n## Setup\n1. Replace `CONTROLLING_VARIABLE` with your variable name.\n2. Replace `MY_MRVS` with your MRVS variable name.\n3. Adjust `COLUMNS_TO_CLEAR`, `UNIQUE_KEY`, and `SORT_BY` as needed.\n\n## Notes\n- To clear the MRVS entirely, set `rows = []` before `setValue`.\n- Works with Catalog Client Scripts; no server call required.\n\n## References\n- GlideForm API (client): `getValue`, `setValue`, `clearValue`  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideForm/concept/c_GlideFormAPI.html\n- Working with MRVS values on the client (community examples)  \n  https://www.servicenow.com/community/developer-articles/accessing-multi-row-variable-set-value-outside-the-multi-row/ta-p/2308876\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Normalise and Reset a MRVS based on Variable Changes/mrvs_normalise_onchange.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n  if (isLoading) return;\n\n  var MRVS_NAME = 'MY_MRVS';                 // your MRVS variable name\n  var COLUMNS_TO_CLEAR = ['env', 'owner'];   // MRVS column names to clear\n  var UNIQUE_KEY = 'hostname';               // MRVS column that should be unique\n  var SORT_BY = 'hostname';                  // MRVS column to sort by\n\n  try {\n    var raw = g_form.getValue(MRVS_NAME);\n    var rows = raw ? JSON.parse(raw) : [];\n    if (!Array.isArray(rows)) rows = [];\n\n    // Clear specified columns\n    rows.forEach(function(row) {\n      COLUMNS_TO_CLEAR.forEach(function(col) { if (row.hasOwnProperty(col)) row[col] = ''; });\n    });\n\n    // Deduplicate by UNIQUE_KEY\n    if (UNIQUE_KEY) {\n      var seen = {};\n      rows = rows.filter(function(row) {\n        var key = String(row[UNIQUE_KEY] || '').toLowerCase();\n        if (!key || seen[key]) return false;\n        seen[key] = true;\n        return true;\n      });\n    }\n\n    // Sort (case-insensitive)\n    if (SORT_BY) {\n      rows.sort(function(a, b) {\n        var A = String(a[SORT_BY] || '').toLowerCase();\n        var B = String(b[SORT_BY] || '').toLowerCase();\n        if (A < B) return -1;\n        if (A > B) return 1;\n        return 0;\n      });\n    }\n\n    g_form.setValue(MRVS_NAME, JSON.stringify(rows));\n  } catch (e) {\n    console.error('MRVS normalise failed', e);\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Normalise and Reset a MRVS based on Variable Changes/variant_reset_specific_columns.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n  if (isLoading) return;\n\n  var MRVS_NAME = 'MY_MRVS';\n  var COLUMNS_TO_CLEAR = ['env', 'region'];\n\n  var rows = [];\n  try { rows = JSON.parse(g_form.getValue(MRVS_NAME) || '[]'); } catch (e) {}\n  if (!Array.isArray(rows)) rows = [];\n\n  rows.forEach(function(row) {\n    COLUMNS_TO_CLEAR.forEach(function(col) { if (row.hasOwnProperty(col)) row[col] = ''; });\n  });\n\n  g_form.setValue(MRVS_NAME, JSON.stringify(rows));\n}\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Onsubmit validation/Readme.md",
    "content": "This project adds pre-validation for hardware availability in ServiceNow Catalog Items. Before submitting a request, the system checks if the requested hardware is available in inventory and blocks submission if stock is insufficient. we can easy to extend  other validations (budget, licenses, etc.).Improves user experience by validating before approval.Prevents unnecessary approvals and fulfillment.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Onsubmit validation/on submit scriptinclude.JS",
    "content": "var HardwareValidationUtils = Class.create();\nHardwareValidationUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    validateHardware: function() {\n        var hardware = this.getParameter('sysparm_hardware');\n        var qty = parseInt(this.getParameter('sysparm_quantity'), 10);\n\n        if (!hardware || isNaN(qty)) {\n            return 'Invalid input!';\n        }\n\n        var gr = new GlideRecord('u_hardware_inventory');\n        if (gr.get(hardware)) {\n            var availableQty = parseInt(gr.getValue('available_quantity'), 10);\n            if (availableQty >= qty) {\n                return 'OK';\n            } else {\n                return 'Not enough stock available!';\n            }\n        }\n        return 'Hardware not found!';\n    },\n\n    type: 'HardwareValidationUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Onsubmit validation/submit validation client script.js",
    "content": "function onSubmit() {\n    \n\n    var hardware = g_form.getValue('hardware_name');\n    var qty = g_form.getValue('quantity');\n\n   \n    var ga = new GlideAjax('HardwareValidationUtils');\n    ga.addParam('sysparm_name', 'validateHardware');\n    ga.addParam('sysparm_hardware', hardware);\n    ga.addParam('sysparm_quantity', qty);\n\n    \n    ga.getXMLAnswer(function(response) {\n        \n\n        if (response !== 'OK') {\n            alert(response);\n            \n            g_form.addErrorMessage(response); // Optional inline error\n            g_form.setSubmit(false); // Prevent submission in Service Portal\n        } else {\n          \n            g_form.setSubmit(true); // Allow submission\n        }\n    });\n\n    return false; \n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Open modal widget in an Onsubmit/README.md",
    "content": "Code snippet to stop submission of a form in an Onsubmit Client Script, use an asynchronous call, and open a Widget in Modal view. In the script provided, there are two buttons in the modal. The first continues with the submission to create a new record and the second one cancels it. We can use a Script Include to get some value that we want and based on that open the modal or continue with submission.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Open modal widget in an Onsubmit/openmodal.js",
    "content": "    function onSubmit() {\n\n      \n        if (g_scratchpad.isFormValid)\n            return true;\n\n      //We can do some check using a Client Callable script include\n        var getAnswer = new GlideAjax('example');\n        getAnswer.addParam('sysparm_name', 'checkFor');\n        getAnswer.addParam('sysparm_input1', g_user.userID);\n        getAnswer.addParam('sysparm_input2', g_form.getUniqueValue());\n\n        getAnswer.getXML(parsing);\n\n        function parsing(response) {\n\n            var answer = response.responseXML.documentElement.getAttribute('answer');\n            if (answer) {\n                var data = JSON.parse(answer);\n          \n                if (data == true) {\n\n                    spModal.open({\n                        title: \"Test title\",\n                        widget: \"mywidget\",\n                        buttons: [{\n                                label: 'Close',\n                                value: 'close'\n                            },\n                            {\n                                label: 'Create New Record',\n                                value: 'create'\n                            }\n                        ],\n                        size: 'md'\n                    }).then(function(answer) {\n                      //if button pressed is \"create\" then submit the form\n                        if (answer.value == 'create') {\n                            g_scratchpad.isFormValid = true;\n                            g_form.submit();\n                        }\n                    });\n                } else {\n                    g_scratchpad.isFormValid = true;\n                    g_form.submit();\n                }\n\n            }\n\n        }\n      //Dont submit the form\n        return false;\n    }\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/PAN Validation/PAN Validation.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n    var panNumber = g_form.getValue(\"pan_number\"); //Get the PAN card information\n    var panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/; // Regex for the PAN Card\n\n    if (panRegex.test(panNumber)) {\n        g_form.showFieldMsg(\"pan_number\", \"Valid PAN card number.\", true); //Valid PAN card enterd populates this message \n    } else {\n        g_form.showErrorBox(\"pan_number\", \"InValid PAN card number.\", true); //In Valid PAN card details enterd populate this message \n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/PAN Validation/README.md",
    "content": "PAN is a ten-digit unique alphanumeric number issued by the Income Tax Department.\nIndian PAN (Permanent Account Number) card based on its standardized format. \nA PAN number is a unique 10-character alphanumeric identifier issued by the Indian government, and it follows a specific structure:\nFirst 5 characters: Uppercase English letters (A-Z).\nNext 4 characters: Numeric digits (0-9). \nLast character: A single uppercase English letter (A-Z).\n ![image](https://github.com/user-attachments/assets/37a6490b-912e-499c-9c41-c0cdf79491a5)\n\nWhen we provide the information in the mentioned format it validates as a valid PAN Card\nEg: ABCDE1234F\nResult  \n![image](https://github.com/user-attachments/assets/779757b9-2d46-439b-bc8c-a468a5c0264a)\n\nMentioned formate was not used it will give an error message to the field\nEg:abcde1234f  \n![image](https://github.com/user-attachments/assets/1dd214b8-aaf5-405d-aefb-8e46b786be3c)\n\nEg: 1234ABCDEF  \n![image](https://github.com/user-attachments/assets/08f538b7-d1ac-4d26-8579-8342ca757c8c)\n\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Passport Validation/README.md",
    "content": "This OnChange Catalog Client Script is for validating passport number, date of issue, and date of expiry. \n\nIt follows the specified rules(As per indian passport):- \n- The passport number should be 8 characters long, with the first character as an uppercase letter, the second and third characters as numbers (1-9 for the first digit, 0-9 for the second digit).\n- The date of expiry should be calculated based on the date of issue:\n- 1. If the user is an adult (18 years or older), the expiry date should be exactly 5 years from the date of issue.\n  2. If the user is under 18, the expiry date should be exactly 10 years from the date of issue.\n        \nPassport Number Validation:\nThe passportPattern uses a regular expression:\n^[A-Z] – The first character must be an uppercase letter.\n[1-9][0-9] – The second and third characters are numbers; the first is between 1-9, and the second between 0-9.\n[A-Z0-9]{5}$ – The last five characters are alphanumeric.\nIf the passport number does not match this pattern, the script displays an error message and clears the field.\n\nDate of Expiry Calculation:\n- After the date of issue and age are provided, the script calculates the expiry date by adding 5 years for adults (18 or older) or 10 years for minors (under 18).\n- The calculated expiry date is automatically set in the date_of_expiry field in the yyyy-MM-dd format.\n- Prompts are displayed if necessary fields like date_of_issue or age are missing before attempting the expiry date calculation.\n\nThis Client Script will ensure that the entered passport information and expiry date meet the requirements, providing a seamless and guided experience for the user.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Passport Validation/passportvalidity.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading) return;\n  \n    var passportNumber = g_form.getValue('passport_number');\n    var dateOfIssue = g_form.getValue('date_of_issue');\n    var age = parseInt(g_form.getValue('age'), 10);\n    var dateOfExpiry = g_form.getValue('date_of_expiry');\n  \n    // Passport Number Validation\n    var passportPattern = /^[A-Z][1-9][0-9][A-Z0-9]{5}$/;\n    if (passportNumber && !passportPattern.test(passportNumber)) {\n        g_form.showFieldMsg('passport_number', \"The entered number is invalid passport number format. It must be 8 characters long, start with an uppercase letter, followed by a number between 1-9, then 0-9, and the rest alphanumeric.\", \"error\");\n        g_form.clearValue('passport_number');\n    } else {\n        g_form.hideFieldMsg('passport_number');\n    }\n  \n    // Date of Expiry Calculation based on date of issue\n    if (dateOfIssue && age) {\n        var issueDate = new GlideDate();\n        issueDate.setValue(dateOfIssue);\n        var expiryDate = new GlideDate();\n        expiryDate.setValue(issueDate);\n      \n        if (age >= 18) {\n            expiryDate.addYears(5); // Adult - add 5 years\n        } else {\n            expiryDate.addYears(10); // Under 18 - add 10 years\n        }\n      \n        g_form.setValue('date_of_expiry', expiryDate.getByFormat('yyyy-MM-dd')); // Set expiry date in correct format\n        g_form.hideFieldMsg('date_of_expiry');\n    } else if (!dateOfIssue) {\n        g_form.showFieldMsg('date_of_issue', \"Please enter the Date of Issue first.\", \"info\");\n    } else if (!age) {\n        g_form.showFieldMsg('age', \"Please enter your age first.\", \"info\");\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Password Validation Script/README.md",
    "content": "Description of the Combined Password Validation Script\nPurpose\nThe script validates the password entered by the user in a ServiceNow catalog item form. It ensures that the password meets strong security criteria and does not include the user's first or last name, enhancing overall security.\n\nValidation Criteria\nStrong Password Requirements:\n\nAt least 8 characters long.\nContains at least one uppercase letter.\nContains at least one lowercase letter.\nContains at least one digit.\nContains at least one special character (e.g., @$!%*?&).\nFirst and Last Name Restrictions:\n\nThe password cannot contain the user's first name or last name.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Password Validation Script/Script.js",
    "content": "function onSubmit() {\n    // Get the password value from the field\n    var password = g_form.getValue('password'); // Change 'password' to your field name\n    // Get the first and last name values from the fields\n    var firstName = g_form.getValue('first_name'); // Change 'first_name' to your field name\n    var lastName = g_form.getValue('last_name'); // Change 'last_name' to your field name\n    // Define the regex pattern for a strong password\n    var passwordPattern = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$/;\n    \n    // Check if the password contains the first or last name\n    if (password.includes(firstName) || password.includes(lastName)) {\n        // Display an error message if validation fails\n        g_form.showFieldMsg('password', 'Password cannot contain your first or last name.', 'error');\n        return false; // Prevent form submission\n    }\n  // Validate the password against the pattern\n    if (!passwordPattern.test(password)) {\n        // Display an error message if validation fails\n        g_form.showFieldMsg('password', 'Password must be at least 8 characters long, contain at least one uppercase letter, one lowercase letter, one digit, and one special character.', 'error');\n        return false; // Prevent form submission\n    }\n\n\n    return true; // Allow form submission if all validations pass\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Percentage Symbol/readme.md",
    "content": "Sets the field value to the formatted percentage string (e.g., 45 becomes 45.00%).\n\nRetrieves the current value of the field.\nRemoves any existing % symbol to avoid duplication.\nValidates the input to ensure it's a number.\nConverts the value to a float.\nFormats it to two decimal places and appends a % symbol. \n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Percentage Symbol/script.js",
    "content": "/*Sets the field value to the formatted percentage string (e.g., 45 becomes 45.00%).\n\nRetrieves the current value of the field.\nRemoves any existing % symbol to avoid duplication.\nValidates the input to ensure it's a number.\nConverts the value to a float.\nFormats it to two decimal places and appends a % symbol. */\n\n\nfunction onChange(control, oldValue, newValue, isLoading) {\n   if (isLoading || newValue == '') {\n      return;\n   }\n\n   function formatPercent(value) {\n    if (value === null || value === undefined || value === '' || isNaN(value)) {\n        return \"\";\n    }\n    var num = parseFloat(value);\n    if (isNaN(num)) \n\treturn \"\";\n    return num.toFixed(2) + \"%\";\n}\nvar des_amount = g_form.getValue(\"<variable_name>\").replace(\"%\",\"\");\n\ng_form.setValue(\"<variable_name>\", formatPercent(des_amount));\n   \n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/PopulateDropdown/README.md",
    "content": "## Load / Populate the options for the second dropdown field (select box) based on what user chooses in the first dropdown\n![alt text](demo_catalog.png)\n\n### Use case\n\nYou need to dynamically populate the options for a dropdown (select box) field following another field.\n\nFor example, you need to create a catalog that has 2 fields: country and city, when the user chooses a country, the city field will only show the cities of that country.\n\nThis will especially come in handy when you have hundreds of options to populate, and easier to maintain than using catalog UI policies 👍\n\n### Set up\n\n- On the catalog, create 2 select box fields, for example: `city` and `country`\n- Add the question choices for the 1st dropdown\n- In the 2nd dropdwon, add any value for the question choices to bypass the mandatory\n- Create new client script, on change, for the FIRST dropdown. Only applies on Catalog Item view. It is recommended to set the variables readonly on the RITM and SCTASK view.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/PopulateDropdown/script.js",
    "content": "// Please check readme for the set up\nvar data = {\n  Japan: [\"Tokyo\", \"Osaka\"],\n  USA: [\"New York\", \"Chicago\", \"San Diego\"],\n};\n\nfunction onChange(control, oldValue, newValue, isLoading) {\n  var secondDropdown = \"city\";\n\n  g_form.clearOptions(secondDropdown); // Remove all the options of the 2nd dropdown\n\n  // When user switch to 'None' in country or when the form is loaded\n  if (newValue == \"\" || isLoading) {\n    return;\n  }\n\n  var cities = data[newValue];\n\n  if (cities && cities.length) {\n    cities.forEach(function (value) {\n      g_form.addOption(secondDropdown, value, value);\n    });\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Prevent duplicate records on MRVS/README.md",
    "content": "# Prevent duplicate records to be selected in Multi row variable set\n\nWe have many ways to do this but this is bit unique compare to what you find in community, you can do it in one single script within the Variable set. \nOf course ServiceNow introduce a new feature in Quebec and have Unique checkbox field introuduced but to have a custom info message you need to go with the custom script.\n\n  [ServiceNow Docs to disallow duplicate values](https://docs.servicenow.com/bundle/quebec-servicenow-platform/page/product/service-catalog-management/task/t_CreateAVariableForACatalogItem.html)\n  \n  [Click here for the script](script.js)\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Prevent duplicate records on MRVS/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n\tif (isLoading || newValue == '') {\n\t\treturn;\n\t}\n\t\n\tvar MRVS_FIELD = \"field_name_here\"; //Multi row variable set name\n\t\n\tvar MRVS = (g_service_catalog.parent.getValue(MRVS_FIELD).length != 0) ? JSON.parse(g_service_catalog.parent.getValue(MRVS_FIELD)) : [];\n\t\n\t//If the MRVS is empty - exit\n\tif(MRVS.length == 0)return;\n\t\n\tvar valueExists = MRVS.some(function(obj){\n\t\treturn obj.Variable_Name == newValue; // Reference variable name which needs to be unique\n\t});\n\t\n\tif(valueExists){\n\t\tg_form.showFieldMsg(MRVS_FIELD , \"Field must be unique\");\n\t}\n\n\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Real time count of letters/Count letters.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading) {\n        return;\n    }\n\n    var maxChars = 100;//count of charaters \n    var currentLength = newValue.length;\n\n    // Clear previous messages\n    g_form.clearMessages();\n\n    // Show info message\n    g_form.addInfoMessage('Character count: ' + currentLength + ' / ' + maxChars);\n\n    if (currentLength > maxChars) {\n        // Show error message\n        g_form.addErrorMessage('Character limit exceeded! Please shorten your text.');\n        g_form.showFieldMsg('short_description', 'Too many characters!', 'error');\n\n        // Make field mandatory to block submission\n        g_form.setMandatory('short_description', true);\n    } else {\n        // Remove mandatory if valid\n        g_form.setMandatory('short_description', false);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Real time count of letters/readme.md",
    "content": "This onChange Catalog Client Script displays the current character count for a text field and enforces a maximum limit by showing error messages and making the field mandatory to prevent form submission when exceeded.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Remove reference icon from portal/README.md",
    "content": "# Remove reference icon from portal using Catalog Client script of a Catalog Item\n\nThis catalog client script is design to remove the reference icon from the Portal for any reference field record.\nThis needs to be configured for each Catalog Item where the reference field is being used.\n\n* [Click here for script](remove-reference-icon-from-portal-onLoad.js)"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Remove reference icon from portal/remove-reference-icon-from-portal-onLoad.js",
    "content": "function onLoad() {\n    setTimeout(function() {\n        var referenceElement = top.document.getElementsByClassName('btn btn-default bg-white lookup')[0];\n\n        if (referenceElement != undefined || referenceElement != null)\n            referenceElement.remove();\n        \n    }, 500);\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Restrict Number of rows in Multi Row Variable/README.md",
    "content": "## restrict_multi_row.js\nUse this to restrict multi row variable set rows to 1. this value can be changed to any number of rows as requirement.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Restrict Number of rows in Multi Row Variable/restrict_multi_row.js",
    "content": "function onLoad() {\n\tvar field = g_form.getField(\"mrvs_variable_set_name\");\n\tif (field != null) {\n\t\tfield.max_rows_size = 1;\n\t}\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Return Date Validation/README.md",
    "content": "This piece of code was written as a part of an usecase where the return date value validation was expected to be after start date as well as within 6 months from the start date. This code runs in an OnChange catalog client script for the field 'u_return_date' and validates the return date(u_return_date) in a ServiceNow Catalog item form to ensure:\n\n1. A start date(u_start_date) is entered before setting a return date(u_return_date).\n2. The return date is within 6 months after the start date.\n3. Return date must be after the start date, it can't be same as it or before it.\n\nLet’s say with an example:\n\na)\n  u_start_date = 2025-10-01\n  You enter u_return_date = 2026-04-15\n  \n  Steps:\n  a)Difference = 196 days → More than 180 days\n  b)Result: u_return_date is cleared and error shown: “Select Return Date within 6 months from Start Date”\n\nb)\n  u_start_date = 2025-10-02\n  You enter u_return_date = 2025-10-01\n  \n  Steps:\n  a)Difference = -1 day → Return date put as 1 day before start date\n  b)Result: u_return_date is cleared and error shown: “Select Return Date in future than Start Date”\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Return Date Validation/validateReturndate.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n    var u_start_date = g_form.getValue('u_start_date');  //start date validation to check to see whether filled or not\n    if (!u_start_date) {\n        g_form.clearValue('u_return_date');\n\t\t    g_form.showFieldMsg('u_return_date', 'Please enter start date', 'error');\n    } else {\n    \t\tvar startTime = getDateFromFormat(u_start_date, g_user_date_format);   //converting to js date object\n    \t\tvar returnTime = getDateFromFormat(newValue, g_user_date_format);\n    \t\tvar selectedStartDate = new Date(startTime);\n    \t\tvar returnDate = new Date(returnTime);\n    \t\tvar returnDateDifference = (returnDate - selectedStartDate) / 86400000; //converting the diff between the dates to days by dividing by 86400000 \n    \t\tif (returnDateDifference > 180) {\n      \t\t\tg_form.clearValue('u_return_date');\n      \t\t\tg_form.showFieldMsg('u_return_date', 'Select Return Date within 6 months from Start Date', 'error');\n    \t\t} else if (returnDateDifference < 1) {\n      \t\t\tg_form.clearValue('u_return_date');\n      \t\t\tg_form.showFieldMsg('u_return_date', 'Select Return Date in future than Start Date', 'error');\n\t\t    }\n\t  }\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Reusable GlideAjax Client Script/DynamicTableQueryUtil.js",
    "content": "var DynamicTableQueryUtil = Class.create();\nDynamicTableQueryUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getTableRow: function() {\n        var tableName = this.getParameter('sysparm_table_name');\n        var keyField = this.getParameter('sysparm_key_field');\n        var keyValue = this.getParameter('sysparm_key_value');\n        var fieldsParam = this.getParameter('sysparm_fields');\n        var limitFields = !JSUtil.nil(fieldsParam);\n        var desiredFields = limitFields ? fieldsParam.split(',') : [];\n\n        var result = {};\n        var tableObj = {};\n        var gr = new GlideRecord(tableName);\n\n        // Use addQuery for non-sys_id fields\n        if (keyField === 'sys_id') {\n            if (!gr.get(keyValue)) {\n                return null;\n            }\n        } else {\n            gr.addQuery(keyField, keyValue);\n            gr.query();\n            if (!gr.next()) {\n                return null;\n            }\n        }\n\n        // Handle variables (if present)\n        if (gr.variables) {\n            for (var key in gr.variables) {\n                if (!JSUtil.nil(gr.variables[key])) {\n                    var variableObj = gr.variables[key];\n                    tableObj['variables.' + key] = {\n                        fieldDisplayVal: variableObj.getDisplayValue() || String(variableObj),\n                        fieldVal: String(variableObj)\n                    };\n                }\n            }\n        }\n\n        // Handle standard fields\n        var fields = gr.getFields();\n        for (var i = 0; i < fields.size(); i++) {\n            var field = fields.get(i);\n            var fieldName = field.getName();\n            tableObj[fieldName] = {\n                fieldDisplayVal: field.getDisplayValue() || String(field),\n                fieldVal: String(field)\n            };\n        }\n\n        // Add sys_id explicitly\n        tableObj['sys_id'] = {\n            fieldDisplayVal: 'Sys ID',\n            fieldVal: gr.getUniqueValue()\n        };\n\n        // Filter fields if requested\n        if (limitFields) {\n            desiredFields.forEach(function(field) {\n                field = field.trim();\n                if (tableObj[field]) {\n                    result[field] = tableObj[field];\n                }\n            });\n        } else {\n            result = tableObj;\n        }\n\n        return new JSON().encode(result);\n    }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Reusable GlideAjax Client Script/Readme.md",
    "content": "This solution provides a generic and reusable GlideAjax-based client-server interaction in ServiceNow that allows querying any table by passing:\n\nTable name\nKey field and value\nDesired fields to retrieve\n\nIt dynamically returns field values from the server and populates them on the form, making it ideal for use cases like CMDB enrichment, entitlement lookups, or dynamic form population.\n\n1. Client Script (onChange)\nTriggers on field change.\nSends parameters to the Script Include via GlideAjax.\nReceives JSON response and sets target field value.\n\nParameters:\nsysparm_table_name: Table to query (e.g., sys_user)\nsysparm_key_field: Field to match (e.g., sys_id)\nsysparm_key_value: Value to match\nsysparm_fields: Comma-separated list of fields to retrieve\n\n2. Script Include: DynamicTableQueryUtil\n   \nProcesses incoming parameters.\nQueries the specified table and retrieves requested fields.\nSupports both standard fields and catalog item variables.\nReturns a JSON object with field values and display values.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Reusable GlideAjax Client Script/clientscript.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    // Define parameters dynamically\n    var tableName = 'sys_user'; // Change as needed\n    var keyField = 'sys_id'; // Change as needed\n    var fieldsToFetch = 'email'; // Comma-separated list\n    var targetField = 'user'; // Field to populate\n\n    var ga = new GlideAjax('DynamicTableQueryUtil');\n    ga.addParam('sysparm_name', 'getTableRow');\n    ga.addParam('sysparm_table_name', tableName);\n    ga.addParam('sysparm_key_field', keyField);\n    ga.addParam('sysparm_key_value', newValue);\n    ga.addParam('sysparm_fields', fieldsToFetch);\n    ga.getXML(function(response) {\n        var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n        if (!answer) {\n            alert('No response from Script Include');\n            return;\n        }\n\n        var parsedAnswer = JSON.parse(answer);\n        if (parsedAnswer[fieldsToFetch]) {\n            g_form.setValue(targetField, parsedAnswer[fieldsToFetch]['fieldVal']);\n        } else {\n            alert('error');\n        }\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Rounding Money or Price Field/README.md",
    "content": "This script is OnChange, and will automatically round the selected field to [x] value.\nThe script can either be applied to a Catalog Item, or a specific Variable in a Var Set.\n\nFor example, if prompting a user for a cost, and said cost should only be in multiples of $5.\n\nAny instance of `[VAR NAME]` in these files should be replaced with the name of the variable that the script is being applied to.\n\nMore information and a full write-up available on [Community](https://community.servicenow.com/community?id=community_article&sys_id=75ab01271bac5d10c17111751a4bcb40)."
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Rounding Money or Price Field/catalog_client_script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n\n    //Value for increment rounding\n    var roundTo = [ROUNDING INCREMENT];\n\n    //Standard onChange code + if the value is now empty, return\n    if (isLoading || newValue == '') return;\n\n    //Get the existing value, and remove the $, if it exists\n    var existingVal = newValue.replace('$', '');\n\n    //Make sure the field contains a valid numerical value\n    if(!Number.isInteger(parseInt(existingVal))) return;\n\n    //Round up if not a multiple of the increment\n    if(existingVal % roundTo !=0) {\n\n        //Calculate the rounded value\n        var newVal = (Math.ceil(existingVal/roundTo)*roundTo);\n        \n        //Set the value of the field to the new, rounded value\n        g_form.setValue('[VAR_NAME]', \"$\"+ newVal);\n\n        //Show a message beneath the field indicating it was rounded, and the new value\n        g_form.showFieldMsg('[VAR_NAME]', \"Rounded $\" + existingVal + \" to $\" + newVal);\n    }\n} "
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Rounding Money or Price Field/catalog_client_script_config.md",
    "content": "Name: Round [Field] to nearest [Rounding Amount]\nUI Type: All\nType: onChange\n\nVariable name: [VAR NAME]\n\nScript: see `catalog_client_script.js` for code."
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Schedule Request/Readme.JS",
    "content": "This project allows users to schedule a Service Catalog request for a future date and time. Instead of submitting immediately, the request is stored and automatically submitted later using a Scheduled Job\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Schedule Request/scheduled client script.js",
    "content": "function onSubmit() {\n  var scheduledTime = g_form.getValue('scheduled_time');\n  var currentTime = new Date().toISOString();\n\n  if (scheduledTime > currentTime) {\n    var ga = new GlideAjax('ScheduledRequestHelper');\n    ga.addParam('sysparm_name', 'storeScheduledRequest');\n    ga.addParam('sysparm_item', g_form.getUniqueValue());\n    ga.addParam('sysparm_time', scheduledTime);\n    ga.getXMLAnswer(function(response) {\n      alert('Your request has been scheduled for: ' + scheduledTime);\n    });\n    return false; // Prevent immediate submission\n  }\n\n  return true; // Submit immediately if time is now or past\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Schedule Request/scheduled scriptinclude.JS",
    "content": "var ScheduledRequestHelper = Class.create();\nScheduledRequestHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  storeScheduledRequest: function() {\n    var itemID = this.getParameter('sysparm_item');\n    var scheduledTime = this.getParameter('sysparm_time');\n\n    var record = new GlideRecord('x_snc_scheduled_requests'); // Custom table\n    record.initialize();\n    record.catalog_item = itemID;\n    record.scheduled_time = scheduledTime;\n    record.insert();\n\n    return 'Scheduled successfully';\n  }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Schedule Request/scheduled_job.JS",
    "content": "var now = new GlideDateTime();\nvar gr = new GlideRecord('x_snc_scheduled_requests');\ngr.addQuery('scheduled_time', '<=', now);\ngr.query();\n\nwhile (gr.next()) {\n  var request = new GlideRecord('sc_request');\n  request.initialize();\n  request.requested_for = gr.requested_for;\n  request.cat_item = gr.catalog_item;\n  request.insert();\n\n  gr.deleteRecord(); // Remove after submission\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set User Field Values on Load/README.md",
    "content": "On Load Catalog client script is created to auto set the field values and make that field read only\n - Navigate to your instance -> App Navigator > Open Catalog CLient Script [catalog_script_client]\n - Set following field Values\n        - Name: xyz\n        - Applies to: A Catalog item\n        - Type: onLoad\n        - Catalog item: Select your Catalog item\n        - UI Type: All\n        - Isolated script: Checked\n  -  Create the script as per script.js file.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set User Field Values on Load/script.js",
    "content": "/*\nSet user field value on load using catalog cleint script and make the field readonly\n*/\n\nfunction onLoad()\n{\n  var user_id = g_user.userID;\n  g_form.setValue('field_name', user_id);\n  g_form.setReadOnly('field_name', true);\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set and Lock Variable by Group/README.md",
    "content": "**Set and Lock Variable by Group**\n\nThis solution provides a secure and dynamic way to control data entry on a Service Catalog form based on the user's group membership. It is typically used to pre-fill and lock certain justification or approval bypass fields for authorized users (like managers or executive staff), improving their efficiency while maintaining an accurate audit trail.\n\nThis functionality requires a combined Client-side (Catalog Client Script) and Server-side (Script Include) approach to ensure the group check is done securely.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set and Lock Variable by Group/set_lock_variable_by_grp.js",
    "content": "// onload Catalog Client Script with Catalog Name \nfunction onLoad() {\n    var variableName = 'bypass_approval_reason';\n    var targetGroupName = 'ServiceNow Support'; // The group authorized to skip this step\n    var ga = new GlideAjax('UserUtils');\n    ga.addParam('sysparm_name', 'isMemberOf');\n    ga.addParam('sysparm_group_name', targetGroupName);\n    ga.getXMLAnswer(checkAndLockVariable);\n    function checkAndLockVariable(response) {\n        var isMember = response;\n        if (isMember == 'true') {\n            var message = 'Value set and locked due to your ' + targetGroupName + ' membership.';\n            var setValue = 'Bypassed by authorized ' + targetGroupName + ' member.';\n            g_form.setValue(variableName, setValue);\n            g_form.setReadOnly(variableName, true);\n            g_form.showFieldMsg(variableName, message, 'info');\n        } else {\n            g_form.setReadOnly(variableName, false);\n        }\n    }\n}\n\n//Script Include\nvar UserUtils = Class.create();\nUserUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    isMemberOf: function() {\n        var groupName = this.getParameter('sysparm_group_name');\n        var isMember = gs.getUser().isMemberOf(groupName);\n        return isMember.toString();\n    },\n\n    type: 'UserUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set fields from URL Parameter 2/CatalogClientScript.js",
    "content": "function onLoad() {\n   \n    var taskId = getParameterValue(\"taskid\");\n   \n    if (taskId != \"\" && taskId != null && taskId != undefined) {\n      console.log('=== CAMACHO Task id: ' + taskId);\n      \n      var gaGetTaskNumber = new GlideAjax('UtilsAjax');\n      gaGetTaskNumber.addParam('sysparm_name', 'getTaskNumber');\n      gaGetTaskNumber.addParam('sysparm_task_id', taskId);\n      gaGetTaskNumber.getXMLAnswer(setmyFormValue);\n    }\n }\n \n function setmyFormValue(answer) {\n   \n   //console.log('=== CAMACHO Entered setmyFormValue');\n   if (answer) {\n     var obj = JSON.parse(answer);\n     var numero = obj.number.toString();\n     console.log(numero);\n     \n     g_form.setValue('task_number', numero);\n     \n   }\n }\n \n function getParameterValue(name) {\n   var url = top.location.href;\n   var value = new URLSearchParams(url).get(name);\n   if (value) {\n     return value;\n   } else {\n     return null;\n   }\n }"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set fields from URL Parameter 2/KMXOUtils.js",
    "content": "var KMXOUtils = Class.create();\nKMXOUtils.prototype = {\n    initialize: function() {\n    },\n\n  /*\n  * Receives a um sys_id and returns a Task table field value\n  *\n  * @param {String} - taskId\n  * @return {Object}\n  */\n  getTaskNumber: function(taskId)\n  {\n      if (taskId != \"\" && taskId != null && taskId != undefined) {\n\n        var grTask = new GlideRecord('x_770214_consultor_rwd_activity');\n\n        if (grTask.get(taskId)) {\n          \n          var number = grTask.getValue('number');\n          \n          var obj = {};\n          obj[\"number\"] = number;\n          \n          return obj;\n\n        } else {\n\n          return {};\n\n        }\n        \n      }\n  },\n\n    type: 'KMXOUtils'\n};"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set fields from URL Parameter 2/README.md",
    "content": "# Set fields on a catalog item from URL parameters.\n\nThe mission was to get a sys_id from the URL, query a record and return a value to the front-end.\n\nIn the front-end we have a Record Producer (RP) with a String field.\n\nOn the onLoad event, we want to populate the field in order to show the value retrieved from the back-end.\n\nThe Use Case is defined so let's go to the step by step process.\n\n1. Create a Util class in the back-end\n\nOur Utils class will be a Script Include that receives a sys_id and returns a String. \n\n[KMXOUtils](KMXOUtils.js) \n\n2. Create a class to provide access for the Front-End\n\n2.1. To provide access in this class, the parameter Client callable should be True (checked)\n\n[UtilsAjax](UtilsAjax.js) \n\n3. I'll suppose that you have a String field called task_number in your RP\n\n3.1. Create a Catalog Client Script (Type: OnLoad) to get the URL parameter and call the back-end class:\n\n[CatalogClientScript](CatalogClientScript.js) \n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set fields from URL Parameter 2/UtilsAjax.js",
    "content": "var UtilsAjax = Class.create();\nUtilsAjax.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\n  getTaskNumber: function() {\n    var taskId = this.getParameter('sysparm_task_id');\n    gs.debug('=== Camacho UtilsAjax = Received the sys_id ' + taskId);\n    return JSON.stringify(new KMXOUtils().getTaskNumber(taskId));\n    \n  },\n    type: 'UtilsAjax'\n});"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set fields from URL Parameters/README.md",
    "content": "# Set fields on a catalog item from URL parameters.\n\nThis only works on both the classic ui and service portal.\n\nTo use this you must provide the technical name as a url parameter and then provide the value you would like set. This script is also console logging the techcnical names if you don't have them handy. Reference fields require using the sys_id.\n\n\n### Example\n\nFor the OOTB Password Reset catalog item it has a field \"Whose password needs to be reset?\" with a technical value of \"caller_id\" after adding this script you could use the below url parameter to auto populate the form with Abel Tuter.\n\n/sp?id=sc_cat_item&sys_id=29a39e830a0a0b27007d1e200ad52253&**caller_id=62826bf03710200044e0bfc8bcbe5df1**\n\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Set fields from URL Parameters/script.js",
    "content": "function onLoad() {\n\n\ttry{ // Classic UI\n\t\tvar pFields = g_form.nameMap;\n\t\tconsole.log(pFields);\n\t\tpFields.forEach(function(field){\n\t\t\tif(getParam(field.prettyName)){\n\t\t\t\tg_form.setValue(field.prettyName, getParam(field.prettyName));\n\t\t\t}\n\t\t});\n\n\t}catch(e){ // Service Portal or Mobile\n\t\tvar fields = g_form.getEditableFields();\n\t\tconsole.log(fields);\n\t\tfields.forEach(function(field){\n\t\t\tif(getParam(field)){\n\t\t\t\tg_form.setValue(field, getParam(field));\n\t\t\t}\n\t\t});\n\t}\n}\n\nfunction getParam(name){\n\tvar url = new URL(top.location);\n\treturn url.searchParams.get(name);\n}"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Validate a Credit Card Number/README.md",
    "content": "**Description of the Credit Card Number Validation Script**\nPurpose\nThe script validates a credit card number entered by the user in a ServiceNow form. \nIt checks if the number is a valid 16-digit credit card number using a combination of a regular expression and the Luhn algorithm for basic validation.\n\n**Validation Criteria**\nFormat:\nThe credit card number must consist of exactly 16 digits.\n**Luhn Algorithm:**\nThe script implements the Luhn algorithm to determine if the credit card number is potentially valid. \nThis algorithm helps catch common errors in credit card numbers, such as transposed digits.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/Validate a Credit Card Number/Script.js",
    "content": "function onSubmit() {\n    var cardNumber = g_form.getValue('credit_card'); // Change 'credit_card' to your field name\n    var cardPattern = /^\\d{16}$/; // Simple pattern for 16-digit cards\n\n    if (!cardPattern.test(cardNumber) || !isValidCardNumber(cardNumber)) {\n        g_form.showFieldMsg('credit_card', 'Please enter a valid 16-digit credit card number.', 'error');\n        return false;\n    }\n    return true;\n}\n\nfunction isValidCardNumber(number) {\n    var sum = 0;\n    var alternate = false;\n    for (var i = number.length - 1; i >= 0; i--) {\n        var n = parseInt(number.charAt(i), 10);\n        if (alternate) {\n            n *= 2;\n            if (n > 9) n -= 9;\n        }\n        sum += n;\n        alternate = !alternate;\n    }\n    return sum % 10 === 0;\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/catalog Draft/Readm.md",
    "content": "This project implements an Auto Save Draft feature for ServiceNow Catalog Items. It automatically saves the user’s progress (form variables) every few minutes to prevent data loss if the session times out or the browser closes. it Prevents data loss during long form filling.\n\n\n\nfeatures\nAuto-save catalog form data every 2 minutes.\n Stores draft data in a custom table.\nRestores saved data when the user reopens the catalog item.\n Works in Service Portal\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/catalog Draft/Script include.JS",
    "content": "var CatalogDraftUtils = Class.create();\nCatalogDraftUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    saveDraft: function() {\n        var userId = gs.getUserID();\n        var catalogItem = this.getParameter('sysparm_catalog_item');\n        var draftData = this.getParameter('sysparm_draft_data');\n\n        var gr = new GlideRecord('u_catalog_draft');\n        gr.addQuery('user', userId);\n        gr.addQuery('catalog_item', catalogItem);\n        gr.query();\n        if (gr.next()) {\n            gr.variables_json = draftData;\n            gr.last_saved = new GlideDateTime();\n            gr.update();\n        } else {\n            gr.initialize();\n            gr.user = userId;\n            gr.catalog_item = catalogItem;\n            gr.variables_json = draftData;\n            gr.last_saved = new GlideDateTime();\n            gr.insert();\n        }\n        return 'Draft saved successfully';\n    },\n    type: 'CatalogDraftUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/catalog Draft/client script.js",
    "content": "function onLoad() {\n    console.log('Auto Save Draft initialized');\n\n    setInterval(function() {\n        var draftData = {\n            hardware_name: g_form.getValue('hardware_name'),\n            quantity: g_form.getValue('quantity')\n        };\n\n        var ga = new GlideAjax('CatalogDraftUtils');\n        ga.addParam('sysparm_name', 'saveDraft');\n        ga.addParam('sysparm_catalog_item', g_form.getValue('sys_id')); // Catalog item sys_id\n        ga.addParam('sysparm_draft_data', JSON.stringify(draftData));\n\n        ga.getXMLAnswer(function(response) {\n            console.log('Draft saved: ' + response);\n        });\n    }, 120000); // Every 2 minutes\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/onCellEdit Catalog Task State Change Restriction/README.md",
    "content": "# Catalog Task state should not be closed from list edit\nThe onCellEdit Client Script type is used for lists rather than forms and here \nit is being used so that the task should not be closed from the list edit without\nopening the form and filling other mandatory details like worknotes etc.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/onCellEdit Catalog Task State Change Restriction/script.js",
    "content": "function onCellEdit(sysIDs, table, oldValues, newValue, callback) {\n  var saveAndClose = true;\n //Type appropriate comment here, and begin script below\n  // here the values are 7|closed skipped, 3|closed complete and 4|closed incomplete\n if(newValue == 7 || newValue == 3 || newValue == 4) {\n\tsaveAndClose = false;\n\talert('you cannot update from list');\n }\n callback(saveAndClose); \n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/previous Request/Readme.MD",
    "content": "Show previous request ON requested for selection\n\nThis feature enhances the Service Catalog experience by displaying previous requests for the selected Requested For user. When a user selects the Requested For variable in a catalog item form, a confirmation message appears showing the last few requests created for that user.\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/previous Request/previous request client script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') return;\n\n    var ga = new GlideAjax('PreviousRequestsUtils');\n    ga.addParam('sysparm_name', 'getPreviousRequests');\n    ga.addParam('sysparm_requested_for', newValue);\n    ga.getXMLAnswer(function(response) {\n        var requests = JSON.parse(response);\n        if (requests.length === 0) {\n            alert('No previous requests found for this user.');\n        } else {\n            var message = 'Previous Requests:\\n\\n';\n            requests.forEach(function(req) {\n                message += 'Number: ' + req.number + ' | Item: ' + req.item + ' | Date: ' + req.date + '\\n';\n            });\n            if (confirm(message + '\\nDo you want to continue?')) {\n                // User clicked OK\n            } else {\n                // User clicked Cancel\n                g_form.setValue('requested_for', oldValue);\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/previous Request/previous request script include.js",
    "content": "var PreviousRequestsUtils = Class.create();\nPreviousRequestsUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getPreviousRequests: function() {\n        var requestedFor = this.getParameter('sysparm_requested_for');\n        var result = [];\n        var gr = new GlideRecord('sc_req_item');\n        gr.addQuery('requested_for', requestedFor);\n        gr.orderByDesc('sys_created_on');\n        gr.setLimit(5); // Show last 5 requests\n        gr.query();\n        while (gr.next()) {\n            result.push({\n                number: gr.number.toString(),\n                item: gr.cat_item.getDisplayValue(),\n                date: gr.sys_created_on.getDisplayValue()\n            });\n        }\n        return JSON.stringify(result);\n    }\n});\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/spModal for Sweet Alerts/readme.md",
    "content": "In ServiceNow, Open catalog client Scripts [catalog_script_client] and paste the code snippet of [spModalSweetAlerts.js] file.\n\nSetup:\n1. A catalog item having variable name Rewards[rewards] of type 'Select Box'(include none as true) and 2 choices(Yes and No)\n2. A Single line type field named 'Reward Selected' [reward_selected] which will hold the value selected by user from the spModal popup.\n3. The onLoad catalog client script setup as below:\n4. Type: onChange\n5. Variable: rewards (as per step 1)\n6. Script: [[spModalSweetAlerts.js]]\n   \n\n\nScreenshots:\n\n\n<img width=\"1338\" height=\"268\" alt=\"image\" src=\"https://github.com/user-attachments/assets/f7f22b83-7e0e-47bb-bbed-2a8f38783a4d\" />\n\n\nRewards selected as 'Yes'\n\n<img width=\"1353\" height=\"327\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1bb55339-36b4-4a9c-8b65-2b254b87cf5b\" />\n\nFrom the spModal popup select anyone of the reward, it should be populated in the Reward Selected field.\nAlong with that a message shows the same of the selection.\n\n<img width=\"1350\" height=\"319\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1b23c766-51f8-4b01-9073-f836f390deb2\" />\n\n"
  },
  {
    "path": "Client-Side Components/Catalog Client Script/spModal for Sweet Alerts/spModalSweetAlerts.js",
    "content": "function onChange(control, oldValue, newValue) {\n    if (newValue == 'Yes') {\n        spModal.open({\n            title: \"Reward Type\",\n            message: \"Please select the category of Reward\",\n            buttons: [{\n                    label: \"Star Performer\",\n                    value: \"Star Performer\"\n                },\n                {\n                    label: \"Emerging Player\",\n                    value: \"Emerging Player\"\n                },\n                {\n                    label: \"High Five Award\",\n                    value: \"High Five Award\"\n                },\n                {\n                    label: \"Rising Star\",\n                    value: \"Rising Star\"\n                }\n            ]\n        }).then(function(choice) {\n            if (choice && choice.value) {\n\t\t\t\tg_form.addInfoMessage('Selected Reward: '+ choice.label);\n                g_form.setValue('reward_selected', choice.value);\n            }\n        });\n    } else {\n        g_form.clearValue('reward_selected');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Abort action when description is empty/Code.js",
    "content": "function onSubmit() {\n    //Type appropriate comment here, and begin script below\n    var description = g_form.getValue('description');\n\tvar state = g_form.getValue('state');\n\n    if ((!description) && (state == 'completed')) {\n        g_form.addErrorMessage('Please provide Description Value, Description Cannot be empty');\n\n        return false;\n    }\n\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Abort action when description is empty/ReadMe.md",
    "content": "When an Incident record is being closed, the system should validate that the Description field is not empty and state is completed.\nIf the Description field is blank and state is completed, the record submission (update) should be aborted, and the user should be prompted to provide a description before closing the incident.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Abort direct incident closure without Resolve State/readme.md",
    "content": "📘 README — Incident State Validation (Client Script)\nOverview\n\nThis Client Script enforces the correct ITIL incident lifecycle by preventing users from directly closing an incident without first transitioning it through the Resolved state.\nIf a user attempts to move the state directly to Closed Complete, the system reverts the change and displays a notification.\n\nWhat This Code Does\nMonitors changes to the state field on Incident form\nChecks if new selection is trying to skip the Resolved step\nReverts state to its previous value when the rule is violated\nAlerts the user with a clear and guided message\nRefreshes the form to maintain data consistency\nUsage Instructions\n\nCreate a Client Script on the Incident table\nType: onChange\nField Name: state\nPaste the script under the script section\nTest by trying to directly move an Incident to Closed Complete\nS\ncript Requirements\nState values must be configured as:\n6 → Resolved\n7 → Closed Complete\n\nScript runs only on Incident records\nMust be active in applicable ITIL views\nNotes for Developers\nThis code supports clean transition handling for ITSM workflows\nHelps enforce process compliance without server-side overhead\nRecommended for environments requiring strict closure governance\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Abort direct incident closure without Resolve State/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    // Prevent execution during form load or when value is empty\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    // Dummy values for state comparison\n    // Assuming:\n    // 6 = Resolved\n    // 7 = Closed Complete\n    var RESOLVED = 6;\n    var CLOSED = 7;\n\n    // Prevent direct move to Closed without passing through Resolved\n    if (newValue == CLOSED && oldValue != RESOLVED) {\n        \n        // Reset to previous state\n        g_form.setValue('state', oldValue);\n\n        // Show validation warning\n        g_form.addErrorMessage(\n            'Direct closure is not allowed. Please first move the incident to Resolved state.'\n        );\n\n        // Reload page after showing error\n        setTimeout(function() {\n            location.reload();\n        }, 3000);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Add Field Decoration/Add Field Decoration.js",
    "content": "function onLoad() {\n    // Adding Field Decorators\n    g_form.addDecoration(\"caller_id\", \"icon-user-profile\", \"The Requester\");\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Add Field Decoration/README.md",
    "content": "# Client Script - Add Field Decoration\n\nA client script that added field decoration.\n\n## Usage\n\n- Create a new OnLoad script\n- Copy this script into it\n- Change the field name, icon and the message\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Add Image to Field Based on Company/AddImageToFieldBasedOnCompany.js",
    "content": "function onLoad() {\n    //Type appropriate comment here, and begin script below\n\t\n\t//Get element of the field\n    var requestedForLabel = $('element.sc_req_item.request.requested_for');\n\tvar company = g_scratchpad.company;\n\n\t//Set the Position of the image\n\tvar bgPosition = \"5% 45%\";\n\t\n\tvar image = '';\n\t\n\t//Set the image based on the Company\n\tswitch (company) {\n            case 'company 1':\n                image = 'url(company1.png)';\n                break;\n            case 'company 2':\n                image = 'url(company2.png)';\n                break;\n            case 'company 3':\n                image = 'url(company3.png)';\n                break;\n            default:\n                image = '';\n        }\n\t\n\t//Update the Field Label\n    requestedForLabel.down('label').setStyle({\n        backgroundImage: image,\n        backgroundRepeat: \"no-repeat\",\n        backgroundPosition: bgPosition,\n        paddingLeft: '30px'\n    });\n\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Add Image to Field Based on Company/README.md",
    "content": "# Add Image to Field Based on Company\nFrom the User Profile get the Company associated to the User using a Display Business Rule and putting the value in the scratchpad. After that use the scratchpad value in the Client Script to check the company name and then associate the image accordingly. You would need to add all your images to the ServiceNow instance first with the correct names.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Add Image to Field Based on Company/SetCompanyScratchPadValue.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Set the Scratchpad value for Company which would be used in the Client Script\n\tg_scratchpad.company = current.request.requested_for.company.getDisplayValue();\n\n})(current, previous);\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Adding Placeholder on Resolution Notes/README.md",
    "content": "# Adding Placeholder Text in Resolution Notes\n\nTo maintain consistency and ensure specific information is captured in resolution notes, process owners may require fulfillers to follow a predefined format when resolving tickets.\n\nBy adding **placeholder** text in the resolution notes, fulfillers are reminded of the required information(e.g., Root cause, Steps taken, Resolution provided), reducing the risk of missing important details. The placeholder disappears as soon as the fulfiller begins entering their notes, ensuring it doesn't interfere with their input.\n\n## How It Works\n\n### When?\n- The placeholder text is automatically added when the state of the ticket changes to Resolved (6).\n\n### What Happens?\n- A placeholder text appears in the resolution notes field to guide the fulfiller.\n- As soon as the fulfiller starts typing, the placeholder disappears.\n- This ensures consistency and alignment with the process requirements.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Adding Placeholder on Resolution Notes/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n    var res = g_form.getElement('close_notes');\n    if (newValue == 6)\n        res.placeholder = \"1. Root Cause\" + \"\\n\" + \"2. Steps taken\" + \"\\n\" + \"3. Resolution provided\"; //Placeholder text as required\n    else\n        res.placeholder = \"\";\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto Update Priority based on Impact and Urgency/readme.md",
    "content": "🧩 Readme : Client Script: Auto Priority Update Based on Impact and Urgency\n📘 Overview\n\nThis client script automatically updates the Priority field on the Incident form whenever the Impact or Urgency value changes.\nIt follows the ITIL standard mapping to ensure the correct priority is always set automatically, improving data accuracy and efficiency for service desk agents.\n\n⚙️ Script Details\nField\tValue\nName\tAuto Priority Update based on Impact and Urgency\nType\tonChange\nApplies to Table\tIncident\nApplies on Fields\timpact, urgency\nUI Type\tAll (Classic, Mobile, Workspace)\nActive\t✅ Yes\nCondition\tLeave blank\n💻 Script Code\n// ==========================================================================\n// Script Name: Auto Priority Update based on Impact and Urgency\n// Table: Incident\n// Type: onChange | Fields: impact, urgency\n// UI Type: All\n// Version: 2025 Production Ready\n// ==========================================================================\n\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    // Skip execution if form is loading or field is empty\n    if (isLoading || newValue == '') {\n        return;\n    }\n\n    // Get Impact and Urgency values\n    var impact = g_form.getValue('impact');\n    var urgency = g_form.getValue('urgency');\n\n    // Define Priority Matrix (ITIL standard)\n    var priorityMatrix = {\n        '1': { '1': '1', '2': '2', '3': '3' },\n        '2': { '1': '2', '2': '3', '3': '4' },\n        '3': { '1': '3', '2': '4', '3': '5' }\n    };\n\n    // Find the new Priority\n    var newPriority = priorityMatrix[impact]?.[urgency];\n\n    // Update the Priority field if valid\n    if (newPriority) {\n        if (g_form.getValue('priority') != newPriority) {\n            g_form.setValue('priority', newPriority);\n            g_form.showFieldMsg('priority', 'Priority auto-updated based on Impact and Urgency', 'info');\n        }\n    } else {\n        // Optional: Clear Priority if invalid combination is selected\n        g_form.clearValue('priority');\n        g_form.showFieldMsg('priority', 'Invalid Impact/Urgency combination — priority cleared', 'error');\n    }\n}\n\n🧠 How It Works\n\nThe script runs automatically when Impact or Urgency changes.\nIt checks the ITIL-based matrix to determine the correct Priority.\nIf a valid combination is found, the Priority field updates automatically.\nA small info message appears to confirm the update.\n\n🔢 ITIL Mapping Table\nImpact\tUrgency\tResulting Priority\n1 (High)\t1 (High)\t1 (Critical)\n1\t2\t2\n1\t3\t3\n2\t1\t2\n2\t2\t3\n2\t3\t4\n3\t1\t3\n3\t2\t4\n3\t3\t5\n✅ Benefits\n\nAutomatically enforces ITIL priority standards\nReduces manual effort and user errors\nEnsures consistency in priority calculation\nCompatible with Classic UI, Next Experience, and Agent Workspace\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto Update Priority based on Impact and Urgency/script.js",
    "content": "//Auto Priority Update based on Impact and Urgency\n\n// ==========================================================================\n// Script Name: Auto Priority Update based on Impact and Urgency\n// Table: Incident (or any Task-based table)\n// Type: onChange | Fields: impact, urgency\n// UI Type: All\n// ==========================================================================\n\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    // Prevent the script from running when the form loads or when the field is empty\n    if (isLoading || newValue == '') {\n        return;\n    }\n\n    // ----------------------------------------------------------------------\n    // Step 1: Fetch field values from the form\n    // ----------------------------------------------------------------------\n    var impact = g_form.getValue('impact');   // e.g., 1 - High, 2 - Medium, 3 - Low\n    var urgency = g_form.getValue('urgency'); // e.g., 1 - High, 2 - Medium, 3 - Low\n\n    // ----------------------------------------------------------------------\n    // Step 2: Define the ITIL-based Priority Matrix\n    // ----------------------------------------------------------------------\n    // Each row represents \"Impact\", and each column represents \"Urgency\"\n    // The resulting value sets the \"Priority\"\n    var priorityMatrix = {\n        '1': { '1': '1', '2': '2', '3': '3' }, // Impact = High\n        '2': { '1': '2', '2': '3', '3': '4' }, // Impact = Medium\n        '3': { '1': '3', '2': '4', '3': '5' }  // Impact = Low\n    };\n\n    // ----------------------------------------------------------------------\n    // Step 3: Determine the new priority based on selected Impact/Urgency\n    // ----------------------------------------------------------------------\n    var newPriority = priorityMatrix[impact]?.[urgency]; // optional chaining prevents errors\n\n    // ----------------------------------------------------------------------\n    // Step 4: Update the Priority field and inform the user\n    // ----------------------------------------------------------------------\n    if (newPriority) {\n        // Only update if priority is different from current value\n        if (g_form.getValue('priority') != newPriority) {\n            g_form.setValue('priority', newPriority);\n\n            // Show message (works in both Classic UI and Next Experience)\n            g_form.showFieldMsg('priority', 'Priority auto-updated based on Impact and Urgency', 'info');\n        }\n    } else {\n        // Optional: clear priority if invalid combination is selected\n        g_form.clearValue('priority');\n        g_form.showFieldMsg('priority', 'Invalid Impact/Urgency combination — priority cleared', 'error');\n    }\n\n    // ----------------------------------------------------------------------\n    // End of Script\n    // ----------------------------------------------------------------------\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-Populate Planned End Date/README.md",
    "content": "An onChange client script that calls the script includes one that adds the specified number of business days to the planned start date and autopopulates the planned end date based on the type.\nFor the client callable script include refer to the README file in the Add Business Days Script include folder. Link: [Add Business Days Script Include](/Server-Side%20Components/Script%20Includes/Add%20Business%20Days/README.md)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-Populate Planned End Date/autoPopulatePlannedEndDate.js",
    "content": "//Client script\n//Table: Change Request\n//UI Type: All\n//Type: onChange\n//Field: Planned Start Date\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }  \n    var daysToAdd;\n    if(g_form.getValue('type') =='standard' || g_form.getValue('type') =='normal')\n      daysToAdd = 3; \n    else if(g_form.getValue('type') =='emergency')\n      daysToAdd = 1;\n    var ga = new GlideAjax(\"addBusinessDays\"); //Calling the add business days script include, which is in the Server-Side Components/Script Includes/Add Business Days/addBusinessDays.js\n    ga.addParam('sysparm_name', 'addDays');\n    ga.addParam('sysparm_days', daysToAdd); \n    ga.addParam('sysparm_date', newValue);\n    ga.getXML(processResponse);\n\n    function processResponse(response) {\n        var answer = response.responseXML.documentElement.getAttribute(\"answer\").toString();\n        g_form.setValue('end_date', answer);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-Populate Short Discription/Auto populate short description.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    // Define category-to-short description mapping\n    var categoryToShortDescription = {\n        'hardware': 'Hardware Issue - ',\n        'software': 'Software Issue - ',\n        'network': 'Network Issue - ',\n        'inquiry': 'Inquiry/Help - ',\n\t\t'database': 'Database - '\n    };\n\n    // Convert the selected value to lowercase for matching\n    var selectedCategory = newValue.toLowerCase();\n\n    // If category exists in mapping, update the short description\n    if (categoryToShortDescription.hasOwnProperty(selectedCategory)) {\n        var existingDesc = g_form.getValue('short_description') || '';\n        var prefix = categoryToShortDescription[selectedCategory];\n\n        // Only add prefix if it's not already there\n        if (!existingDesc.startsWith(prefix)) {\n            g_form.setValue('short_description', prefix + existingDesc);\n            g_form.showFieldMsg('short_description', 'Short Description auto-updated based on category.', 'info');\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-Populate Short Discription/README.md",
    "content": "\n# Auto-Populate Short Description Client Script\n\n## Overview\n\nThis client script is designed to enhance the user experience in ServiceNow by automatically populating the 'Short Description' field when a user selects a category for an incident or request. It simplifies the data entry process, ensures consistency in short descriptions, and saves time for users.\n\n## How It Works\n\nWhen a user selects a category from the provided options, the script appends a predefined prefix to the existing 'Short Description' value, creating a more informative short description.\n\n### Configuration\n\nTo configure and use this client script in your ServiceNow instance, follow these steps:\n\n1. **Create a Client Script:**\n\n   - Log in to your ServiceNow instance as an admin or developer.\n   - Navigate to \"System Definition\" > \"Client Scripts.\"\n   - Create a new client script and give it a name (e.g., \"Auto-Populate Short Description\").\n\n2. **Copy and Paste the Script:**\n\n   - Copy the JavaScript code provided in this README.\n   - Paste the code into your newly created client script.\n\n3. **Attach to 'category' Field:**\n\n   - Save the client script and ensure it's active.\n   - Attach the client script to the 'category' field of the relevant table (e.g., Incident, Request) where you want to enable this functionality.\n\n4. **Define Your Category-to-Short-Description Mappings:**\n\n   - Modify the script to define your own mappings for categories and their corresponding short description prefixes. Customize the `categoryToShortDescription` object to match your requirements.\n\n5. **Testing:**\n\n   - Test the functionality by creating or editing an incident or request record.\n   - Select a category, and observe how the 'Short Description' field is automatically populated based on your mappings.\n\n## Example Mapping\n\nHere's an example of how you can define category-to-short-description mappings in the script:\n\n```javascript\nvar categoryToShortDescription = {\n  'Hardware': 'Hardware Issue - ',\n  'Software': 'Software Issue - ',\n  'Network': 'Network Issue - ',\n  'Other': 'Other Issue - '\n};\n```\n\nYou can customize these mappings to align with your organization's specific categories and short description conventions.\n\n## Benefits\n\n- Streamlines data entry for users.\n- Ensures consistent and informative short descriptions.\n- Saves time and reduces the risk of human error.\n- Enhances user experience in ServiceNow.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-Populate Short Discription/Readme.md",
    "content": "# Auto-Populate Short Description (Client Script)\n## Overview\n\nThis client script automatically updates the Short Description field in ServiceNow whenever a user selects a category on the form. It improves data consistency, saves time, and ensures that short descriptions remain meaningful and standardized.\n\n## How It Works\n\nWhen a user selects a category such as Hardware, Software, or Network, the script automatically prefixes the existing short description with a corresponding label (for example, “Hardware Issue –”).\nThis makes incident records easier to identify and improves the quality of data entry.\n\n## Configuration Steps\n\n1. Log in to your ServiceNow instance with admin or developer access.\n2. Navigate to System Definition → Client Scripts.\n3. Create a new Client Script with the following details:\n\n```\nTable: Incident\nType: onChange\nField name: category\nCopy and paste the provided script into the Script field.\nSave the record and make sure the script is active.\n```\n\n## Testing\n\n1. Open any existing or new Incident record.\n2. Select a category such as Hardware or Software.\n3. The Short Description field will automatically update with the corresponding prefix.\n\n## Benefits\n\nSpeeds up data entry for users.\nMaintains consistency in short descriptions.\nReduces manual effort and human errors.\nEnhances clarity in incident listings and reports.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-Populate Short Discription/autoPopulateSD.js",
    "content": "// Client Script to Auto-Populate Short Description based on Category\n\nfunction onChangeCategory() {\n    var categoryField = g_form.getValue('category'); // Get the selected category\n    var shortDescriptionField = g_form.getValue('short_description'); // Get the Short Description field\n\n    // Define mappings for categories and corresponding short descriptions\n    var categoryToShortDescription = {\n        'Hardware': 'Hardware Issue - ',\n        'Software': 'Software Issue - ',\n        'Network': 'Network Issue - ',\n        'Other': 'Other Issue - '\n    };\n\n    // Update Short Description based on the selected category\n    if (categoryToShortDescription.hasOwnProperty(categoryField)) {\n        var newShortDescription = categoryToShortDescription[categoryField] + shortDescriptionField;\n        g_form.setValue('short_description', newShortDescription);\n    }\n}\n\n// Attach the onChangeCategory function to the 'category' field\ng_form.observe('change', 'category', onChangeCategory);\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-populate watch_list based on company/README.md",
    "content": "Table: Incident\nType: onChange\nfield: caller_id\n\nSometimes customers have a requirement to add users or distribution lists to all tickets that are raised for their company. This script allows to automatically add a list of users or external email addresses to the watch_list field."
  },
  {
    "path": "Client-Side Components/Client Scripts/Auto-populate watch_list based on company/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading) {\n        return;\n    }\n    var callerID = g_form.getReference('caller_id', getUserCompany);\n}\n\nfunction getUserCompany(callerID) {\n    switch (callerID.company) {\n        case 'company_1_sys_id':\n            g_form.setValue('watch_list', 'PEOPLE_TO_ADD');\n            //PEOPLE_TO_ADD is a comma separated list of user sys_id\n            //and external email addresses\n            break;\n        case 'company_2_sys_id':\n            g_form.setValue('watch_list', 'PEOPLE_TO_ADD');\n            break;\n        default:\n            break;\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Call SI to recover User data/README.md",
    "content": "# Call Script Include to recover User data\n\nIn this onChange Client Script, the scenario is this:\n- In a form, we have a reference field to the User table to allow our user to select any user within the platform.\n- Once a user is selected, we are passing that Sys ID to recover more information regarding the selected record.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Call SI to recover User data/script.js",
    "content": "/*\nType: onChange\nField: a reference to the User table\n*/\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    var test = newValue;\n\n    //The Script Include called here can be found in:\n    //Server-Side Components / Script Includes / Get User Data by Id\n    //It is in Global scope \n    var ga = new GlideAjax('GetUserData');//script include name\n    ga.addParam('sysparm_name', 'GetUserBy_id'); //method to be called\n    ga.addParam('sysparm_userid', test); //send a parameter to the method.\n    ga.getXMLAnswer(userInfoParse);\n\n    function userInfoParse(response) {\n        if (response == '') {\n            g_form.addErrorMessage('User not found with the informed sys_id.');\n        } else {\n            //alert(response);\n            var obj = JSON.parse(response);\n            g_form.setValue('u_first_name', obj.first_name.toString());\n            g_form.setValue('u_last_name', obj.last_name.toString());\n            g_form.setValue('u_email', obj.email.toString());\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Change Label of Field/Change Label of Field.js",
    "content": "function onLoad() {\n    //Type appropriate comment here, and begin script below\n    if (g_form.getValue('priority') == '1') {\n        g_form.setLabelOf('description', 'Please Explain Briefly');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Change Label of Field/README.md",
    "content": "\n# Change Label of Field \n\n• Modify field labels dynamically in the ServiceNow platform.\n\n• Change labels through the UI form designer or through client/server-side scripts.\n\n• Implement Business Rules to change labels based on conditions.\n\n• Adjust field labels dynamically using Client Scripts based on user interactions.\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Change incident Number label color based on priority/Script.js",
    "content": "function onLoad ()\n{\n  var aPriority = g_form.getValue('priority');\n  var label = g_form.getLable('number');\n  label.style.backgroundColor = \"lightblue\";\n  if(aPriority==1)\n  {\n    label.style.color = \"red\";\n  }\n  else if (aPriority==2)\n  {\n    label.style.color = \"yellow\";\n  }\n  else\n  {\n        label.style.color = \"blue\";\n  }\n}\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Change incident Number label color based on priority/readme.md",
    "content": "Write Client Script \nChange Incident Number Lable color based on priority\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Check all mandatory fields using mandatoryCheck()/README.md",
    "content": "# Mandatory Field Check on Form Change\n\nThis client script demonstrates how to use `g_form.mandatoryCheck()` to validate whether all mandatory fields on a form are filled.\n\nIt is typically used in an **onChange** catalog client script to trigger validation when a specific field changes.\n\nIf mandatory fields are missing, the script:\n- Displays an info message listing the missing fields. Note: the red message is the existing functionality that will give you an error message.\n- Visually highlights each missing field using a flashing effect to guide the user.\n\nThis approach improves user experience by clearly indicating which fields require attention.\n\n**Example configuration**\n<img width=\"1122\" height=\"638\" alt=\"image\" src=\"https://github.com/user-attachments/assets/31f86ae7-27fe-4921-8d8b-391eaa55304d\" />\n\n**Example execution**\n<img width=\"1029\" height=\"495\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b8384507-4292-41f4-9155-9be10195493e\" />\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Check all mandatory fields using mandatoryCheck()/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n   if (isLoading || newValue === '') {\n      return;\n   }\n\n    // Check if all mandatory fields are filled\n    if (!g_form.mandatoryCheck()) {\n        //if not get all missing fields\n        var missing = g_form.getMissingFields();\n        //Info message displaying the fields that are missing\n        g_form.addInfoMessage(\"Please complete the following mandatory fields: \" + missing.join(\", \"));\n        //go through missing fields and flash them\n        missing.forEach(function (fieldName) {\n            g_form.flash(fieldName, \"#FFFACD\",0); // Flash to draw attention\n        });\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Client Validation of Attachments by File Type and Count/README.md",
    "content": "This ServiceNow client script is designed to validate file attachments on a form before submission. It's most likely used as a \"Client Script\" or \"Client Script in a Catalog Item\" that runs in the browser when a user tries to submit a form.\n\nThis client script runs when a form is submitted in ServiceNow. It checks if the user has:\n\nAttached at least one file (shows an error if none).\nAttached no more than three files (shows an error if more).\nOnly uploaded files of type PDF or PNG (shows an error for other types).\nIf any of these checks fail, the form submission is blocked and an appropriate error message is displayed.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Client Validation of Attachments by File Type and Count/code.js",
    "content": "(function executeRule(gForm, gUser, gSNC) {\n    var attachments = gForm.getAttachments();\n    if (!attachments || attachments.length === 0) {\n        gForm.addErrorMessage(\"You must attach at least one file.\");\n        return false;\n    }\n\n    if (attachments.length > 3) {\n        gForm.addErrorMessage(\"You can only upload up to 3 files.\");\n        return false;\n    }\n\n    var allowedTypes = ['pdf', 'png'];\n    for (var i = 0; i < attachments.length; i++) {\n        var fileName = attachments[i].file_name.toLowerCase();\n        var ext = fileName.split('.').pop();\n        if (!allowedTypes.includes(ext)) {\n            gForm.addErrorMessage(\"Only PDF and PNG files are allowed.\");\n            return false;\n        }\n    }\n\n    return true;\n})(gForm, gUser, gSNC);\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Client script using getMessage() function without filling Messages field/README.md",
    "content": "# Client scripts using getMessage() function without filling Messages field\n\n**Problem scenario :**\nDevelopers while writing a client script uses getMessage() function but there are no messages preloaded in the 'Messages' field in client script form\n\n**Solution :**\nWrite an onSubmit client script to check the above scenario and prevent client script submission with an error message on top of the form.\n\nCheck the file `script.js` file for example.\n\nNote : As a best practice for this scenario use 'Checks' in the'Instance scan' feature of serviceNow. If Instance scan is not configured in your instance yet or if you want to avoid the problem during development itself then use this client script.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Client script using getMessage() function without filling Messages field/script.js",
    "content": "function onSubmit() {\n   \n\tvar messages = g_form.getValue('messages');\n\tvar script = g_form.getValue('script');\n\tvar count = 0;\n\tif(messages == '' && script.includes('getMessage'))\n\t\t{\n\t\t\tg_form.addErrorMessage(\"Please add any message in the Messages field before using getMessage() function\");\n\t\t\treturn false;\n\t\t}\n   \n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Color-coded Priority field for improved UX/README.md",
    "content": "# Field Color-Coding Based on Choice Values\n\n## Purpose\nDynamically change the background color of any choice field on a form based on the selected backend value.\n\n## How to Use\n1. Create an OnChange client script on the desired choice field.\n2. Replace `'your_field_name'` in the script with your actual field name.\n3. Update the `colorMap` with relevant backend choice values and colors.\n4. Save and test on the form.\n\n## Key Points\n- Works with any choice field\n- Uses backend values of choices for mapping colors.\n\n## Demo\n\n<img width=\"1710\" height=\"557\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9fb9e68a-1ade-4eb5-81cc-c947c970bd6f\" />\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Color-coded Priority field for improved UX/setColor.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n\n  var colorMap = {\n    '1': '#e74c3c',  // Critical - strong red\n    '2': '#e67e22',  // High - bright orange\n    '3': '#f1c40f',  // Moderate - yellow\n    '4': '#3498db',  // Low - blue\n    '5': '#27ae60'   // Planning - green\n  };\n\n  var priorityField = g_form.getControl('priority');\n  if (!priorityField) return;\n\n  priorityField.style.backgroundColor = colorMap[newValue] || '';\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Conditional Auto-Routing and Dynamic Mandatory Fields/Conditional_AutoRouting_Dynamic_Mandatory_Fields.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading) return;\n\n    if (newValue === 'hardware') {\n        g_form.setMandatory('asset_tag', true);\n        g_form.setDisplay('asset_tag', true);\n        g_form.setValue('assignment_group', 'Hardware Support Group');\n    } else if (newValue === 'software') {\n        g_form.setMandatory('asset_tag', false);\n        g_form.setDisplay('asset_tag', false);\n        g_form.setValue('assignment_group', 'Software Support Group');\n    } else {\n        g_form.setMandatory('asset_tag', false);\n        g_form.setDisplay('asset_tag', true);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Conditional Auto-Routing and Dynamic Mandatory Fields/Readme.md",
    "content": "If an Incident Category = Hardware, make Asset Tag mandatory and automatically assign to Hardware Support Group.\nIf Software, assign to Software Support Group and hide Asset Tag.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Conditional Form Section based on Role/README.md",
    "content": "# Conditional Form Sections Based on User Role\n\n## Overview\nThis client script dynamically shows or hides specific sections of a form based on the logged-in user’s role. It ensures that only authorized users, such as managers, can view and interact with sensitive sections (e.g., budget approvals).\n\n## Use Case\n- Managers: Can see the Budget Approval section.\n- Other Users: The section remains hidden.\n\n## How It Works\n- The script runs onLoad of the form.\n- It checks if the logged-in user has the specified role (manager in this example).\n- If the user has the role, the Budget Approval section is shown. If not, it remains hidden.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Conditional Form Section based on Role/script.js",
    "content": "function onLoad() {\n  var userHasRole = g_user.hasRole('case_manager'); // Check if user has specific role\n\n  if (userHasRole) {\n    g_form.setSectionDisplay('budget_approval', true);  // Show section if user has specific role\n  } else {\n    g_form.setSectionDisplay('budget_approval', false);\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Control Form Behaviour from Reference Lookup/README.md",
    "content": "# Use Case\n\nClicking the lookup icon on a reference field opens the list view for the referenced table. On clicking the 'New' button to create new records on the reference table, 'Default' form view for that table is displayed. There might be a requirement to control the form's behaviour when the new record form is opened from a designated field on a specific table.\n\n\n# Usage\n\nWrite a client script/scripted UI policy on the reference table and add the code in ```script.js``` file.\n\n\n# Explanation\n\nThe URL parameters contains the necessary information about the originating table and field from where the lookup icon is clicked. These parameters can be extracted using the client-side class ```GlideURL```. Key parameters of interest here:\n  - ```sysparm_nameofstack: \"reflist\"``` ==> Will always be reflist when form has originated from a reference lookup icon click\n  - ```sysparm_target: \"change_request.cmdb_ci\"``` ==> Will be in the format <table_name>.<field_name>\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Control Form Behaviour from Reference Lookup/script.js",
    "content": "\nvar url = new GlideURL();\nurl.setFromCurrent();\nvar source = url.getParam('sysparm_nameofstack'); // Always 'reflist' when opening from Reference Lookup Icon\nvar field = url.getParam('sysparm_target'); //Dot-walked path to the field (Example: cmdb_ci field on change_request form - change_request.cmdb_ci)\n\nif (source === 'reflist' && field === 'change_request.cmdb_ci') {\n\t// Add form control logic here\n\tg_form.setMandatory('name', true);\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Count Assigned To Field/README.md",
    "content": "Count Assigned To Field\n\n1. Write a Client Script name as getAssignedToCount\n2. Glide the Incident Table\n3. Use onChange Client Script\n4. Use the Field name as \"assigned_to\" field\n5. Glide the Script Include using \"GlideAjax\".\n6. Call the function \"getCount\" from Script Include\n7. Add the parameter for the newValue.\n8. Use the getXML for asynchronous response.\n9. Get the answer using the callback function\n10. Use the logic for the more than how many tickets that error needs to populate\n11. Use the addErrorMessage for marking the error message\n12. Use the setValue for the \"assigned_to\" field.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Count Assigned To Field/getAssignedToCount.js",
    "content": "function onChange(control,oldValue,newValue,isLoading,isTemplate) {\n  if(isLoading || newValue === '') {\n    return;\n  }\n\n  var ga = new GlideAjax('countAssignedUtil');\n  ga.addParam('sysparm','getCount');\n  ga.addParam('sysparm_assignedto', newValue);\n  ga.getXML(callback);\n\n  function callback(response){\n    var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n    if(answer >=5){\n      g_form.addErrorMessage(\"Please select another person to work on this Incident, selected user is already having 5 tickets in his/her Queue\");\n      g_form.setValue(\"assigned_to\", \"\");\n    }\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Date Range Validation (Within 30 Days)/README.md",
    "content": "Date Range Validation (Within 30 Days) in Client Side\n\nThis ServiceNow client script provides real-time date validation for form fields, ensuring users can only select dates within a specific 30-day window from today's date. The script runs automatically when a user changes a date field value, providing immediate feedback and preventing invalid date submissions.\n\nThe script validates that any date entered in a form field meets these criteria:\nMinimum Date: Today's date (no past dates allowed)\nMaximum Date: 30 days from today's date\nReal-time Validation: Instant feedback as users type or select dates\nUser-friendly Errors: Clear error messages explaining the valid date range\nAutomatic Field Clearing: Invalid dates are automatically cleared to prevent submission\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Date Range Validation (Within 30 Days)/dateRangeValidation.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n    \n    var todayDate = new Date();\n    var futureDate = new Date();\n    futureDate.setDate(futureDate.getDate() + 30);\n    \n    var todayDateStr = formatDate(todayDate, g_user_date_format);\n    var futureDateStr = formatDate(futureDate, g_user_date_format);\n    \n    var selectedDateNum = getDateFromFormat(newValue, g_user_date_format);\n    var todayDateNum = getDateFromFormat(todayDateStr, g_user_date_format);\n    var futureDateNum = getDateFromFormat(futureDateStr, g_user_date_format);\n    \n    if (selectedDateNum < todayDateNum || selectedDateNum > futureDateNum) {\n        g_form.showFieldMsg(control, 'Date must be between today and 30 days from today', 'error');\n        g_form.clearValue(control);\n    } else {\n        g_form.hideFieldMsg(control);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Detect oldValue newValue and Operation in Glide List Type Fields/detectOldValuenewValueOperation.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading) {\n        return;\n    }\n\n    var prevValue;\n    if (g_scratchpad.prevValue == undefined)\n        prevValue = oldValue;\n    else {\n        prevValue = g_scratchpad.prevValue;\n    }\n    g_scratchpad.prevValue = newValue;\n\n\n    var oldGlideValue = prevValue.split(',');\n    var newGlideValue = newValue.split(',');\n\n    var operation;\n\n    if (oldGlideValue.length > newGlideValue.length || newValue == '') {\n        operation = 'remove';\n    } else if (oldGlideValue.length < newGlideValue.length || oldGlideValue.length == newGlideValue.length) {\n        operation = 'add';\n    } else {\n        operation = '';\n    }\n\n    var ajaxGetNames = new GlideAjax('watchListCandidatesUtil');\n    ajaxGetNames.addParam('sysparm_name', 'getWatchListUsers');\n    ajaxGetNames.addParam('sysparm_old_values', oldGlideValue);\n    ajaxGetNames.addParam('sysparm_new_values', newGlideValue);\n    ajaxGetNames.getXMLAnswer(function(response) {\n\n        var result = JSON.parse(response);\n\n        g_form.clearMessages();\n        g_form.addSuccessMessage('Operation Performed : ' + operation);\n        g_form.addSuccessMessage('OldValue : ' + result.oldU);\n        g_form.addSuccessMessage('NewValue : ' + result.newU);\n\n    });\n\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Detect oldValue newValue and Operation in Glide List Type Fields/readme.md",
    "content": "In Client Scripts, oldValue will display the value of last value/record which is stored in that field. \nFor new records, it is generally empty and for existing records it displays the value which is stored after load.\nIf we will try to change the value in that field, it will still show oldValue the same value which was there during the load of form.\n\nSo, In order to identify the oldValue on change(as it does in business rule(previous)), This script comes handy and also it will\ndetect the action performed.\n\n\nThis onChange Client script comes handy when dealing with Glide List type fields where we have to detect whether the value was added or removed and returns the display name of users who were added/removed along with name of operation performed.\n\nSetup details:\n\nonChange client Script on [incident] table\nField Name: [watch_list]\nScript: Check [detectOldValuenewValueOperation.js] file\nScript Include Name: watchListCandidatesUtil (client callable/GlideAjax Enabled - true), Check [watchListCandidatesUtil.js] file for script\n\n\n\nOutput:\n\nCurrently there is no one in the watchlist field:\n\n<img width=\"873\" height=\"298\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a46ca53a-f031-4bf9-9f85-2056c408b66b\" />\n\n\nAdding [Bryan Rovell], It shows the operation was addition, oldValue as 'No record found' as there was no value earlier.New Value shows the name of the user (Bryan Rovell)\n<img width=\"870\" height=\"443\" alt=\"image\" src=\"https://github.com/user-attachments/assets/484284b6-846e-424c-b9c8-a53278f48c72\" />\n\n\nAdding 2 users one by one:\n\n<img width=\"987\" height=\"484\" alt=\"image\" src=\"https://github.com/user-attachments/assets/35dfe96a-c932-4f95-9c8e-bdb48b1c7b5f\" />\n\n\nRemoving 2 at once:\n\n<img width=\"879\" height=\"496\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c83d4e01-f150-44cb-9078-9841072ec949\" />\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Detect oldValue newValue and Operation in Glide List Type Fields/watchListCandidatesUtil.js",
    "content": "var watchListCandidatesUtil = Class.create();\nwatchListCandidatesUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\n    getWatchListUsers: function() {\n\n        var oldUsers = this.getParameter('sysparm_old_values');\n        var newUsers = this.getParameter('sysparm_new_values');\n\n        var result = {\n            oldU: this._getUserNames(oldUsers),\n            newU: this._getUserNames(newUsers)\n        };\n\n        return JSON.stringify(result);\n    },\n\n    \n    _getUserNames: function(userList) {\n        var names = [];\n\n        var grUserTab = new GlideRecord('sys_user');\n        grUserTab.addQuery('sys_id', 'IN', userList);\n        grUserTab.query();\n        if (grUserTab.hasNext()) {\n            while (grUserTab.next()) {\n                names.push(grUserTab.getDisplayValue('name'));\n            }\n            return names.toString();\n        } else {\n            return 'No record found';\n        }\n    },\n\n\n    type: 'watchListCandidatesUtil'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Custom Field Based on Incident Channel Field and populate with Caller Information/GlideAjaxCallerInfo.js",
    "content": "var CallerInfoHelper = Class.create();\nCallerInfoHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getCallerInfo: function() {\n        var callerSysId = this.getParameter('sysparm_caller');\n        if (!callerSysId)\n            return JSON.stringify({ email: '', mobile: '' });\n\n        var userGR = new GlideRecord('sys_user');\n        if (!userGR.get(callerSysId))\n            return JSON.stringify({ email: '', mobile: '' });\n\n        var userObj = {\n            email: userGR.email.toString(),\n            mobile: userGR.mobile_phone.toString()\n        };\n\n        return JSON.stringify(userObj);\n    },\n\n    type: 'CallerInfoHelper'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Custom Field Based on Incident Channel Field and populate with Caller Information/README.md",
    "content": "# Display Custom Email/Phone Field Based on Incident Channel Field and Populate those Field with Caller Information\n\nDisplays either the **Email** or **Phone** field on the **Incident** form based on the selected **Channel** value (Email or Phone) and populate the fields with the caller’s details.\n\n### Use Case\n- When **Channel = Email**, the **Email** field becomes visible and is auto-populated with the caller’s email address  \n- When **Channel = Phone**, the **Phone** field becomes visible and is auto-populated with the caller’s mobile number  \n- Both details fetched from the caller’s record from **sys_user** table.  \n- The custom Email and Phone fields may also serve as placeholder to update if details differ from the caller record\n\n### Prerequisites\n- Create Two custom fields on Incident Table\n  - **u_email** which captures store the caller’s email address\n  -  **u_phone** which capture caller’s mobile number\n- Create **Two UI Policies** which hides the u_email and u_phone field unless channel choice is phone or email\n- Create an onChange Client Script that calls a GlideAjax Script to fetch the caller’s contact details and populate the custom Email or Phone field on the Incident form\n- To further enhance usecase Regex used on Phone field. Refer (https://github.com/ServiceNowDevProgram/code-snippets/pull/2375)\n  \n---\n\n### Incident Record when channel choice is other than Email or Phone \n\n![Display_CustomField_Autopopulate_Caller_3](Display_CustomField_Autopopulate_Caller_3.png)\n\n---\n\n### Incident Record when Channel choice is email and populate Email Field by caller's Email\n\n![Display_CustomField_Autopopulate_Caller_1](Display_CustomField_Autopopulate_Caller_1.png)\n\n---\n\n### Incident Record when channel choice is phone and populate Phone Field by caller's Phone Number\n\n![Display_CustomField_Autopopulate_Caller_2](Display_CustomField_Autopopulate_Caller_2.png)\n\n---\n\n\n\n\n\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Custom Field Based on Incident Channel Field and populate with Caller Information/clientScriptOnChangeCaller.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue === '') return;\n\n    var ga = new GlideAjax('CallerInfoHelper');\n    ga.addParam('sysparm_name', 'getCallerInfo');\n    ga.addParam('sysparm_caller', newValue);\n\n    ga.getXMLAnswer(function(answer) {\n        // Confirm what you’re actually receiving\n        console.log(\"GlideAjax raw answer:\", answer);\n\n        if (!answer) return;\n\n        var info;\n        try {\n            info = JSON.parse(answer);\n        } catch (e) {\n            console.log(\"Error parsing JSON:\", e);\n            return;\n        }\n\n        g_form.setValue('u_email', info.email || '');\n        g_form.setValue('u_phone', info.mobile || '');\n\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Incident Count of Assigned-To User When Field Changes/README.md",
    "content": "## Display Info Message of Incident Count of Assigned-To User When Field Assigned-To Changes\n\nDisplays a message showing the count of **open incidents** assigned to a user whenever the **Assigned To** field changes on the Incident form.  \n\n- Helps assess the assignee’s **current workload** by fetching and displaying active incident counts (excluding *Resolved*, *Closed*, and *Canceled* states)\n- Shows an **info message** with the count of the assignee's assigned incidents\n- Uses an **onChange Client Script** on the **Assigned To** field and a **GlideAjax Script Include** called from the client script to fetch the count dynamically\n\n---\n\n### Info Message Example 1  \n![Incident_Count_message_1](Incident_Count_message_1.png)\n\n### Info Message Example 2  \n![Incident_Count_message_2](Incident_Count_message_2.png)\n\n---\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Incident Count of Assigned-To User When Field Changes/clientScript.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') return;\n\n    // Clear any previous messages\n    g_form.clearMessages();\n\n    // Create GlideAjax object to call the Script Include\n    var ga = new GlideAjax('IncidentAssignmentCheck');\n    ga.addParam('sysparm_name', 'getIncidentCount');\n    ga.addParam('sysparm_user', newValue);\n\n    \n    ga.getXMLAnswer(function(response) {\n        var count = parseInt(response, 10);\n\n        \n        if (isNaN(count)) {\n            g_form.addErrorMessage(\"Could not retrieve open incident count.\");\n            return;\n        }\n\n        var userName = g_form.getDisplayValue('assigned_to');\n        var msg = userName + \" currently has \" + count + \" incidents assigned \";\n\n        if (count >= 5) {\n            g_form.addInfoMessage(msg + \" Please review workload before assigning more incidents\");\n        } else {\n            g_form.addInfoMessage(msg);\n        }\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Incident Count of Assigned-To User When Field Changes/glideAjaxIncidentCount.js",
    "content": "var IncidentAssignmentCheck = Class.create();\nIncidentAssignmentCheck.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getIncidentCount: function() {\n        var user = this.getParameter('sysparm_user');\n        var count = 0;\n\n        if (user) {\n            var gr = new GlideAggregate('incident');\n            gr.addQuery('assigned_to', user);\n            gr.addQuery('state', 'NOT IN', '6,7,8');\n            gr.addAggregate('COUNT');a\n            gr.query();\n\n            if (gr.next()) {\n                count = gr.getAggregate('COUNT');\n            }\n        }\n        return count;\n    },\n\n    type: 'IncidentAssignmentCheck'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Section on State/README.md",
    "content": "# Display Section Based on State\n\n## Overview\nThis client script dynamically displays specific sections on a form based on the selected State field value. It improves user experience by hiding or showing relevant sections as needed.\n\n## How It Works\n- When the State field changes, the script checks the new value.\n- For Example, if the state is changed to \"Resolved\", the Resolution Section becomes visible.\n- For other states, the section remains hidden.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display Section on State/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n   if (isLoading || newValue === '') {\n      return;\n   }\n\n   if (newValue == 6)  //State on which section needs to be displayed\n\t\t g_form.setSectionDisplay('resolution_information', true);  //Section which needs to be display\n\telse\n\t\tg_form.setSectionDisplay('resolution_information', false);   \n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display a Live Word Count for Description Field/README.md",
    "content": "The script enhances a form field (specifically the description field) by:\n-Adding a live word counter below the field.\n-Visually warning the user if the word count exceeds 150 words.\n\nThis client-side script, intended for use in a ServiceNow form (e.g., catalog item or incident form), dynamically appends a custom `<div>` element below the `description` field to display a real-time word count. It leverages the `g_form.getControl()` API to access the field's DOM element and attaches an `input` event listener to monitor user input. The script calculates the word count by splitting the input text using a regular expression (`\\s+`) and updates the counter accordingly. It applies conditional styling to the counter (`green` if ≤150 words, `red` if >150), providing immediate visual feedback to the user to enforce input constraints.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Display a Live Word Count for Description Field/code.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === oldValue) {\n        return;\n    }\n\n    var wordCount = newValue.trim().split(/\\s+/).length;\n    var message = 'Word Count: ' + (newValue ? wordCount : 0);\n    var messageType = (wordCount > 150) ? 'error' : 'info';\n\n    g_form.showFieldMsg('description', message, messageType);\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Field Dependencies with GlideAjax/README.md",
    "content": "# Dynamic Field Dependencies with GlideAjax\n\nThis folder contains advanced Client Script examples demonstrating real-time field dependencies using GlideAjax for server-side data retrieval, cascading dropdowns, and dynamic form behavior.\n\n## Overview\n\nModern ServiceNow forms often require dynamic behavior based on user selections. This example demonstrates:\n- **Real-time field updates** using GlideAjax for server-side queries\n- **Cascading dropdown menus** that filter based on parent selections\n- **Conditional field visibility** based on complex business logic\n- **Debouncing** to prevent excessive server calls\n- **Loading indicators** for better user experience\n- **Error handling** for failed AJAX calls\n- **Performance optimization** with caching and batching\n\n## Script Descriptions\n\n- **dynamic_category_subcategory.js**: Client Script implementing cascading category/subcategory dropdowns with real-time filtering.\n- **conditional_field_loader.js**: Advanced example showing conditional field loading based on multiple dependencies.\n- **ajax_script_include.js**: Server-side Script Include (Client Callable) that provides data for the client scripts.\n- **debounced_field_validator.js**: Real-time field validation with debouncing to reduce server load.\n\n## Key Features\n\n### 1. GlideAjax Communication\nEfficient client-server communication:\n```javascript\nvar ga = new GlideAjax('MyScriptInclude');\nga.addParam('sysparm_name', 'getSubcategories');\nga.addParam('sysparm_category', categoryValue);\nga.getXMLAnswer(callback);\n```\n\n### 2. Cascading Dropdowns\nDynamic filtering of child fields:\n- Parent field change triggers child field update\n- Child options filtered based on parent selection\n- Multiple levels of cascading supported\n- Maintains previously selected values when possible\n\n### 3. Debouncing\nPrevents excessive server calls:\n- Waits for user to stop typing before making request\n- Configurable delay (typically 300-500ms)\n- Cancels pending requests when new input received\n- Improves performance and user experience\n\n### 4. Loading Indicators\nVisual feedback during AJAX calls:\n- Shows loading spinner or message\n- Disables fields during data fetch\n- Clears loading state on completion or error\n- Prevents duplicate submissions\n\n## Use Cases\n\n- **Category/Subcategory Selection**: Filter subcategories based on selected category\n- **Location-Based Fields**: Update city/state/country fields dynamically\n- **Product Configuration**: Show/hide fields based on product type\n- **Assignment Rules**: Dynamically populate assignment groups based on category\n- **Cost Estimation**: Calculate and display costs based on selections\n- **Availability Checking**: Real-time validation of resource availability\n- **Dynamic Pricing**: Update pricing fields based on quantity/options\n\n## Implementation Requirements\n\n### Client Script Configuration\n- **Type**: onChange (for field changes) or onLoad (for initial setup)\n- **Table**: Target table (e.g., incident, sc_req_item)\n- **Field**: Trigger field (e.g., category)\n- **Active**: true\n- **Global**: false (table-specific for better performance)\n\n### Script Include Configuration\n- **Name**: Descriptive name (e.g., CategoryAjaxUtils)\n- **Client callable**: true (REQUIRED for GlideAjax)\n- **Active**: true\n- **Access**: public or specific roles\n\n### Required Fields\nEnsure dependent fields exist on the form:\n- Add fields to form layout\n- Configure field properties (mandatory, read-only, etc.)\n- Set up choice lists for dropdown fields\n\n## Performance Considerations\n\n### Optimization Techniques\n1. **Cache responses**: Store frequently accessed data client-side\n2. **Batch requests**: Combine multiple queries into single AJAX call\n3. **Minimize payload**: Return only required fields\n4. **Use indexed queries**: Ensure server-side queries use indexed fields\n5. **Debounce input**: Wait for user to finish typing\n6. **Lazy loading**: Load data only when needed\n\n### Best Practices\n- Keep Script Includes focused and single-purpose\n- Validate input parameters server-side\n- Handle errors gracefully with user-friendly messages\n- Test with large datasets to ensure performance\n- Use browser developer tools to monitor network calls\n- Implement timeout handling for slow connections\n\n## Security Considerations\n\n### Input Validation\nAlways validate parameters server-side:\n```javascript\n// BAD: No validation\nvar category = this.getParameter('sysparm_category');\nvar gr = new GlideRecord('cmdb_ci_category');\ngr.addQuery('parent', category); // SQL injection risk\n\n// GOOD: Validate and sanitize\nvar category = this.getParameter('sysparm_category');\nif (!category || !this._isValidSysId(category)) {\n    return '[]';\n}\n```\n\n### Access Control\n- Respect ACLs in Script Includes\n- Use GlideRecordSecure when appropriate\n- Don't expose sensitive data to client\n- Implement role-based filtering\n\n### XSS Prevention\n- Sanitize data before displaying\n- Use g_form.setValue() instead of innerHTML\n- Validate choice list values\n- Escape special characters\n\n## Error Handling\n\n### Client-Side\n```javascript\nga.getXMLAnswer(function(response) {\n    if (!response || response === 'error') {\n        g_form.addErrorMessage('Failed to load data. Please try again.');\n        return;\n    }\n    // Process response\n});\n```\n\n### Server-Side\n```javascript\ntry {\n    // Query logic\n} catch (ex) {\n    gs.error('Error in getSubcategories: ' + ex.message);\n    return 'error';\n}\n```\n\n## Testing Checklist\n\n- [ ] Test with empty/null values\n- [ ] Test with invalid input\n- [ ] Test with large datasets (1000+ records)\n- [ ] Test on slow network connections\n- [ ] Test concurrent user interactions\n- [ ] Test browser compatibility (Chrome, Firefox, Safari, Edge)\n- [ ] Test mobile responsiveness\n- [ ] Verify ACL enforcement\n- [ ] Check for console errors\n- [ ] Monitor network tab for performance\n\n## Browser Compatibility\n\nTested and compatible with:\n- Chrome 90+\n- Firefox 88+\n- Safari 14+\n- Edge 90+\n- ServiceNow Mobile App\n\n## Related APIs\n\n- **GlideAjax**: Client-server communication\n- **GlideForm (g_form)**: Form manipulation\n- **GlideUser (g_user)**: User context\n- **GlideRecord**: Server-side queries\n- **JSON**: Data serialization\n\n## Additional Resources\n\n- ServiceNow Client Script Best Practices\n- GlideAjax Documentation\n- Client-Side Scripting API Reference\n- Performance Optimization Guide\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Field Dependencies with GlideAjax/ajax_script_include.js",
    "content": "/**\n * CategoryAjaxUtils Script Include\n * \n * Name: CategoryAjaxUtils\n * Client callable: true (REQUIRED)\n * Active: true\n * \n * Description: Server-side Script Include providing data for dynamic field dependencies\n * Supports multiple AJAX methods for category/subcategory and related field operations\n */\n\nvar CategoryAjaxUtils = Class.create();\nCategoryAjaxUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    \n    /**\n     * Get subcategories for a given category\n     * Parameters: sysparm_category, sysparm_table\n     * Returns: JSON array of {value, label} objects\n     */\n    getSubcategories: function() {\n        var category = this.getParameter('sysparm_category');\n        var table = this.getParameter('sysparm_table') || 'incident';\n        \n        // Validate input\n        if (!category) {\n            return JSON.stringify([]);\n        }\n        \n        var subcategories = [];\n        \n        try {\n            // Query subcategory choices\n            var gr = new GlideRecord('sys_choice');\n            gr.addQuery('name', table);\n            gr.addQuery('element', 'subcategory');\n            gr.addQuery('dependent_value', category);\n            gr.addQuery('inactive', false);\n            gr.orderBy('sequence');\n            gr.orderBy('label');\n            gr.query();\n            \n            while (gr.next()) {\n                subcategories.push({\n                    value: gr.getValue('value'),\n                    label: gr.getValue('label')\n                });\n            }\n            \n            // If no dependent choices found, get all subcategories\n            if (subcategories.length === 0) {\n                gr = new GlideRecord('sys_choice');\n                gr.addQuery('name', table);\n                gr.addQuery('element', 'subcategory');\n                gr.addQuery('inactive', false);\n                gr.orderBy('sequence');\n                gr.orderBy('label');\n                gr.setLimit(100); // Limit to prevent performance issues\n                gr.query();\n                \n                while (gr.next()) {\n                    subcategories.push({\n                        value: gr.getValue('value'),\n                        label: gr.getValue('label')\n                    });\n                }\n            }\n            \n        } catch (ex) {\n            gs.error('[CategoryAjaxUtils] Error in getSubcategories: ' + ex.message);\n            return 'error';\n        }\n        \n        return JSON.stringify(subcategories);\n    },\n    \n    /**\n     * Get all dependent field data in a single call (performance optimization)\n     * Parameters: sysparm_category, sysparm_priority, sysparm_assignment_group, sysparm_table\n     * Returns: JSON object with multiple field data\n     */\n    getDependentFields: function() {\n        var category = this.getParameter('sysparm_category');\n        var priority = this.getParameter('sysparm_priority');\n        var assignmentGroup = this.getParameter('sysparm_assignment_group');\n        var table = this.getParameter('sysparm_table') || 'incident';\n        \n        var result = {\n            subcategories: [],\n            suggested_assignment_group: null,\n            sla_info: null,\n            estimated_cost: null,\n            recommendations: []\n        };\n        \n        try {\n            // Get subcategories\n            result.subcategories = this._getSubcategoriesArray(category, table);\n            \n            // Get suggested assignment group based on category\n            result.suggested_assignment_group = this._getSuggestedAssignmentGroup(category);\n            \n            // Get SLA information\n            result.sla_info = this._getSLAInfo(category, priority);\n            \n            // Get estimated cost (if applicable)\n            result.estimated_cost = this._getEstimatedCost(category, priority);\n            \n            // Get recommendations\n            result.recommendations = this._getRecommendations(category, priority);\n            \n        } catch (ex) {\n            gs.error('[CategoryAjaxUtils] Error in getDependentFields: ' + ex.message);\n            return 'error';\n        }\n        \n        return JSON.stringify(result);\n    },\n    \n    /**\n     * Validate field dependencies\n     * Parameters: sysparm_category, sysparm_subcategory\n     * Returns: JSON object with validation result\n     */\n    validateDependencies: function() {\n        var category = this.getParameter('sysparm_category');\n        var subcategory = this.getParameter('sysparm_subcategory');\n        \n        var result = {\n            valid: false,\n            message: ''\n        };\n        \n        try {\n            // Check if subcategory is valid for the category\n            var gr = new GlideRecord('sys_choice');\n            gr.addQuery('name', 'incident');\n            gr.addQuery('element', 'subcategory');\n            gr.addQuery('value', subcategory);\n            gr.addQuery('dependent_value', category);\n            gr.query();\n            \n            if (gr.hasNext()) {\n                result.valid = true;\n                result.message = 'Valid combination';\n            } else {\n                result.valid = false;\n                result.message = 'Invalid subcategory for selected category';\n            }\n            \n        } catch (ex) {\n            gs.error('[CategoryAjaxUtils] Error in validateDependencies: ' + ex.message);\n            result.valid = false;\n            result.message = 'Validation error: ' + ex.message;\n        }\n        \n        return JSON.stringify(result);\n    },\n    \n    // ========================================\n    // Private Helper Methods\n    // ========================================\n    \n    /**\n     * Get subcategories as array (internal method)\n     * @private\n     */\n    _getSubcategoriesArray: function(category, table) {\n        var subcategories = [];\n        \n        var gr = new GlideRecord('sys_choice');\n        gr.addQuery('name', table);\n        gr.addQuery('element', 'subcategory');\n        gr.addQuery('dependent_value', category);\n        gr.addQuery('inactive', false);\n        gr.orderBy('sequence');\n        gr.orderBy('label');\n        gr.query();\n        \n        while (gr.next()) {\n            subcategories.push({\n                value: gr.getValue('value'),\n                label: gr.getValue('label')\n            });\n        }\n        \n        return subcategories;\n    },\n    \n    /**\n     * Get suggested assignment group based on category\n     * @private\n     */\n    _getSuggestedAssignmentGroup: function(category) {\n        // This could be a lookup table or business rule\n        // For demonstration, using a simple mapping\n        var categoryGroupMap = {\n            'hardware': 'Hardware Support',\n            'software': 'Application Support',\n            'network': 'Network Operations',\n            'database': 'Database Team',\n            'inquiry': 'Service Desk'\n        };\n        \n        var groupName = categoryGroupMap[category];\n        if (!groupName) {\n            return null;\n        }\n        \n        // Look up the actual group sys_id\n        var gr = new GlideRecord('sys_user_group');\n        gr.addQuery('name', groupName);\n        gr.addQuery('active', true);\n        gr.query();\n        \n        if (gr.next()) {\n            return gr.getUniqueValue();\n        }\n        \n        return null;\n    },\n    \n    /**\n     * Get SLA information based on category and priority\n     * @private\n     */\n    _getSLAInfo: function(category, priority) {\n        // Simplified SLA calculation\n        // In production, this would query actual SLA definitions\n        var resolutionHours = 24; // Default\n        \n        if (priority == '1') {\n            resolutionHours = 4;\n        } else if (priority == '2') {\n            resolutionHours = 8;\n        } else if (priority == '3') {\n            resolutionHours = 24;\n        } else if (priority == '4') {\n            resolutionHours = 72;\n        }\n        \n        // Adjust based on category\n        if (category === 'hardware') {\n            resolutionHours *= 1.5; // Hardware takes longer\n        }\n        \n        return {\n            resolution_time: resolutionHours,\n            response_time: resolutionHours / 4\n        };\n    },\n    \n    /**\n     * Get estimated cost based on category and priority\n     * @private\n     */\n    _getEstimatedCost: function(category, priority) {\n        // Simplified cost estimation\n        var baseCost = 100;\n        \n        var categoryMultiplier = {\n            'hardware': 2.0,\n            'software': 1.5,\n            'network': 1.8,\n            'database': 1.7,\n            'inquiry': 0.5\n        };\n        \n        var priorityMultiplier = {\n            '1': 3.0,\n            '2': 2.0,\n            '3': 1.0,\n            '4': 0.5,\n            '5': 0.3\n        };\n        \n        var catMult = categoryMultiplier[category] || 1.0;\n        var priMult = priorityMultiplier[priority] || 1.0;\n        \n        return Math.round(baseCost * catMult * priMult);\n    },\n    \n    /**\n     * Get recommendations based on category and priority\n     * @private\n     */\n    _getRecommendations: function(category, priority) {\n        var recommendations = [];\n        \n        // Add category-specific recommendations\n        if (category === 'hardware') {\n            recommendations.push('Attach hardware diagnostic logs');\n            recommendations.push('Include serial number and model');\n        } else if (category === 'software') {\n            recommendations.push('Include application version');\n            recommendations.push('Attach error screenshots');\n        } else if (category === 'network') {\n            recommendations.push('Run network diagnostics');\n            recommendations.push('Include IP address and subnet');\n        }\n        \n        // Add priority-specific recommendations\n        if (priority == '1' || priority == '2') {\n            recommendations.push('Consider escalating to manager');\n            recommendations.push('Notify stakeholders immediately');\n        }\n        \n        return recommendations;\n    },\n    \n    /**\n     * Validate if a string is a valid sys_id\n     * @private\n     */\n    _isValidSysId: function(value) {\n        if (!value || typeof value !== 'string') {\n            return false;\n        }\n        // sys_id is 32 character hex string\n        return /^[0-9a-f]{32}$/.test(value);\n    },\n    \n    type: 'CategoryAjaxUtils'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Field Dependencies with GlideAjax/conditional_field_loader.js",
    "content": "/**\n * Advanced Conditional Field Loader with Multiple Dependencies\n * \n * Table: incident\n * Type: onChange\n * Field: category, priority, assignment_group (multiple scripts or combined)\n * \n * Description: Shows/hides and populates fields based on multiple conditions\n * Demonstrates debouncing, caching, and complex business logic\n */\n\n// Global cache object (persists across onChange calls)\nif (typeof window.fieldDependencyCache === 'undefined') {\n    window.fieldDependencyCache = {};\n    window.fieldDependencyTimers = {};\n}\n\n/**\n * Main onChange handler for category field\n */\nfunction onChangeCategoryWithDependencies(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading) {\n        return;\n    }\n    \n    var priority = g_form.getValue('priority');\n    var assignmentGroup = g_form.getValue('assignment_group');\n    \n    // Update field visibility based on category\n    updateFieldVisibility(newValue);\n    \n    // Load dependent data with debouncing\n    loadDependentFields(newValue, priority, assignmentGroup);\n}\n\n/**\n * Update field visibility based on category selection\n */\nfunction updateFieldVisibility(category) {\n    // Example: Show additional fields for specific categories\n    var showAdvancedFields = false;\n    var showHardwareFields = false;\n    \n    // Check cache first\n    var cacheKey = 'visibility_' + category;\n    if (window.fieldDependencyCache[cacheKey]) {\n        var cached = window.fieldDependencyCache[cacheKey];\n        showAdvancedFields = cached.advanced;\n        showHardwareFields = cached.hardware;\n    } else {\n        // Determine visibility (this could also be an AJAX call)\n        if (category === 'hardware' || category === 'network') {\n            showHardwareFields = true;\n        }\n        \n        if (category === 'software' || category === 'database') {\n            showAdvancedFields = true;\n        }\n        \n        // Cache the result\n        window.fieldDependencyCache[cacheKey] = {\n            advanced: showAdvancedFields,\n            hardware: showHardwareFields\n        };\n    }\n    \n    // Show/hide fields with animation\n    if (showHardwareFields) {\n        g_form.setSectionDisplay('hardware_details', true);\n        g_form.setDisplay('cmdb_ci', true);\n        g_form.setDisplay('hardware_model', true);\n        g_form.setMandatory('cmdb_ci', true);\n    } else {\n        g_form.setSectionDisplay('hardware_details', false);\n        g_form.setDisplay('cmdb_ci', false);\n        g_form.setDisplay('hardware_model', false);\n        g_form.setMandatory('cmdb_ci', false);\n        g_form.clearValue('cmdb_ci');\n        g_form.clearValue('hardware_model');\n    }\n    \n    if (showAdvancedFields) {\n        g_form.setDisplay('application', true);\n        g_form.setDisplay('version', true);\n        g_form.setMandatory('application', true);\n    } else {\n        g_form.setDisplay('application', false);\n        g_form.setDisplay('version', false);\n        g_form.setMandatory('application', false);\n        g_form.clearValue('application');\n        g_form.clearValue('version');\n    }\n}\n\n/**\n * Load dependent fields with debouncing to prevent excessive AJAX calls\n */\nfunction loadDependentFields(category, priority, assignmentGroup) {\n    // Clear existing timer\n    if (window.fieldDependencyTimers.loadFields) {\n        clearTimeout(window.fieldDependencyTimers.loadFields);\n    }\n    \n    // Set new timer (debounce for 300ms)\n    window.fieldDependencyTimers.loadFields = setTimeout(function() {\n        executeDependentFieldLoad(category, priority, assignmentGroup);\n    }, 300);\n}\n\n/**\n * Execute the actual AJAX call to load dependent data\n */\nfunction executeDependentFieldLoad(category, priority, assignmentGroup) {\n    // Check cache first\n    var cacheKey = 'fields_' + category + '_' + priority + '_' + assignmentGroup;\n    if (window.fieldDependencyCache[cacheKey]) {\n        applyDependentFieldData(window.fieldDependencyCache[cacheKey]);\n        return;\n    }\n    \n    // Show loading indicators\n    showLoadingIndicators(['subcategory', 'assignment_group', 'assigned_to']);\n    \n    // Make AJAX call\n    var ga = new GlideAjax('CategoryAjaxUtils');\n    ga.addParam('sysparm_name', 'getDependentFields');\n    ga.addParam('sysparm_category', category);\n    ga.addParam('sysparm_priority', priority);\n    ga.addParam('sysparm_assignment_group', assignmentGroup);\n    ga.addParam('sysparm_table', g_form.getTableName());\n    \n    ga.getXMLAnswer(function(response) {\n        // Hide loading indicators\n        hideLoadingIndicators(['subcategory', 'assignment_group', 'assigned_to']);\n        \n        if (!response || response === 'error') {\n            g_form.addErrorMessage('Failed to load dependent fields. Please refresh and try again.');\n            return;\n        }\n        \n        try {\n            var data = JSON.parse(response);\n            \n            // Cache the response\n            window.fieldDependencyCache[cacheKey] = data;\n            \n            // Apply the data\n            applyDependentFieldData(data);\n            \n        } catch (ex) {\n            g_form.addErrorMessage('Error processing field dependencies: ' + ex.message);\n            console.error('Field dependency error:', ex);\n        }\n    });\n}\n\n/**\n * Apply dependent field data to the form\n */\nfunction applyDependentFieldData(data) {\n    // Update subcategories\n    if (data.subcategories) {\n        g_form.clearOptions('subcategory');\n        for (var i = 0; i < data.subcategories.length; i++) {\n            var sub = data.subcategories[i];\n            g_form.addOption('subcategory', sub.value, sub.label);\n        }\n    }\n    \n    // Update suggested assignment group\n    if (data.suggested_assignment_group) {\n        g_form.setValue('assignment_group', data.suggested_assignment_group);\n        g_form.showFieldMsg('assignment_group', 'Auto-populated based on category', 'info', 3000);\n    }\n    \n    // Update SLA information\n    if (data.sla_info) {\n        var slaMsg = 'Expected resolution time: ' + data.sla_info.resolution_time + ' hours';\n        g_form.showFieldMsg('priority', slaMsg, 'info');\n    }\n    \n    // Update estimated cost (if applicable)\n    if (data.estimated_cost) {\n        g_form.setValue('estimated_cost', data.estimated_cost);\n    }\n    \n    // Show recommendations\n    if (data.recommendations && data.recommendations.length > 0) {\n        var recMsg = 'Recommendations: ' + data.recommendations.join(', ');\n        g_form.addInfoMessage(recMsg);\n    }\n}\n\n/**\n * Show loading indicators on multiple fields\n */\nfunction showLoadingIndicators(fields) {\n    for (var i = 0; i < fields.length; i++) {\n        var field = fields[i];\n        g_form.showFieldMsg(field, 'Loading...', 'info');\n        g_form.setReadOnly(field, true);\n    }\n}\n\n/**\n * Hide loading indicators on multiple fields\n */\nfunction hideLoadingIndicators(fields) {\n    for (var i = 0; i < fields.length; i++) {\n        var field = fields[i];\n        g_form.hideFieldMsg(field);\n        g_form.setReadOnly(field, false);\n    }\n}\n\n/**\n * Clear cache (useful for testing or when data changes)\n */\nfunction clearFieldDependencyCache() {\n    window.fieldDependencyCache = {};\n    console.log('Field dependency cache cleared');\n}\n\n/**\n * onLoad script to initialize form\n * Type: onLoad\n */\nfunction onLoadInitializeDependencies() {\n    // Initialize cache\n    if (typeof window.fieldDependencyCache === 'undefined') {\n        window.fieldDependencyCache = {};\n        window.fieldDependencyTimers = {};\n    }\n    \n    // Load initial dependencies if category is already set\n    var category = g_form.getValue('category');\n    if (category) {\n        updateFieldVisibility(category);\n    }\n    \n    // Add cache clear button for admins (optional)\n    if (g_user.hasRole('admin')) {\n        console.log('Field dependency cache available. Use clearFieldDependencyCache() to clear.');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Field Dependencies with GlideAjax/dynamic_category_subcategory.js",
    "content": "/**\n * Dynamic Category/Subcategory Client Script\n * \n * Table: incident (or any table with category/subcategory fields)\n * Type: onChange\n * Field: category\n * \n * Description: Dynamically populates subcategory field based on selected category\n * using GlideAjax to fetch filtered options from server\n */\n\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    // Don't run during form load or if value hasn't changed\n    if (isLoading || newValue === '') {\n        return;\n    }\n    \n    // Clear subcategory when category changes\n    g_form.clearValue('subcategory');\n    \n    // Show loading indicator\n    g_form.showFieldMsg('subcategory', 'Loading subcategories...', 'info');\n    g_form.setReadOnly('subcategory', true);\n    \n    // Make AJAX call to get filtered subcategories\n    var ga = new GlideAjax('CategoryAjaxUtils');\n    ga.addParam('sysparm_name', 'getSubcategories');\n    ga.addParam('sysparm_category', newValue);\n    ga.addParam('sysparm_table', g_form.getTableName());\n    \n    ga.getXMLAnswer(function(response) {\n        // Clear loading indicator\n        g_form.hideFieldMsg('subcategory');\n        g_form.setReadOnly('subcategory', false);\n        \n        // Handle error response\n        if (!response || response === 'error') {\n            g_form.addErrorMessage('Failed to load subcategories. Please try again.');\n            return;\n        }\n        \n        // Parse response\n        try {\n            var subcategories = JSON.parse(response);\n            \n            // Clear existing options (except empty option)\n            g_form.clearOptions('subcategory');\n            \n            // Add new options\n            if (subcategories.length === 0) {\n                g_form.addInfoMessage('No subcategories available for this category.');\n                g_form.setReadOnly('subcategory', true);\n            } else {\n                // Add each subcategory as an option\n                for (var i = 0; i < subcategories.length; i++) {\n                    var sub = subcategories[i];\n                    g_form.addOption('subcategory', sub.value, sub.label);\n                }\n                \n                // Auto-select if only one option\n                if (subcategories.length === 1) {\n                    g_form.setValue('subcategory', subcategories[0].value);\n                }\n                \n                // Show success message\n                g_form.showFieldMsg('subcategory', subcategories.length + ' subcategories loaded', 'info', 2000);\n            }\n            \n        } catch (ex) {\n            g_form.addErrorMessage('Error parsing subcategory data: ' + ex.message);\n            console.error('Subcategory parsing error:', ex);\n        }\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Location Validation Approach/Readme.md",
    "content": "**User Location Validator**\nThis script restricts form submissions based on the physical location of the user. The current location is obtained using the browser’s geolocation API (latitude and longitude), and is then compared against the user's assigned business location stored in ServiceNow.\n\n**How It Works**\n- The **server-side Script Include**(UserLocationUtils.js) fetches the assigned business location’s latitude, longitude, and name for the logged-in user.\n- The **client-side script**(User Location Validator.js) uses the browser API to obtain the current latitude and longitude of the user at form submission.\n- It calculates the distance between these two points using the **Haversine formula**, which accounts for the spherical shape of the Earth.\n- The key constant `earthRadiusKm = 6371` defines the Earth's radius in kilometers and is essential for accurate distance calculation.\n- If the user’s current location is outside the predefined radius (default 10 km), the form submission is blocked with an error message showing the distance and allowed location.\n- If the user is within range, a confirmation info message is displayed and the submission proceeds.\n\n**Sample Output**\n- **Success:** \"Location validated successfully within range of Headquarters.\"\n- **Failure:** \"You are 15.23 km away from your registered location: Headquarters.\"\n\n**Usage Notes**\n- Requires user consent for geolocation access in the browser.\n- The script uses descriptive variable names for clarity and maintainability.\n- Suitable for scenarios requiring geo-fencing compliance or location-based workflow restrictions.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Location Validation Approach/User Location Validator.js",
    "content": "function onSubmit() {\n  // Check if the browser supports geolocation\n  if (\"geolocation\" in navigator) {\n    // Request current user position\n    navigator.geolocation.getCurrentPosition(function(position) {\n      var currentLatitude = position.coords.latitude;   // Current user latitude\n      var currentLongitude = position.coords.longitude; // Current user longitude\n\n      // Allowed business location coordinates fetched from server\n      var allowedLatitude = locData.latitude;\n      var allowedLongitude = locData.longitude;\n      var locationName = locData.name;\n\n      // Earth's radius in kilometers - constant used in distance calculation formula\n      var earthRadiusKm = 6371;\n\n      // Convert degree differences to radians\n      var deltaLatitude = (currentLatitude - allowedLatitude) * Math.PI / 180;\n      var deltaLongitude = (currentLongitude - allowedLongitude) * Math.PI / 180;\n\n      // Haversine formula components\n      var a = Math.sin(deltaLatitude / 2) * Math.sin(deltaLatitude / 2) +\n              Math.cos(allowedLatitude * Math.PI / 180) *\n              Math.cos(currentLatitude * Math.PI / 180) *\n              Math.sin(deltaLongitude / 2) * Math.sin(deltaLongitude / 2);\n\n      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));\n\n      // Calculate distance in kilometers between current and allowed locations\n      var distanceKm = earthRadiusKm * c;\n\n      // Check if user's current distance exceeds tolerance (e.g., 10 km)\n      if (distanceKm > 10) {\n        alert(\"You are \" + distanceKm.toFixed(2) + \" km away from your registered location: \" + locationName);\n        g_form.addErrorMessage(\"Location validation failed: Submission outside the allowed radius.\");\n        return false; // Cancel form submission\n      } else {\n        g_form.addInfoMessage(\"Location validated successfully within range of \" + locationName);\n        return true; // Allow form submission\n      }\n    }, function(error) {\n      alert(\"Geolocation error: \" + error.message);\n      return false; // Stop submission if geolocation fails\n    });\n\n    // Prevent form submission while waiting for async geolocation result\n    return false;\n  } else {\n    g_form.addErrorMessage(\"Geolocation is not supported by your browser.\");\n    return false; // Block if geolocation API unsupported\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Location Validation Approach/UserLocationUtils.js",
    "content": "var UserLocationUtils = Class.create();\nUserLocationUtils.prototype = {\n    initialize: function() {\n\n    },\n    getUserLocationCoords: function() {\n        var user = gs.getUser();\n        var loc = user.getRecord().location;\n        if (loc) {\n            var locGR = new GlideRecord('cmn_location');\n            if (locGR.get(loc))\n                return {\n                    latitude: parseFloat(locGR.latitude),\n                    longitude: parseFloat(locGR.longitude),\n                    name: locGR.name.toString()\n                };\n        }\n        return null;\n    },\n\n    type: 'UserLocationUtils'\n};\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Reference Qualifier with Filtering/README.md",
    "content": "**Dynamic Reference Qualifier with Filtering**\nThis Client Script provides a solution for dynamically updating the available options in a Reference Field based on the value selected in another field on the same form.\n\nThis technique is essential for ensuring data quality and improving the user experience (UX). \n\nA typical use case is filtering the Assignment Group field to show only groups relevant to the selected Service, Category, or Location.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic Reference Qualifier with Filtering/reference_qual_dynamic.js",
    "content": " function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n    var controlledField = 'assignment_group'; \n    var controllingField = 'u_service';     \n    var serviceSysId = g_form.getValue(controllingField);\n    var encodedQuery = 'typeLIKEITIL^u_related_service=' + serviceSysId;\n    g_form.setQuery(controlledField, encodedQuery);\n    var currentGroupSysId = g_form.getValue(controlledField);\n    if (currentGroupSysId && oldValue !== '' && currentGroupSysId !== '') {\n        g_form.setValue(controlledField, '');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic UI Actions/README.md",
    "content": "# Dynamic Visible UI Actions\n\nThis code snippet can be used to dynamically change the visibility of an UI action via a Client Script or UI Policy.\nAs this happens on the client, it happens without the need of saving/reloading the form like you'd have to do if controlled via the UI Action's condition field (server-side). \n\nAs this uses DOM manipulation you might have to uncheck the \"Isolate Script\" field on the Client Script or UI Policy to make it work or make sure you have a system property \"glide.script.block.client.globals\" with a value of false if you're in a scoped application.\n\n**Note**: DOM manipulation should be used with caution. \n\n\n\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic UI Actions/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    // Adjust condition to your needs\n    if (newValue === \"true\") { \n        $$('button[data-action-name^=action_name').each(function(e) { // replace action_name with the action name of the UI Action\n            e.hide();\n        });\n    } else {\n        $$('button[data-action-name^=action_name').each(function(e) { // replace action_name with the action name of the UI Action\n            e.show();\n        });\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic script to make fields read only/reame.md",
    "content": "Dynamic script to make fields read only\n\nIt runs when the field value changes.On change client script\nIf the new value equals '7', it retrieves all editable fields using g_form.getEditableFields().\nThen it loops through each field and sets it to read-only using g_form.setReadOnly().\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamic script to make fields read only/script.js",
    "content": "/*\nIt runs when the field value changes.\nIf the new value equals '7', it retrieves all editable fields using g_form.getEditableFields().\nThen it loops through each field and sets it to read-only using g_form.setReadOnly().\n\n*/\n\n\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n\n    if (newValue == '7') { // update condition as required\n        var fields = g_form.getEditableFields();\n        for (var i = 0; i < fields.length; i++) {\n            g_form.setReadOnly(fields[i].getName(), true);\n        }\n  \n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamically Switch Form View Based on Field Value/README.md",
    "content": "## Dynamically Switch Form View Based on Field Value\n\nThis client script demonstrates how to **automatically switch form views** based on the value of a field.\n\n**Use case:**  \nFor example, if the **Category** field is set to *Hardware*, the form view switches to **ess**.  \nYou can extend this by updating the mapping object to support additional fields and values (e.g., *Software → itil*, *Network → support*).\n\n**Benefit:**  \nImproves user experience by guiding users to the **most relevant form view**, ensuring the right fields are shown for the right scenario.\n\n**Test:**  \n- Change the **Category** field to *Hardware* → Form view should switch to **ess**.  \n- Update mapping to add new conditions (e.g., *Software → itil*) and verify the view switches accordingly.\n\n**How to Use:**  \n1. **Modify the table name** in the `switchView` function to match your target table:\n   ```javascript\n   switchView(\"section\", \"<your_table_name>\", targetView);\n2. **Modify the view mapping**\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Dynamically Switch Form View Based on Field Value/dynamic-form-view-onchange.js",
    "content": "/**\n * dynamic-form-view-onchange.js\n *\n * Dynamically switches the form view automatically depending on the value of a specific field.\n * Example: If Category = Hardware → switch to ess view.\n * Extendable by modifying the mapping object for different fields/values.\n *\n */\n\nfunction onChange(control, oldValue, newValue, isLoading) {\n  if (isLoading || !newValue) {\n    return;\n  }\n\n  // Field value → view name mapping\n  var viewMapping = {\n    \"hardware\": \"ess\",\n    \"software\": \"itil\",\n    \"network\": \"support\"\n  };\n\n  var fieldValue = newValue.toLowerCase();\n  var targetView = viewMapping[fieldValue];\n\n  if (targetView) {\n    try {\n      // Here for example the table name is incident\n      switchView(\"section\", \"incident\", targetView);\n    } catch (e) {\n      console.error(\"View switch failed: \", e);\n    }\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Enable VIP and Senior VIP Checkboxes and read only/README.md",
    "content": "This script is to enable The VIP and Senior VIP check boxes on the user form to be visible but read-only for any user who is not a system administrator.  ths script helps to better manage the VIP users and better administration of the Platform. \nYou need to run this client script on the sys_user table and UI Type as Desktop <as per your requirement> and Type as \"On load\" and make this as Global.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Enable VIP and Senior VIP Checkboxes and read only/ScriptToEnableVIP_superVIP users",
    "content": "function onLoad() {\n   //Type appropriate comment here, and begin script below\n   var myRole= g_user.hasRole(\"admin\");\n\tif(myRole!=true){\n\t\tg_form.setReadOnly(\"vip\",true);\n\t\t\t}else{\n\t\tg_form.setReadOnly(\"vip\",false);\n\t\t\t}\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/End Date can't be before Start Date/README.md",
    "content": "This script is for an onChange client script\n\nThis is using an example where you have two date variables and need to ensure the user does not choose an end date that's before the start date\n\n1. replace 'start_date' in the script with your actual start date field name\n2. replace 'end_date' in the script with yoru actual start date field name\n3. replace showFieldMsg and showErrorBox messages with your own message, if applicable\n\nThis script works for both the standard (desktop) UI and Service Portal\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/End Date can't be before Start Date/code.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n    var sDate = g_form.getValue('start_date'); //this gets the value from your start date field\n\tif (sDate == '') { //if the start date hasn't been populated then clear the end-date and inform user to please choose a stating date first\n\t\tg_form.clearValue('end_date');\n\t\tg_form.showFieldMsg('end_date', \"Please choose a starting date first\");\n\t}\n    if (newValue < sDate) { //if the end date is before the start date, clear the field value, and inform the user that the end date cannot be before the start date\n        g_form.clearValue('end_date');\n\t\tg_form.showErrorBox('end_date', \"End date cannot be before the Start date\");\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Expanding Info Message/README.md",
    "content": "This is an expanding info message. It can even run a script when an element is clicked."
  },
  {
    "path": "Client-Side Components/Client Scripts/Expanding Info Message/script.js",
    "content": "function onLoad() {\n    var message = '';\n\n    message += 'This is an expanding info message. It can even run code! Click \"Show more\" to see!';\n    message += '<div>';\n    message += '<p><a href=\"#\" onclick=\"javascript: jQuery(this.parentNode).next().toggle(200);\">Show more</a></p>';\n    message += '<div style=\"display: none;\">';\n    message += '<ul style=\"list-style: none\">';\n    message += '<li>This is the expanded info in the message.</li>';\n    message += '<li>You can include any details you like here, including info retreived from a script like the sys_id of the current record: ' + g_form.getUniqueValue() + '</li>';\n    message += '</ul>';\n    message += '<p>You can even run a script when an element is clicked <a href=\"#\" onclick=\"javascript: alert(\\'See?\\');\">like this</a>. You just have to escape your code in the HTML.</p>';\n    message += '</div>';\n    message += '</div>';\n\n    g_form.addInfoMessage(message);\n\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Field Completion Counter/README.md",
    "content": "# Field Completion Counter\n\n## Use Case / Requirement\nDisplay a simple message showing how many fields are completed vs total fields on a form. This helps users track their progress while filling out forms.\n\n## Solution\nA simple onLoad client script that:\n- Counts filled vs empty fields\n- Shows completion status in an info message\n- Updates when fields are modified\n\n## Implementation\nAdd this as an **onLoad** client script on any form.\n\n## Notes\n- Excludes system fields and read-only fields\n- Updates in real-time as users fill fields\n- Simple and lightweight solution"
  },
  {
    "path": "Client-Side Components/Client Scripts/Field Completion Counter/script.js",
    "content": "function onLoad() {\n    // Display field completion counter\n    showFieldProgress();\n    \n    // Set up listener for field changes\n    setupProgressUpdater();\n    \n    function showFieldProgress() {\n        var allFields = g_form.getFieldNames();\n        var visibleFields = [];\n        var filledFields = 0;\n        \n        // Count visible, editable fields\n        for (var i = 0; i < allFields.length; i++) {\n            var fieldName = allFields[i];\n            \n            // Skip system fields and hidden/readonly fields\n            if (fieldName.indexOf('sys_') === 0 || \n                !g_form.isVisible(fieldName) || \n                g_form.isReadOnly(fieldName)) {\n                continue;\n            }\n            visibleFields.push(fieldName);\n            \n            // Check if field has value\n            if (g_form.getValue(fieldName)) {\n                filledFields++;\n            }\n        }\n        var totalFields = visibleFields.length;\n        var percentage = totalFields > 0 ? Math.round((filledFields / totalFields) * 100) : 0;\n        \n        g_form.addInfoMessage('Form Progress: ' + filledFields + '/' + totalFields + ' fields completed (' + percentage + '%)');\n    }\n    \n    function setupProgressUpdater() {\n        // Simple debounced update\n        var updateTimer;\n        \n        function updateProgress() {\n            clearTimeout(updateTimer);\n            updateTimer = setTimeout(function() {\n                g_form.clearMessages();\n                showFieldProgress();\n            }, 500);\n        }\n        \n        // Listen for any field change\n        var allFields = g_form.getFieldNames();\n        for (var i = 0; i < allFields.length; i++) {\n            g_form.addElementChangeListener(allFields[i], updateProgress);\n        }\n    }\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Field Placeholder/README.md",
    "content": "Hint that appears inside an input field to indicate the type of data the user should enter. \nIt is typically shown in a lighter font colour and disappears once the user starts typing. \nThis helps guide users by providing an example or context without taking up additional space on the form.\nFor example, a field placeholder in an short description input might read: \"Please give the issue details here\"\n \n![image](https://github.com/user-attachments/assets/600d8ba7-2322-4b7f-b769-41f464082341)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Field Placeholder/fieldplaceholder.js",
    "content": "function onLoad() {\n   //Type appropriate comment here, and begin script below\n   var shortDescription = g_form.getControl(\"short_description\");\n   shortDescription.placeholder = \"Please give the issue details here\";\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Field Validations/README.md",
    "content": "An `onLoad` client script that validates required fields in specific ServiceNow form views.\n\nThis ServiceNow client script provides automatic validation of required form fields when users access specific form views. The script runs immediately when a form loads and checks that critical fields are populated, displaying user-friendly error messages for any missing required information. This ensures data completeness and improves form submission success rates by catching validation issues early in the user workflow.\n\nWhat This Script Does:\nThe onLoad client script performs comprehensive field validation with these key capabilities:\nView-Specific Validation: Only triggers validation when accessing a designated form view\nMultiple Field Support: Validates multiple required fields simultaneously in a single operation\nSmart Field Detection: Uses field labels (not technical names) in error messages for better user experience\nConsolidated Error Display: Shows all missing required fields in a single, clear error message\nImmediate Feedback: Provides instant validation results as soon as the form loads\nNon-Intrusive Design: Displays informational errors without blocking form interaction\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Field Validations/fieldValidation.js",
    "content": "function onLoad(){\n  var targetViewName = 'your_target_view_name';\n    var requiredFields = ['field1', 'field2', 'field3'];\n    \n    var currentViewName = g_form.getViewName();\n    \n    if (currentViewName === targetViewName) {\n        var emptyFields = [];\n        \n        for (var i = 0; i < requiredFields.length; i++) {\n            var fieldValue = g_form.getValue(requiredFields[i]);\n            if (!fieldValue || fieldValue.trim() === '') {\n                emptyFields.push(g_form.getLabelOf(requiredFields[i]));\n            }\n        }\n        \n        if (emptyFields.length > 0) {\n            var errorMessage = \"The following required fields cannot be empty: \" + \n                             emptyFields.join(', ');\n            g_form.addErrorMessage(errorMessage);\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Form Dirty State Prevention/README.md",
    "content": "# Form Dirty State Prevention\n\n## Overview\nDetects form changes and warns users before navigating away or closing the form, preventing accidental data loss.\n\n## What It Does\n- Tracks form field changes (dirty state)\n- Warns user before leaving unsaved form\n- Allows user to cancel navigation\n- Works with all form fields\n- Prevents accidental data loss\n- Clean, reusable pattern\n\n## Use Cases\n- Complex multi-field forms\n- Long data entry forms\n- Forms with expensive operations\n- Critical data entry (financial, medical)\n- Any form where accidental exit would cause issues\n\n## Files\n- `form_dirty_state_manager.js` - Client Script to manage form state\n\n## How to Use\n\n### Step 1: Create Client Script\n1. Go to **System Definition > Scripts** (any table)\n2. Create new Client Script\n3. Set **Type** to \"onChange\"\n4. Copy code from `form_dirty_state_manager.js`\n5. Set to run on any field\n\n### Step 2: Add Navigation Handler\n1. Add to form's onLoad script:\n```javascript\n// Initialize dirty state tracking\nvar formStateManager = new FormDirtyStateManager();\n```\n\n### Step 3: Test\n1. Open form and make changes\n2. Try to navigate away without saving\n3. User sees warning dialog\n4. User can choose to stay or leave\n\n## Example Usage\n```javascript\n// Automatically tracks all field changes\n// When user tries to close/navigate:\n// \"You have unsaved changes. Do you want to leave?\"\n// - Leave (discard changes)\n// - Stay (return to form)\n```\n\n## Key Features\n- ✅ Detects any field change\n- ✅ Persistent across form interactions\n- ✅ Works with new records and updates\n- ✅ Ignores read-only fields\n- ✅ Resets after save\n- ✅ No performance impact\n\n## Output Examples\n```\nUser opens form and changes a field\n→ Form marked as \"dirty\"\n\nUser clicks close/back button\n→ Warning dialog appears: \"You have unsaved changes\"\n\nUser clicks Leave\n→ Form closes, changes discarded\n\nUser clicks Save then navigates\n→ No warning (form is clean)\n```\n\n## Customization\n```javascript\n// Customize warning message\nvar warningMessage = \"Warning: You have unsaved changes!\";\n\n// Add specific field tracking\ng_form.addOnFieldChange('priority', myCustomHandler);\n\n// Reset dirty flag after save\ng_form.save(); // Automatically triggers cleanup\n```\n\n## Requirements\n- ServiceNow instance\n- Client Script access\n- Any table form\n\n## Browser Support\n- Chrome, Firefox, Safari, Edge (all modern browsers)\n- Works with ServiceNow classic and modern UI\n\n## Related APIs\n- [g_form API](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_FormAPI.html)\n- [Client Script Events](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_ClientScriptEvents.html)\n- [Form Field Changes](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_FieldChanges.html)\n\n## Best Practices\n- Apply to important data entry forms\n- Test with real users\n- Consider accessibility for screen readers\n- Use with save shortcuts (Ctrl+S)\n- Combine with auto-save patterns\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Form Dirty State Prevention/form_dirty_state_manager.js",
    "content": "// Client Script: Form Dirty State Manager\n// Purpose: Track form changes and warn user before leaving with unsaved changes\n\nvar FormDirtyStateManager = Class.create();\nFormDirtyStateManager.prototype = {\n    initialize: function() {\n        this.isDirty = false;\n        this.setupFieldChangeListeners();\n        this.setupNavigationWarning();\n    },\n    \n    // Mark form as changed when any field is modified\n    setupFieldChangeListeners: function() {\n        var self = this;\n        g_form.addOnFieldChange('*', function(control, oldValue, newValue, isLoading) {\n            // Ignore system updates and form loads\n            if (isLoading || oldValue === newValue) {\n                return;\n            }\n            self.setDirty(true);\n        });\n    },\n    \n    // Warn user before navigating away with unsaved changes\n    setupNavigationWarning: function() {\n        var self = this;\n        \n        // Warn on form close attempt\n        window.addEventListener('beforeunload', function(e) {\n            if (self.isDirty && !g_form.isNewRecord()) {\n                e.preventDefault();\n                e.returnValue = 'You have unsaved changes. Do you want to leave?';\n                return e.returnValue;\n            }\n        });\n        \n        // Warn on GlideForm navigation\n        g_form.addOnSave(function() {\n            // Reset dirty flag after successful save\n            self.setDirty(false);\n            return true;\n        });\n    },\n    \n    setDirty: function(isDirty) {\n        this.isDirty = isDirty;\n        if (isDirty) {\n            // Optional: Show visual indicator\n            document.title = '* ' + document.title.replace(/^\\* /, '');\n            gs.info('[Form State] Unsaved changes detected');\n        }\n    },\n    \n    isDirtyState: function() {\n        return this.isDirty;\n    }\n};\n\n// Initialize on form load\nvar formDirtyState = new FormDirtyStateManager();\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Field Value From List on Client/README.md",
    "content": "# Use Case\n\nLet;s consider a scenario. Some validation logic needs to be executed on click of a list banner button. The field values to be validated are present right on the screen, so why bother writing a GlideAjax code + client-callable Script Include to call the server and run the same validation on the server. Client-side ```GlideList``` API provides the ```getCell()``` method to fetch visible cell values from the list.\n\n\n# Usage\n\nWrite a client-side code on the UI Action and add the code in ```script.js``` file.\n```g_list.getCell(String recSysId, String fieldName)```\n\n\n# Explanation\n\nValues of visible fields/cells can be extracted using the ```getCell(String recSysId, String fieldName)``` method of client-side class ```GlideList (g_list)```. Required parameters:\n  - ```String recSysId``` - SysID of the record/row whose value is to be fetched\n  - ```String fieldName``` - Name of the field whose value is to be fetched\n\n**Returns:** ```HTMLElement (HTMLTableCellElement)```, string content can be extracted using ```.innerText``` property\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Field Value From List on Client/script.js",
    "content": "/* g_list.getCell(rowSysId, <field_name>) - Accepts two parameters\n * recordSysID (to identify the row) - For example: 23d7584c977a611056e8b3e6f053af6b\n * field_name - name of the field whose value is to be fetched\n */\n\nvar recSysId = g_list.getChecked(); // 23d7584c977a611056e8b3e6f053af6b - Can be modified to pass Sys ID by other means\nvar fieldName = 'short_description'; // Modify as per requirement\n\nvar cellVal = g_list.getCell(recSysId, fieldName).innerText;\n\ng_GlideUI.addOutputMessage({\n    msg: cellVal,\n    type: 'success',\n    preventDuplicates: true\n}); // Using this fancy notification trick because g_form is not supported on list view\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Form Elements/README.MD",
    "content": "This script is a Client Script (onLoad) for ServiceNow which retrieves all field names present on the current form and displays them in an alert message.\n\nExample: Hi Sai, please find the elements of the form short_description, description, number, etc.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Form Elements/getFormElements.js",
    "content": "function onLoad() {\n    //Type appropriate comment here, and begin script below\n    var arr = [];\n    for (var i = 0; i < g_form.elements.length; i++) {\n        arr.push(g_form.elements[i].fieldName);\n    }\n    alert(\"Hi Sai, please find the form elements: \" + arr.join(\",\"));\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Logged in User Information/README.md",
    "content": "# The Glide User (g_user) is a global object available within the client side. It provides information about the logged-in  user.\n\nProperty                   Description\n\ng_user.userID              Sys ID of the currently logged-in user\ng_user.name                User's Full name\ng_user.firstName           User's First name\ng_user.lastName            User's Last name\n\n# It also has some methods available within the client side.\n\nMethod                     Description\n\ng_user.hasRole()           Determine whether the logged-in user has a specific role\ng_user.hasRoleExactly()    Do not consider the admin role while evaluating the method\ng_user.hasRoles()          You can pass two or more roles in a single method\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Logged in User Information/script.js",
    "content": "if (g_user.hasRole('admin') || g_user.hasRole('itil')) {\n    // User has at least one of the roles\n    g_form.setDisplay('internal_notes', true);\n}\n\nif (g_user.hasRole('admin') && g_user.hasRole('itil')) {\n    // User must have both roles\n    g_form.setDisplay('advanced_settings', true);\n}\n\n//Using the parameters to set a field value\ng_form.setValue('short_description', g_user.firstName);\n\ng_form.setValue('short_description', g_user.lastName);\n\ng_form.setValue('short_description', g_user.name);\n\ng_form.setValue('short_description', g_user.userID);\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get URL Parameters/README.md",
    "content": "# Get URL Parameters in Global,Scoped Application for Record & Catalog Item\n\nMany times there is a need to grab parameters from URL. This could be required at table form load or catalog item load and either in Global scope or custom scope application when redirection happened. Given script will help you in achieving this:\n\n[Click here for the script](script.js)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get URL Parameters/script.js",
    "content": "function onLoad() {\n    //Type appropriate comment here, and begin script below\n\n    if (typeof spModal != \"undefined\") { // For Service Portal\n        var url = top.location.href;\n        var value = new URLSearchParams(url).get(\"sys_id\"); //provide the parameter name\n        console.log(value);\n    } else { //For Native UI\n        var glideURL = new GlideURL();\n        glideURL.setFromCurrent();\n        var id = glideURL.getParam(\"sysparm_id\"); // provide the parameter name\n        console.log(id);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Value from URL Parameter/README.md",
    "content": "Script that can be used in Client scripts to get URL parameter value.\n\n** Isolate script should be **false**\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Get Value from URL Parameter/script.js",
    "content": "//Isolate Script should be false\nfunction onLoad() {\n\n    var getUrlParameter = function(url, parameterName) {\n        return new URLSearchParams(url).get();\n    };\n    //should not use top.location, in UI16 this will \"break out\" of the iFrame and return the \"nav_to.do?uri=xyz\" URL.\n    var winURL = window.location.href;\n    console.log(getUrlParameter(winURL, \"<parameter_name>\"));\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Health Scan Prevent Insert Update in Before BRs/README.md",
    "content": "**Client script Deatils**\n1. Table: sys_script\n2. Type: onSubmit\n3. UI Type: Desktop\n\n**Benefits**\n1. This client script will prevent admin users to do insert/update operation in  onBefore business rules.\n2. It will help to avoid HealthScan findings thus increasing healthscan score.\n3. Will prevent recursive calls.\n\nLink to ServiceNow Business Rules best Practise : https://developer.servicenow.com/dev.do#!/guide/orlando/now-platform/tpb-guide/business_rules_technical_best_practices\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Health Scan Prevent Insert Update in Before BRs/script.js",
    "content": "function onSubmit() {\n    /*\n\tThis client script will prevent insert and update operation in onBefore business rules.\n    Table: sys_script\n    Type: onSubmit\n    Ui Type: Desktop\n    */\n    var whenCond = g_form.getValue('when'); // when condition of business rule\n    var scriptVal = g_form.getValue('script'); // script value of business rule.\n\n    if (whenCond == 'before' && (scriptVal.indexOf('insert()') > -1 || scriptVal.indexOf('update()')) > -1) {\n        alert(\"As per ServiceNow best Practise insert and update should be avoided in onBefore BRs. If you still want tp proceed try deactivating client script : \" + g_form.getUniqueValue());\n        return false;\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Hide Dependent Choice field if there no dependent choices/HideDepnedentField.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    var fieldToHide = 'subcategory'; // I have taken subcategory as an example\n    if (newValue === '') {\n        g_form.setDisplay(fieldToHide, false);\n        return;\n    }\n    var ga = new GlideAjax('NumberOfDependentChoices');\n    ga.addParam('sysparm_name', 'getCountOfDependentChoices');\n    ga.addParam('sysparm_tableName', g_form.getTableName());\n    ga.addParam('sysparm_element', fieldToHide);\n    ga.addParam('sysparm_choiceName', newValue);\n    ga.getXMLAnswer(callBack);\n\n    function callBack(answer) {\n        g_form.setDisplay(fieldToHide, parseInt(answer) > 0 ? true : false);\n    }\n\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Hide Dependent Choice field if there no dependent choices/NumberOfDependentChoices.js",
    "content": "var NumberOfDependentChoices = Class.create();\nNumberOfDependentChoices.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getCountOfDependentChoices: function() {\n\t\tvar dependentChoiceCount = 0;\n\t\tvar choiceName = this.getParameter('sysparm_choiceName');\n\t\tvar tableName = this.getParameter('sysparm_tableName');\n\t\tvar element = this.getParameter('sysparm_element');\n\t\tvar choiceCountGa = new GlideAggregate('sys_choice');\n\t\tchoiceCountGa.addAggregate('COUNT');\n\t\tchoiceCountGa.addQuery('dependent_value',choiceName);\n\t\tchoiceCountGa.addQuery('inactive','false');\n\t\tchoiceCountGa.addQuery('name',tableName);\n\t\tchoiceCountGa.addQuery('element',element);\n\t\tchoiceCountGa.query();\n\t\twhile(choiceCountGa.next()){\n\t\t\tdependentChoiceCount = choiceCountGa.getAggregate('COUNT');\n\t\t}\n\t\treturn dependentChoiceCount;\n\t},\n    type: 'NumberOfDependentChoices'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Hide Dependent Choice field if there no dependent choices/README.md",
    "content": "Hide the dependent choice field when there are no available options for the selected parent choice.\n\nFor example, if a selected category on the incident form has no subcategories, then the subcategory field should be hidden.\n\nThe file NumberOfDependentChoices.js is a client callable script include file which has a method which returns number of dependent choices for a selected choice of parent choice field.\n\nHideDepnedentField.js is client script which hides the dependent choice field(ex:subcategory field on incident form) if there are no active choices to show for a selected choices of it's dependent field (example: category on incident form)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Hide Work Notes section/README.md",
    "content": "**Client Script**\n\nClient Script for hiding work notes section on incident form. In this example it is configured to hide work notes section, when incident is in state new, but you can adjust condition to fit your requirements. \n\n**Example configuration of Client Script**\n\n![Coniguration](ScreenShot_1.PNG)\n\n**Example effect of execution**\n\nWhen incident is in state NEW (Work notes section not visible):\n\n![Not Visible](ScreenShot_2.PNG)\n\nWhen incident is not in state NEW (Work notes section visible):\n\n![Visible](ScreenShot_3.PNG)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Hide Work Notes section/script.js",
    "content": "function onLoad() {\n    //Script to hide work notes section, when incident is in state NEW\n\n    //Get incident state\n    var state = g_form.getValue('state');\n\n    //Check if incident is in state NEW (value = 1)\n    if (state == 1) {\n\n        //Hide work notes section \n        g_form.setSectionDisplay('notes', false);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/How to adjust the Date format within a client script to align with the User Date format/README.md",
    "content": "# When getting a date from another table, it's usually in the format (YYYY-MM-DD). To display it in the user's preferred format on the client side, use the method below.\n\nIf date is fetched from a query(like GlideAjax), date returned from query pass that date object into \"new Date()\"\n\n# Example\n\n```\nvar user_date = formatDate(new Date(<returnDateObj>), g_user_date_format)\n\ng_form.setValue('<field_name>',user_date);\n\n```\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/How to adjust the Date format within a client script to align with the User Date format/code.js",
    "content": "var currentDateObj = new Date();\nvar currentDateUsr = formatDate(currentDateObj, g_user_date_format);\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Incident Count of Selected CI with Clickable Link to Related Incidents/README.md",
    "content": "# Incident Count of Selected Configuration Item with Info Message and Link to its Related Incident \n\nDisplays a message showing the count of open incidents associated with a selected **Configuration Item (CI)** whenever the **Configuration Item** field changes on the Incident form.\n\n- Helps quickly identify whether the selected CI has existing incident by fetching and displaying active incident counts (excluding *Resolved*, *Closed*, and *Canceled* states).  \n- Shows an **info message** with a **clickable link** that opens a filtered list of related incidents for that CI  \n- If more than five incidents are linked, a **warning message** appears suggesting Problem investigation for frequent or repeated CI issues.\n- Uses an **onChange Client Script** on the *Configuration Item* field and a **GlideAjax Script Include** called from the client script to fetch the incident count  \n\n---\n\n## Warning Message displayed on form when CI has 5 or more incidents \n\n![CI_Incident_Message_Count_1](CI_Incident_Message_Count_1.png)\n\n---\n\n## Info Message displayed on form when CI has no incidents\n\n![CI_Incident_Message_Count_2](CI_Incident_Message_Count_2.png)\n\n---\n\n## Info Message displayed on form when CI has incidents less than 5\n\n![CI_Incident_Message_Count_3](CI_Incident_Message_Count_3.png)\n\n---\n\n## Upon clicking the url link filter list opens with incidents associated with CI\n\n![CI_Incident_Message_Count_4](CI_Incident_Message_Count_4.png)\n\n---\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Incident Count of Selected CI with Clickable Link to Related Incidents/clientScriptCiIncident.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') return;\n\n    g_form.clearMessages();\n\n    // Call Script Include to count incidents by CI\n    var ga = new GlideAjax('ConfigurationIncidentCheck');\n    ga.addParam('sysparm_name', 'getIncidentCount');\n    ga.addParam('sysparm_ci', newValue);\n\n    ga.getXMLAnswer(function(response) {\n        var count = parseInt(response, 10);\n        if (isNaN(count)) {\n            g_form.addErrorMessage(\"Could not retrieve incident count for this CI.\");\n            return;\n        }\n\n        var ciName = g_form.getDisplayValue('cmdb_ci');\n        var url = '/incident_list.do?sysparm_query=cmdb_ci=' + newValue + '^stateNOT IN6,7,8';\n        var msg = 'Configuration Item <b>' + ciName + '</b> has <a target=\"_blank\" href=\"' + url + '\"><b>' + count + '</b> related incident(s)</a>.';\n\n       if (count === 0) {\n    g_form.addInfoMessage(\n        'Configuration Item <b>' + ciName + '</b> has no incidents associated with it.'\n    );\n} else {\n    if (count >= 5) {\n        g_form.addWarningMessage(\n            msg + ' consider Problem investigation.'\n        );\n    } else {\n        g_form.addInfoMessage(msg);\n    }\n}\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Incident Count of Selected CI with Clickable Link to Related Incidents/glideAjaxIncidentCiCount.js",
    "content": "var ConfigurationIncidentCheck = Class.create();\nConfigurationIncidentCheck.prototype = Object.extendsObject(AbstractAjaxProcessor, {\ngetIncidentCount: function() {\n        var ci = this.getParameter('sysparm_ci');\n        if (!ci) return 0;\n\n        var gr = new GlideAggregate('incident');\n        gr.addQuery('cmdb_ci', ci);\n        gr.addQuery('state', 'NOT IN', '6,7,8'); \n        gr.addAggregate('COUNT');\n        gr.query();\n\n        return gr.next() ? gr.getAggregate('COUNT') : 0;\n    },\n\n    type: 'ConfigurationIncidentCheck'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Live Character Counter and Validator/README.md",
    "content": "This solution dynamically provides users with real-time feedback on the length of a text input field (like short_description or a single-line text variable).\nIt immediately displays a character count beneath the field and uses visual cues to indicate when a pre-defined character limit has been reached or exceeded.\n\nThis is a vital User Experience (UX) enhancement that helps agents and users write concise, actionable information, leading to improved data quality and better integration reliability.\n\nName\tLive_Character_Counter_ShortDesc_OnLoad\t\nTable\t: Custom Table or Incident\nType\tonChange \nField : Description \nUI Type\tAll\t\nIsolate Script\tfalse\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Live Character Counter and Validator/character_counter.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    \n    var FIELD_NAME = 'short_description';\n    var MAX_CHARS = 100;\n    var currentLength = newValue.length;\n    var counterId = FIELD_NAME + '_counter_label';\n    if (typeof g_form.getControl(FIELD_NAME) !== 'undefined' && !document.getElementById(counterId)) {\n        var controlElement = g_form.getControl(FIELD_NAME);\n        var counterLabel = document.createElement('div');\n        counterLabel.setAttribute('id', counterId);\n        counterLabel.style.fontSize = '85%';\n        counterLabel.style.marginTop = '2px';\n        controlElement.parentNode.insertBefore(counterLabel, controlElement.nextSibling);\n    }\n    var counterElement = document.getElementById(counterId);\n\n    if (counterElement) {\n        var remaining = MAX_CHARS - currentLength;\n        \n      \n        counterElement.innerHTML = 'Characters remaining: ' + remaining + ' (Max: ' + MAX_CHARS + ')';\n\n        // Apply red color if the limit is exceeded\n        if (remaining < 0) {\n            counterElement.style.color = 'red';\n        } else {\n            // Revert color if back within limits\n            counterElement.style.color = 'inherit';\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/MRVS variables validations/README.md",
    "content": "Multi Row Variable Set (MRVS) variables validations which are based on variables out of MRVS\n\nfor this we need to use \"g_service_catalog\" client side API of ServiceNow\n\nExample:\nRequirement is like there is an adaptation leave form, where the user can take 30 leaves in batches not all at once. \nUsers can enter the last working date which is a normal variable in catalog item and batched leaves in MRVS where the Extended leave start and end dates will populate.\nWe are restricting here only to enter extended leave start date after the last working date. \n\n![image](https://user-images.githubusercontent.com/46869542/193416763-27fb52c9-e15b-48b5-99fd-6c146a6819c3.png)\n\n\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/MRVS variables validations/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n\n    //Type appropriate comment here, and begin script below\n    if (g_form.getValue('extended_leave_start') <= g_service_catalog.parent.getValue(\"last_working_date\")) { // extended leave start date is earlier or equal to last working date\n        g_form.clearValue('extended_leave_start');\n        g_form.showFieldMsg('extended_leave_start', \"Entended leave start can't be earlier or equal to last working day\", 'error');\n    }\n\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Major Incident Proposal/Client_Script.js",
    "content": "function onSubmit() {\n    var priority = g_form.getValue('priority');\n\n    if (priority == '1') {\n        // is already a Major Incident\n        var gaCheck = new GlideAjax('CreateMajorIncident');\n        gaCheck.addParam('sysparm_name', 'isAlreadyMajorIncident');\n        gaCheck.addParam('sysparm_sysid', g_form.getUniqueValue());\n\n        var response = gaCheck.getXMLWait();\n        var isMajorIncident = response.documentElement.getAttribute('answer'); \n        \n        // not yet a Major Incident\n        if (isMajorIncident == 'false') {\n            var resp = confirm(getMessage('Please confirm that you would like to propose a Major Incident Candidate?'));\n\n            if (resp) {\n                // propose the Major Incident\n                var ga = new GlideAjax('CreateMajorIncident');\n                ga.addParam('sysparm_name', 'majorIncCreate');\n                ga.addParam('sysparm_sysid', g_form.getUniqueValue());\n\n                var majorIncidentResponse = ga.getXMLWait(); \n                var incidentNumber = majorIncidentResponse.documentElement.getAttribute('answer');\n\n                alert(\"Incident \" + incidentNumber + \" has been proposed as a Major Incident candidate.\");\n            } else {\n                // cancels\n                return false;\n            }\n        }\n    }\n\n    return true; // Allow if priority is not 1 or after processing\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Major Incident Proposal/README.md",
    "content": "<h1>Major Incident Proposal - Client Script & Script Include</h1>\n\n<p>This solution asks the users to propose an incident as a <strong>Major Incident</strong> candidate. The process is initiated from the <strong>Incident</strong> form when the priority is set to <strong>1 (Critical)</strong>, and the system checks if the incident has already been proposed as a Major Incident. If not, the user is prompted to confirm whether they wish to propose the incident. Upon confirmation, the incident is updated, and the <strong>Major Incident</strong> status is assigned.</p>\n\n<p>This solution consists of a <strong>Client Script</strong> and a <strong>Script Include</strong>, which handle the user interaction and the back-end logic, respectively.</p>\n\n<h2>Functionality</h2>\n\n<h3>1. Client Script - <code>onSubmit()</code></h3>\n<p>The <strong>Client Script</strong> is triggered when the user submits an Incident form with a priority of <strong>1 (Critical)</strong>. It performs the following actions:</p>\n<ul>\n    <li>Checks if the incident is already marked as a Major Incident by calling the Script Include via <strong>GlideAjax</strong>.</li>\n    <li>If the incident is not already marked as a Major Incident, the user is prompted to confirm whether they wish to propose it as a Major Incident.</li>\n    <li>Upon confirmation, the incident is proposed as a Major Incident candidate, and a success message is displayed with the incident number.</li>\n</ul>\n\n<h4>Key Features:</h4>\n<ul>\n    <li><strong>Priority Check</strong>: The script only runs when the priority is set to 1 (Critical).</li>\n    <li><strong>GlideAjax Call</strong>: Uses <strong>GlideAjax</strong> to communicate with the server-side <strong>Script Include</strong> to check and propose the Major Incident.</li>\n    <li><strong>User Confirmation</strong>: The user is prompted to confirm the proposal of the Major Incident before proceeding.</li>\n    <li><strong>Synchronous Execution</strong>: The script waits for a response from the server using <code>getXMLWait()</code> to ensure the process completes before submitting the form.</li>\n</ul>\n\n\n\n<h3>2. Script Include - <code>CreateMajorIncident</code></h3>\n<p>The <strong>Script Include</strong> provides the back-end logic for:</p>\n<ul>\n    <li>Checking whether the incident is already proposed as a Major Incident.</li>\n    <li>Proposing the incident as a Major Incident by updating its <code>major_incident_state</code>, <code>proposed_by</code>, and <code>proposed_on</code> fields, and adding work notes.</li>\n</ul>\n\n\n\n<h3>Usage Example:</h3>\n<ol>\n    <li>When the priority of an incident is set to <strong>1 (Critical)</strong>, the client script checks whether the incident is already a Major Incident.</li>\n    <li>If not, the user is prompted to confirm the Major Incident proposal.</li>\n    <li>Upon confirmation, the <code>CreateMajorIncident</code> Script Include updates the incident record to reflect its <strong>proposed</strong> Major Incident status and returns the incident number.</li>\n</ol>\n\n<h2>Customization</h2>\n<p>You can easily customize this functionality by:</p>\n<ul>\n    <li>Adding more validation rules to the Script Include.</li>\n    <li>Modifying the client script to handle different priorities or additional fields.</li>\n    <li>Updating the work notes or other fields when proposing the incident.</li>\n</ul>\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Major Incident Proposal/Script_Include.js",
    "content": "var CreateMajorIncident = Class.create();\nCreateMajorIncident.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    majorIncCreate: function() {\n\n        var incSysId = this.getParameter('sysparm_sysid');\n\n        var ginc = new GlideRecord('incident');\n\n        if (ginc.get(incSysId)) {\n\n            ginc.major_incident_state = 'proposed';\n            ginc.proposed_by = gs.getUserID();\n            ginc.proposed_on = new GlideDateTime();\n            ginc.work_notes = \"Hello World! \" + new GlideDateTime();\n            ginc.update();\n\n            return ginc.number.toString();\n        }\n\n        return 'false';\n    },\n\n\n    isAlreadyMajorIncident: function() {\n        var incSysId = this.getParameter('sysparm_sysid');\n        var ginc = new GlideRecord('incident');\n\n        if (ginc.get(incSysId)) {\n            return ginc.major_incident_state == 'proposed' ? 'true' : 'false';\n        }\n\n        return 'false';\n    },\n\n    type: 'CreateMajorIncident'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make Variable Editor Read Only for Catalog Items containing MRVS/CheckMRVSDetails.js",
    "content": "var CheckMRVSDetails = Class.create();\nCheckMRVSDetails.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\tcheckMRVS: function(){\n\t\tvar itemID = this.getParameter('sysparm_itemID');\n\t\tvar grMultiRow = new GlideRecord('io_set_item');\n\t\tgrMultiRow.addEncodedQuery('variable_set.type=one_to_many'); // Variable Set is MRVS\n\t\tgrMultiRow.addQuery('sc_cat_item',itemID);\n\t\tgrMultiRow.setLimit(1);\n\t\tgrMultiRow.query();\n\t\tif(grMultiRow.next()){\n\t\t\treturn true;\n\t\t}\n\t\treturn false;\n\t},\n\t\n\t\n\t\n    type: 'CheckMRVSDetails'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make Variable Editor Read Only for Catalog Items containing MRVS/MakeVariableSetReadOnly.js",
    "content": "function onLoad() {\n    //Type appropriate comment here, and begin script below\n    var catItem = g_form.getValue('cat_item'); //Sys Id of the Catalog Item\n\n    var ga = new GlideAjax('CheckMRVSDetails'); // SI Name\n    ga.addParam('sysparm_name', 'checkMRVS'); // Function Name\n    ga.addParam('sysparm_itemID', catItem);\n    ga.getXML(checkMRVS);\n\n    function checkMRVS(response) {\n\n        var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n        if (answer == 'true') {\n            g_form.setVariablesReadOnly(true);\n        }\n\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make Variable Editor Read Only for Catalog Items containing MRVS/README.md",
    "content": "# Make Variable Editor Read Only for Catalog Items which have a Multi Row Variable Set\nCreate an onLoad Client Script which would call a Script Include and pass in the Catalog Item sys_id to check if the Catalog Item contains a MRVS. The script has been tailored to work with the Requested Item table. To make it work for any other table which has the Variable Editor replace the field \"cat_item\" with the field containing the details of the Catalog Item\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make all fields read only/README.md",
    "content": "Use this script to make all fields readonly via client script.\n\n**Tested in Global scope\n**You can't make mandatory fields as readonly\n**Best Practice is to use ACLs\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make all fields read only/script.js",
    "content": "function onLoad() {\n    var fields = g_form.getEditableFields();\n    \n    var skippedFields = [\n        'sys_created_on',\n        'sys_created_by',\n        'sys_updated_on',\n        'sys_updated_by',\n    ];\n\n    for (var i = 0; i < fields.length; i++) {\n        var field = fields[i];\n\n        // Skip fields in the designated array\n        if (skippedFields.indexOf(field) !== -1) {\n            continue;\n        }\n\n        g_form.setMandatory(fields[i], false);\n        g_form.setReadOnly(fields[i], true);\n\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make fields read only in specific states/Make fields read only in specific state.md",
    "content": "# Make editable fields read only on load in specific state for example state is On Hold.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Make fields read only in specific states/code.js",
    "content": "function onLoad() {\n    var getStateValue = g_form.getValue('state');\n    if (getStateValue == '3') {\n        var fields = g_form.getEditableFields();\n        for (var fieldLength = 0; fieldLength < fields.length; fieldLength++) {\n            g_form.setReadOnly(fields[fieldLength], true);\n        }\n    }\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Mandatory Field Highlighter/README.md",
    "content": "# Mandatory Field Highlighter\n\n## Use Case\nProvides visual feedback for empty mandatory fields on ServiceNow forms by showing error messages when the form loads. Helps users quickly identify which required fields need to be completed.\n\n## Requirements\n- ServiceNow instance\n- Client Script execution rights\n- Forms with mandatory fields\n\n## Implementation\n1. Create a new Client Script with Type \"onLoad\"\n2. Copy the script code from script.js\n3. **Customize the fieldsToCheck string** with your form's mandatory field names\n4. Apply to desired table/form\n5. Save and test on a form with mandatory fields\n\n## Configuration\nEdit the `fieldsToCheck` variable to include your form's mandatory fields as a comma-separated string:\n\n```javascript\n// Example configurations for different forms:\n\n// For Incident forms:\nvar fieldsToCheck = 'short_description,priority,category,caller_id,assignment_group';\n\n// For Request forms:\nvar fieldsToCheck = 'short_description,requested_for,category,priority';\n\n// For Change Request forms:\nvar fieldsToCheck = 'short_description,category,priority,assignment_group,start_date,end_date';\n\n// For Problem forms:\nvar fieldsToCheck = 'short_description,category,priority,assignment_group';\n\n// Custom fields (add as needed):\nvar fieldsToCheck = 'short_description,priority,u_business_justification,u_cost_center';\n```\n\n## Features\n- Shows error messages under empty mandatory fields on form load\n- Easy configuration with comma-separated field names\n- Automatically skips fields that don't exist on the form\n- Only processes fields that are actually mandatory and visible\n- Uses proper ServiceNow client scripting APIs\n- No DOM manipulation or unsupported methods\n\n## Common Field Names\n- `short_description` - Short Description\n- `priority` - Priority\n- `category` - Category  \n- `caller_id` - Caller\n- `requested_for` - Requested For\n- `assignment_group` - Assignment Group\n- `assigned_to` - Assigned To\n- `state` - State\n- `urgency` - Urgency\n- `impact` - Impact\n- `start_date` - Start Date\n- `end_date` - End Date\n- `due_date` - Due Date\n- `location` - Location\n- `company` - Company\n- `department` - Department\n\n## Notes\n- Uses g_form.showFieldMsg() method to display error messages\n- Uses g_form.hasField() to safely check field existence (built into the safety checks)\n- Only runs on form load - provides initial validation feedback\n- Easy to customize for different forms by modifying the field list\n- Compatible with all standard ServiceNow forms\n- Lightweight and focused on essential functionality\n\n## Example Usage\nFor a typical incident form, simply change the configuration line to:\n```javascript\nvar fieldsToCheck = 'short_description,priority,category,caller_id,assignment_group';\n```\nSave the Client Script and test on an incident form to see error messages appear under empty mandatory fields."
  },
  {
    "path": "Client-Side Components/Client Scripts/Mandatory Field Highlighter/script.js",
    "content": "function onLoad() {\n\n    // USER CONFIGURATION: Add field names you want to check (comma-separated)\n    var fieldsToCheck = 'short_description,priority,category,caller_id';\n    \n    // Convert to array and process\n    var fieldArray = fieldsToCheck.split(',');\n    \n    // Check each field\n    for (var i = 0; i < fieldArray.length; i++) {\n        var fieldName = fieldArray[i];\n        \n        // Skip if field is not mandatory or not visible\n        if (!g_form.isMandatory(fieldName) || !g_form.isVisible(fieldName)) {\n            continue;\n        }\n        \n        // Get current field value\n        var value = g_form.getValue(fieldName);\n        \n        // Show error message if field is empty\n        if (!value || value === '') {\n            g_form.showFieldMsg(fieldName, 'This field is required', 'error');\n        }\n    }\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/MultiSelect in Portal/README.md",
    "content": "The custom widget that enables you to select multiple incidents in the portal page.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/MultiSelect in Portal/script.js",
    "content": "//HTML code that displays the incidents to select multiple at once.\n<div class=\"panel panel-default m-t\">\n  <div class=\"panel-heading\">\n    <h3 class=\"m-b-md\">Complaints</h3>\n\n    <div class=\"form-inline\"\n         style=\"display:flex; align-items:center; flex-wrap:wrap; gap:12px;\">\n      <!-- Search -->\n      <div class=\"form-group\" style=\"margin-left:auto;\">\n        <div class=\"input-group\">\n          <input type=\"text\"\n                 class=\"form-control\"\n                 placeholder=\"Search complaints...\"\n                 ng-model=\"c.searchText\"\n                 ng-keypress=\"$event.keyCode==13 && c.searchIncidents()\"\n                 style=\"min-width:250px;\">\n          <span class=\"input-group-addon\" ng-click=\"c.searchIncidents()\">\n            <i class=\"fa fa-search\"></i>\n          </span>\n        </div>\n      </div>\n    </div>\n  </div>\n\n  <div class=\"panel-body\">\n    <!-- Selected Count Indicator -->\n<div class=\"text-right text-muted\" ng-if=\"c.getSelectedCount() > 0\" style=\"margin-bottom: 8px;\">\n  <span class=\"label label-info\" style=\"font-size: 13px; padding: 6px 10px;\">\n    {{c.getSelectedCount()}} complaint<span ng-if=\"c.getSelectedCount() > 1\">s</span> selected\n    (across pages)\n  </span>\n</div>\n\n    <table id=\"incidentsList\"\n       class=\"table table-hover table-striped table-bordered\"\n       role=\"table\">\n  <thead>\n    <tr>\n      <th>\n        <input type=\"checkbox\"\n               ng-checked=\"c.isAllSelectedOnPage()\"\n               ng-click=\"c.toggleSelectAll($event)\"\n               title=\"Select all on this page\" />\n      </th>\n      <th>\n        <a href=\"javascript:void(0)\" ng-click=\"sortType='batchno'; sortReverse=!sortReverse\">\n          Run Date\n          <span ng-show=\"sortType=='number' && !sortReverse\" class=\"fa fa-caret-down\"></span>\n          <span ng-show=\"sortType=='number' && sortReverse\" class=\"fa fa-caret-up\"></span>\n        </a>\n      </th>\n      <th><a href=\"javascript:void(0)\" ng-click=\"sortType='caseid'; sortReverse=!sortReverse\">\n        Case ID\n        <span ng-show=\"sortType == 'caller_id' && !sortReverse\" class=\"fa fa-caret-down\"></span>\n        <span ng-show=\"sortType == 'caller_id' && sortReverse\" class=\"fa fa-caret-up\"></span>\n        </a></th>\n      <th><a href=\"javascript:void(0)\" ng-click=\"sortType = 'policyid'; sortReverse = !sortReverse\">\n        Policy ID\n        <span ng-show=\"sortType == 'short_description' && !sortReverse\" class=\"fa fa-caret-down\"></span>\n              <span ng-show=\"sortType == 'short_description' && sortReverse\" class=\"fa fa-caret-up\"></span>\n            </a></th>\n      \n    </tr>\n  </thead>\n\n  <tbody>\n    <tr ng-repeat=\"item in c.incidentsList | orderBy:sortType:sortReverse\">\n      <td>\n        <input type=\"checkbox\"\n               ng-checked=\"c.isChecked(item.sys_id)\"\n               ng-click=\"c.toggleCheckbox(item.sys_id, $event)\" />\n      </td>\n      <td><a href=\"{{item.url}}\">{{item.batchno}}</a></td>\n      <td>{{item.caseid}}</td>\n      <td>{{item.policyid}}</td>\n    </tr>\n    <tr ng-if=\"!c.complaintsList.length\">\n      <td colspan=\"9\" class=\"text-center text-muted\">No incidents found.</td>\n    </tr>\n  </tbody>\n</table>\n\n    <!-- Pagination Controls -->\n    <div class=\"pagination-controls text-center\" style=\"margin-top:10px;\">\n      <button class=\"btn btn-default\" ng-disabled=\"c.pageNumber===1\" ng-click=\"c.prevPage()\">Prev</button>\n      <span> Page {{c.pageNumber}} of {{c.totalPages}} </span>\n      <button class=\"btn btn-default\" ng-disabled=\"c.pageNumber===c.totalPages\" ng-click=\"c.nextPage()\">Next</button>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/On load Switch-Case Testing/README.md",
    "content": "This is code snippet of switch case, and it will easily help to understand the usage of switch case and how we can implement it into the environment as per our requirements.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/On load Switch-Case Testing/Switch-Case code snippet.js",
    "content": "  function onLoad() {\n  // Switch Case code snippet using in ServiceNow\n\nvar category = g_form.getValue(\"category\"); // It will capture the value of category field.\n\nswitch(category){\ncase \"hardware\": // if the value would be 'hardware' in category field.\ng_form.addInfoMessage(\"Yes category is hardware\") // This function will add a infomessage.\nbreak; //This keyword is used to stop the execution inside a switch block.\n\ncase \"software\": // if the value would be 'software' in category field.\ng_form.addInfoMessage(\"Yes category is software\") // This function will add a infomessage.\nbreak;\n\ncase \"network\": // if the value would be 'Network' in category field.\ng_form.addInfoMessage(\"Yes category is Network\") // This function will add a infomessage.\nbreak;\n\ncase \"database\": // if the value would be 'Database' in category field.\ng_form.addInfoMessage(\"Yes category is Database\") // This function will add a infomessage.\nbreak;\ndefault: // This will execute only if the category field have the value apart from mentioned cases.\ng_form.addInfoMessage(\"Oh! no category is something else\") // This function will add a infomessage.\n}\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Only number validation for input/OnChange.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n\t\t\t\n\tif (isLoading || newValue === '') {\n\t\treturn;\n\t}\n\n    // Change the name of the field to your field name\n    var FIELD_NAME = '<your field name>'; \n\n\t// allow number only\n\tvar reg = new RegExp(/^\\d+$/);\n\t\n\tif (!reg.test(newValue)) {\n\t\tg_form.hideFieldMsg(FIELD_NAME);\n\t\tg_form.showFieldMsg(FIELD_NAME, g_form.getLabelOf(FIELD_NAME) + ' may contain digits only.', 'error');\n\t\t\n\t} else {\n\t\tg_form.hideFieldMsg(FIELD_NAME);\n\t}\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Only number validation for input/README.md",
    "content": "# Client Script - Input is number only\n\nA client script that validates that the input entry is number only\n\n## Usage\n\n\n- Create a new client script\n- Set the type to OnChange. Update the field in the client script to <your field name>\n- Select a field field that you want to be number\n- Navigate to the form and set the field to a string\n- There will be a validation error close to the field\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Open Record in Agne Workspace Tab/README.md",
    "content": "This code helps to open a record in readonly mode irrespective of ACLS, UI Policies via client script in a Agent workspace tab.\n\nHere we are opening a story that is stored in the parent field on the incident record in the agent workspace.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Open Record in Agne Workspace Tab/openrecawtab.js",
    "content": "// Opens a Story attached to a Incident record in the parent field in the readonly mode.\n  function onLoad(g_form) {\ng_aw.openRecord('rm_story', g_form.getValue('parent'), {readOnlyForm: true}); \n  }\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Populate Jelly Slushbucket with Values/ClientScript.js",
    "content": "//Called when the form loads\naddLoadEvent(function () {\n  //Load the groups when the form loads\n  slush.clear();\n  var ajax = new GlideAjax(\"example_ajax_call\"); // Can use this to get values to fill the slushbucket\n  ajax.addParam(\"sysparm_example\", \"example\");\n  ajax.getXML(loadResponse);\n  return false;\n});\n\n//Called when we get a response from the 'addLoadEvent' function\nfunction loadResponse(response) {\n  //Process the return XML document and add groups to the left select\n  var xml = response.responseXML;\n  var e = xml.documentElement;\n  var items = xml.getElementsByTagName(\"item\");\n  if (items.length == 0) return;\n  //Loop through item elements and add each item to left slushbucket\n  for (var i = 0; i < items.length; i++) {\n    var item = items[i];\n    slush.addLeftChoice(\n      item.getAttribute(\"example\"),\n      item.getAttribute(\"example_2\") +\n        \": \" +\n        item.getAttribute(\"example_3\") +\n        \": \" +\n        item.getAttribute(\"example_4\")\n    ); //This is what will be displayed in the left side of the slushbucket\n  }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Populate Jelly Slushbucket with Values/README.md",
    "content": "This code is used to populate the <g:ui_slushbucket up_down=\"true\"/> Jelly tag within a UI Page.\n\nIn the UI Page, must ensure that this tag is located in the HTML section.\n\nThen in the client script, include the code provided in the example.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Prevent Rejection Without Comments/readme.md",
    "content": "🧩 Readme: Prevent Rejection Without Comments – Client Script\n📘 Overview\n\nThis Client Script enforces that approvers must enter comments before rejecting a record in the Approval [sysapproval_approver] table.\nIt ensures accountability, audit readiness, and clear justification for rejection decisions.\n\n🧠 Use Case\nField\tDetails\nTable\tsysapproval_approver\nType\tClient Script – onSubmit\nPurpose\tPrevent users from rejecting approvals without comments\nBusiness Value\tEnsures transparency and proper audit trail in approval workflows\n⚙️ Configuration Steps\n\nNavigate to System Definition → Client Scripts.\n\nClick New.\n\nFill the form as follows:\n\nField\tValue\nName\tPrevent Rejection Without Comments\nTable\tsysapproval_approver\nUI Type\tAll\nType\tonSubmit\nActive\t✅\nApplies on\tUpdate\n\nPaste the following script in the Script field.\n\n💻 Script\nfunction onSubmit() {\n    // Get the current state value of the approval record\n    var state = g_form.getValue('state');\n\n    // Get the comments entered by the approver\n    var comments = g_form.getValue('comments');\n\n    // Check if the approver is trying to REJECT the record\n    // The out-of-box (OOB) value for rejection in sysapproval_approver is \"rejected\"\n    // If state is 'rejected' and comments are empty, stop the submission\n    if (state == 'rejected' && !comments) {\n\n        // Display an error message to the user\n        g_form.addErrorMessage('Please provide comments before rejecting the approval.');\n\n        // Prevent the form from being submitted (block save/update)\n        return false;\n    }\n\n    // Allow the form submission if validation passes\n    return true;\n}\n\n🧪 Example Scenario\nField\tValue\nApprover\tJohn Doe\nState\tRejected\nComments\t(empty)\n\nUser Action: Clicks Update\nSystem Response: Shows error message —\n\n“Please provide comments before rejecting the approval.”\nRecord submission is blocked until comments are provided.\n\n✅ Expected Outcome\n🚫 Prevents rejection without comments\n⚠️ Displays user-friendly validation message\n📝 Ensures that every rejection has a reason logged for compliance\n\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Prevent Rejection Without Comments/script.js",
    "content": "//Prevent Rejection Without Comments\nfunction onSubmit() {\n    // Get the current state value of the approval record\n    var state = g_form.getValue('state');\n\n    // Get the comments entered by the approver\n    var comments = g_form.getValue('comments');\n\n    // Check if the approver is trying to REJECT the record\n    // The out-of-box (OOB) value for rejection in sysapproval_approver is \"rejected\"\n    // If state is 'rejected' and comments are empty, stop the submission\n    if (state == 'rejected' && !comments) {\n\n        // Display an error message to the user\n        g_form.addErrorMessage('Please provide comments before rejecting the approval.');\n\n        // Prevent the form from being submitted (block save/update)\n        return false;\n    }\n\n    // Allow the form submission if validation passes\n    return true;\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Price field restriction to one currency/README.md",
    "content": "\n# Client Script - Set Price type field to only one currency\n\nIn a multi currecny enabled servicenow environment, if you have a requirement to enable only one currency choice for a particular table and field of type Price.\n\n## Usage\n\n- Create a new client script\n- Set the type to OnLoad.\n- Copy the script to your client script.\n- Update the <price_field> in the client script to 'Your field name'\n- Add your currency code and symbol in place of USD & $\n- Save"
  },
  {
    "path": "Client-Side Components/Client Scripts/Price field restriction to one currency/price_field_restriction_to_one_currency.js",
    "content": "function onLoad(){\n    // Remove all currency options\n    g_form.clearOptions('<price_field>.currency_type');\n\n    // Add only one currency option (e.g., USD)\n    g_form.addOption('<price_field>.currency_type', 'USD', '$');\n\n    // Set the currency field to the only available option\n    g_form.setValue('<price_field>.currency_type', 'USD');\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Redact Sensitive Information/README.md",
    "content": "# Redact Sensitive Information\n\nWhen users create an incident or HR case via the self-service portal, they may occasionally enter sensitive information (e.g., personal identifiers, account numbers). \nTo prevent misuse of such data, **fulfillers** can redact sensitive information from the short description or description fields.\n\nThis ensures that confidential information is safeguarded and not accessible for unauthorized use or distribution.\n\n## Prerequisites\n\n1. Custom Field:\n    Add a custom field to the form to hold the redacted text.\n    Example: u_redact (Redact).\n   \n2. OnSubmit Client Script:\n    Create an onsubmit client script to redact sensitive information.\n    This script will update the **short description** and **description** field with custom value as required.\n\n**Note**: Data that has been redacted cannot be recovered.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Redact Sensitive Information/script.js",
    "content": "function onSubmit() {\n    var redact = g_form.getValue(\"u_redact\"); //custom field on the form to redact information\n    if (redact == true) {\n        var answer = confirm(getMessage('Do you want to redact sensitive information')); //Confirm the user who wants to redact information \n        if (answer) {\n            g_form.setValue('short_description', 'Short Description is redacted as it contained sensitive information'); //Custom short_description post redacting\n            g_form.setValue('description', 'Description is redacted as it contained sensitive information'); //Custom description post redacting\n            g_form.setValue('work_notes', 'The Description and Short Description has been redacted.'); //Adding work notes to track who redacted the short_description and description\n            g_form.setReadOnly('short_description', true);\n            g_form.setReadOnly('description', true);\n            g_form.setReadOnly('u_redact', true)\n        } else {\n            g_form.setValue('u_redact', false);\n            return false;\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Reinstate Error status/README.md",
    "content": "Table: Time Worked [task_time_worked]\nType: onsubmit\n\n#Objective :\nEnsure that time entries (represented by the work_date field) are not submitted after 8:00 PM CST on two key dates:\nThe 16th of the month and The last day of the month\nIf a user tries to submit time for a current or past date after the cut-off time, the submission is blocked and a clear error message is displayed.\n\n#Business Scenario\nImagine a consulting firm where employees log billable hours against customer cases. There are internal controls in place that lock the timekeeping system after a certain cut-off time to ensure accurate payroll and billing.\n\nThe finance department requires that:\nOn the 16th and last day of each month, submissions must be in before 8:00 PM CST.\nIf employees miss the deadline, they can only log time for future dates (not today or the past).\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Reinstate Error status/script.js",
    "content": "function onSubmit() {\n    // Cutoff time for submission in CST.\n    var cutoffTime = \"20:00:00\";\n\n    // Get the current date and time in CST\n    var currentDate = new Date();\n    var currentCSTDate = new Date(\n        currentDate.toLocaleString(\"en-US\", {\n            timeZone: \"America/Chicago\"\n        })\n    );\n\n    // Get time from current CST date\n    var currentCSTTime = currentCSTDate.toTimeString().substring(0, 8);\n\n    // Get last day of the month\n    var dayOfMonth = currentCSTDate.getDate();\n    var lastDayOfMonth = new Date(\n        currentCSTDate.getFullYear(),\n        currentCSTDate.getMonth() + 1,\n        0\n    ).getDate();\n\n    if ((dayOfMonth === 16 || dayOfMonth === lastDayOfMonth) && currentCSTTime > cutoffTime) {\n        var workDate = g_form.getValue(\"work_date\");\n\n        if (workDate) {\n            var formattedWorkDate = new Date(workDate + \"T00:00:00\");\n            // If work_date is on or before current date, block submission\n            if (formattedWorkDate <= currentCSTDate) {\n                g_form.addErrorMessage(\n                    \"The time period closed for time submission at 8:00 PM CST. Time must be billed in the next time period.\" + \": \" + lastDayOfMonth\n                );\n                return false;\n            }\n        }\n    }\n    return true;\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Remove Option from Choice List/README.md",
    "content": "**Purpose:**\nThis onChange function automatically reacts when the \"Category\" field is changed. If the new category selected is \"inquiry,\" the function removes the options for \"Impact\" and \"Urgency\" that have a value of 1.\nWhenever a user selects a new category, the script checks if it’s set to \"inquiry.\" If so, it removes the specified options for \"Impact\" and \"Urgency\".\n**How to Use This Function**\nYou can use this Onchange client script on any form and maanage your field choice options.\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Remove Option from Choice List/Remove Options from Choice List.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\nif (isLoading || newValue == '') {\nreturn;\n}\nif (newValue == 'inquiry') { //Onchange of Category\ng_form.removeOption('impact', '1');\ng_form.removeOption('urgency', '1');\n}\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Require comment onPriority change/README.md",
    "content": "Table: sn_customerservice_case\nType: OnChange\nField: Priority\n\nUse Case:\nMake additional comments mandatory on priority change for the case table.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Require comment onPriority change/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n    if ((!g_form.isNewRecord()) && (newValue != oldValue)) {\n        g_form.setMandatory('comments', true);\n        g_form.addErrorMessage('Additional comment required when changing Priority.');\n    } else {\n        g_form.setMandatory('comments', false);\n    }\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Restrict Creation of P1, P2 Incidents/README.md",
    "content": "The code snippet can be used to restrict the creation of priority P1, P2 incidents except for the admins and a particular group members.\n\nTo achieve this requirement, I have created a onChange client script for the field name \"Priority\" and also created a script include to get the necessary data from the server side.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Restrict Creation of P1, P2 Incidents/Restrict_Creation_Of_P1_P2_Incidents.js",
    "content": "//Client Script\n\n//Type onChnage, Field name: Priority\n\n// To restrict creation of priority P1, P2 incidents except for the admins and a particular group members\n\nvar checkAjax = '';\n// if logged in user is admin then skip the code execution\nif(!g_user.hasRole('admin')){\n\tif(newValue == 1 || newValue == 2){\n\t\tif(g_form.isNewRecord()){\n\t\n\t\tcheckAjax = new GlideAjax('checkMemberOfGroup');\n\t\tcheckAjax.addParm('sysparm_name', 'checkAccessNew');\n\t\tcheckAjax.getXMLWait();\n\t\tvar ans = checkAjax.getAnswer();\n\t\tif(and == 'false'){\n\t\t\tg_form.setValue('impact', 3);\n\t\t\tg_form.setValue('urgency', 3);\n\t\t\tg_form.addErrorMessage('Creation of P1, P2 incidents is restricted to Admins and IT ServiceDesk');\n\t\t}\n\t\telse{\n\t\t\tvar incNumber = g_form.getValuye('number');\t\n\t\t\tcheckAjax = newGlideAjax('checkMemberOfGroup');\n\t\t\tcheckAjax.addParm('sysparm_name', 'checkAccess');\n\t\t\tcheckAjax.addParm('sysparm_number', incNumber); // passing the current incident number so that if the logged in user is an end user, then get the previous values of impact and urgency values.\n\t\t\tcheckAjax.getXMLWait();\n\t\t\tvar ans = checkAjax.getAnswer();\n\t\t\tans = ans.split(\",\");\n\t\t\t// if value returned false, then logged in user is neither admin nor member of a particluar gorup, \n\t\t\tif(ans[2] == 'false'){\n\t\t\t\tvar imp = parseInt(ans[0]);\n\t\t\t\tvar urg = parseInt(ans[1]);\n\t\t\t\t// setting back the impact and urgency values to their previous values if logged in user is not part of a particular group and not an admin.\n\t\t\t\tg_form.setValue('impact', imp);\n\t\t\t\tg_form.setValue('urgency', urg);\n\t\t\t\tg_form.addErrorMessage('Creation of P1, P2 incidents is restricted to Admins and IT ServiceDesk');\n\t\t\t}\n\t\t}\n\t}\n}\n\n\n\n//Script Include\n\nvar checkMemberOfGroup = Class.create();\ncheckMemberOfGroup.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n// The below method is used to restrict the creation of P1, P2 incidents for existing incidents\ncheckAccess: function(){\n \tvar arr = [];\n\tvar number = this.getParameter('sysparm_number');\n\tvar glideInc = new GlideRecord('incident');\n\tglideInc.addquery('number', number);\n\tglideInc.query();\n\tif(glideInc.next()){\n\t\tarr.push(glideInc.impact);\n\t\tarr.push(glideInc.urgency);\n\t}\n\tvar checkGroupMember = gs.getUser().isMemberOf('Group_Name');\n\tif(checkGroupMember){\n\t\tarr.push('true');\n\t}\n\telse{\n\t\tarr.push('false');\n\t}\n\treturn arr.toString();\n},\n// The below function is used to restict the creation of priority P1, P2 incidents for new incidents\n\ncheckAccessNew: function(){\n\t\n\tvar checkGroupMember = gs.getUser().isMemberOf('Group_Name');\n\tif(checkGroupMember){\n\t\treturn true;\n\t}\n\treturn false;\n},\ntype: 'checkMemberOfGroup\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Restrict Fields on Template/README.md",
    "content": "**Details**\n\nThis is a on change client script on sys_template table. This script will restrict users to select defined fields while template creation.\nType: OnChange\nField: Template\nTable: sys_template\n\n**Use Case**\n\nThere is an OOB functionality to restrict fields using \"**save as template**\" ACL, but it has below limitations:\n1. If the requirement is to restrcit more number of fields (example: 20), 20 ACLs will have to be created.\n2. The ACls will have instance wide effect, this script will just restrict on client side.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Restrict Fields on Template/script.js",
    "content": "/*\nType: onChnage\nTable: sys_template\nField: Template\n*/\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n    if (g_form.getValue('table') == 'incident') { // table on which sys_template is being created.\n        var fields = ['active', 'comments']; // array of fields to be restricted while template creation.\n        for (var i = 0; i < fields.length; i++) {\n            if (newValue.indexOf(fields[i]) > -1) {\n                alert(\"You Cannot Add \" + fields[i]); // alert if user selects the restricted field.\n                var qry = newValue.split(fields[i]);\n                g_form.setValue('template', qry[0] + 'EQ');  // set the template value to previous values (oldValue does not work in this case).\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set Severity, state & assigned to/README.md",
    "content": "Use the script provided in script_include.js and script.js to set fetch multiple values from server to client side by passing an\nobject from server to the client side and setting values on your form. This can be used to pass multiple parameters from server to\nclient side.\n\nUse Case:\nConsider you have a reference field on your form referring to \"sn_si_incident\" and you need to set Severity, state and assigned to\nonChange of the reference field.\n\nSolution:\nCreate a client callable script include as mentioned in script_include.js and pass the required values to your client script.\nThen use the onChange client script in script.js to set values on the form.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set Severity, state & assigned to/script.js",
    "content": "//onChange client script\n\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        \n        return;\n    }\n\n\n    //Type appropriate comment here, and begin script below\n    var ga = new GlideAjax('getSIRDetails'); // calling script include\n    ga.addParam('sysparm_name', 'getDetails');\n    ga.addParam('sysparm_sir', newValue); //passing newValue to the script include\n    ga.getXMLAnswer(callBackFunction);\n\n\n    function callBackFunction(response) {\n        var ans = JSON.parse(response);\n        g_form.setValue('severity', ans.severity); // setting values from the obj to appropriate fields\n        g_form.setValue('soc_sir_state', ans.state);\n        g_form.setValue('soc_sir_assigned_to', ans.assignedto);\n\n\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set Severity, state & assigned to/script_include.js",
    "content": "//Client callable script include\nvar getSIRDetails = Class.create();\ngetSIRDetails.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    getDetails: function() {\n        var sir = this.getParameter('sysparm_sir'); //getting the newValue of Security Inc from onChange client script\n        var obj = {}; //declare an object\n        var gr = new GlideRecord('sn_si_incident');\n        gr.addQuery('sys_id', sir); //Query to security incident table with the newValue\n        gr.query();\n        if (gr.next()) {\n            obj.severity = gr.severity.getDisplayValue();  //Setting values in the obj\n            obj.state = gr.state.getDisplayValue();\n            obj.assignedto = gr.assigned_to.getDisplayValue();\n        }\n        return JSON.stringify(obj); //passing the object to client script\n    },\n    type: 'getSIRDetails'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set Urgency to High onChange of caller field/README.md",
    "content": "This is a client script that change urgency to high automatically when changing caller field with the caller name whose vip is true\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set Urgency to High onChange of caller field/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n   if (isLoading || newValue === '') {\n      return;\n   }\n\n   var vipalert = g_form.getReference('caller_id',vipFunction);\n   function vipFunction(vipAlert){\n\t if(vipAlert.vip == 'true'){\n       g_form.setValue('urgency','1');\n\t  }\n  }\n   \n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set field style like font and background/OnLoad client script.js",
    "content": "\n// your condition to apply the style e.g. user is a VIP user\nvar condition = true; // Set the condition as needed\n\n// Find the control\nvar fieldToSetStyle = g_form.getControl('sys_display.incident.caller_id');\n\n\nif (condition == true) {\n    fieldToSetStyle.style.fontWeight = 'bold';\n    fieldToSetStyle.style.backgroundColor = 'red';\n} else  {\n    fieldToSetStyle.style.fontWeight = 'normal';\n    fieldToSetStyle.style.backgroundColor = 'white';\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Set field style like font and background/README.md",
    "content": "# Client Script - Change field style\n\nA client script that changes field font, and background based on some condition\nAnd example would be if the currently raised incident is by a VIP user and hightlight the caller...\n\n## Usage\n\n- Create a new OnLoad script\n- Copy this script into it\n- Set the condition and the field that requires to be changed\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show Current Domain/DomainCheckUtil.js",
    "content": "var DomainCheckUtil = Class.create();\nDomainCheckUtil.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    //get current domain of user session\n    getCurrentDomainName: function() {\n        var sessionDomainId = gs.getSession().getCurrentDomainID();\n        var gr = new GlideRecord('domain');\n        if (gr.get(sessionDomainId)){\n            return gr.name;\n\t\t}\n    //Return global domain name  \n    return 'Global';\n    },\n\n    type: 'DomainCheckUtil'\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show Current Domain/Readme.md",
    "content": "Domain Separation Current Domain Display\nOverview\nThis functionality provides real-time awareness to users about the current selected domain within ServiceNow's Domain Separation framework. It displays an informational message on form load indicating the active domain context, helping prevent accidental configuration or data entry in the wrong domain.\n\nComponents\nScript Include: DomainCheckUtil\nGlobal, client-callable Script Include allowing client scripts to query the current domain name via GlideAjax.\n\nMethods:\nisCurrentDomain(domainSysId) — Checks if a given domain sys_id matches the current session domain.\n\nClient Script\nAn onLoad client script configured globally on the Global table, set to true to load on all forms.\nCalls the Script Include via GlideAjax to retrieve current domain name asynchronously.\n\nDisplays the domain name as an informational message (g_form.addInfoMessage) on the form header on every page load.\n\nUsage\nUpon loading any record form, users see a message stating:\n\"You are currently working in Domain Separation domain: [domain_name].\"\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show Current Domain/Show Current Domain.js",
    "content": "function onLoad() {\n\tvar ga = new GlideAjax('DomainCheckUtil');\n    ga.addParam('sysparm_name', 'getCurrentDomainName');\n    ga.getXMLAnswer(showDomainMessage);\n    function showDomainMessage(response) {\n\t\tvar message = 'You are currently working in Domain Separation domain: <strong>' + response + '</strong>.';\n\t\tg_form.addInfoMessage(message);\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show Message On Both Form and List/README.md",
    "content": "# Use Case\nThe OOB `GlideForm (g_form)` API has documentation on displaying messages of info, warning and error types on form view, but lack a success message. Moreover, this `g_form` API is not accessible on lists and hence makes it difficult to display list level messages.\nHowever, SN provides another client-side method `GlideUI.get().addOutputMessage({options})` that can be used to display messages in native UI irrespective of form or list views. Even the popular `g_form.addInfoMessage(params)` API actually leverages the same `addOutputMessage(options)` method to render messages.\nCustom icons can also be included, but the background colour is lost due to the way `.addOutputMessage()`  method has been implemented.\n\n**Allowed icons (as per SN documentation):**\n> icon-user, icon-user-group, icon-lightbulb, icon-home, icon-mobile, icon-comment, icon-mail, icon-locked, icon-database, icon-book, icon-drawer, icon-folder, icon-catalog, icon-tab, icon-cards, icon-tree-right, icon-tree, icon-book-open, icon-paperclip, icon-edit, icon-trash, icon-image, icon-search, icon-power, icon-cog, icon-star, icon-star-empty, icon-new-ticket, icon-dashboard, icon-cart-full, icon-view, icon-label, icon-filter, icon-calendar, icon-script, icon-add, icon-delete, icon-help, icon-info, icon-check-circle, icon-alert, icon-sort-ascending, icon-console, icon-list, icon-form, and icon-livefeed\n\n\n# Output Screenshot\n\n![image](https://github.com/annaydas/code-snippets/assets/29729050/7c93828c-d30a-4255-a97c-a2ee4f9126ac)\n\n\n# Usage\n\n### Success Message\n```javascript\nGlideUI.get().addOutputMessage({\n    msg: 'Success',\n    type: 'success',\n    preventDuplicates: true\n});\n```\n\n### Warning Message\n```javascript\nGlideUI.get().addOutputMessage({\n    msg: 'Warning',\n    type: 'warning',\n    preventDuplicates: true\n});\n```\n\n### Error Message\n```javascript\nGlideUI.get().addOutputMessage({\n    msg: 'Error',\n    type: 'error',\n    preventDuplicates: true\n});\n```\n\n### Info Message\n```javascript\nGlideUI.get().addOutputMessage({\n    msg: 'Info',\n    type: 'info',\n    preventDuplicates: true\n});\n```\n\n### Custom Icon (but it loses the background colour)\n```javascript\nGlideUI.get().addOutputMessage({\n    msg: 'Custom Icon, but styling is lost',\n    icon: 'icon-lightbulb',\n    type: 'custom-message',\n    preventDuplicates: true\n});\n```\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show Message On Both Form and List/script.js",
    "content": "// Success Message\nGlideUI.get().addOutputMessage({\n    msg: 'Success',\n    type: 'success',\n    preventDuplicates: true\n});\n\n// Warning Message\nGlideUI.get().addOutputMessage({\n    msg: 'Warning',\n    type: 'warning',\n    preventDuplicates: true\n});\n\n// Error Message\nGlideUI.get().addOutputMessage({\n    msg: 'Error',\n    type: 'error',\n    preventDuplicates: true\n});\n\n// Info Message\nGlideUI.get().addOutputMessage({\n    msg: 'Info',\n    type: 'info',\n    preventDuplicates: true\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show field if x things are checked/README.md",
    "content": "Use this script to show a field after `n` checkboxes are checked and not before.\n\n**Tested in Global scope\n**You can't make mandatory fields as readonly\n**Best Practice is to use UI Policies\n**Sometimes you have a lot of check marks and that logic gets narly\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Show field if x things are checked/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    //Set the mandatory checkbox variable names and total mandatory count here\n    var mandatoryVars = ['option1', 'option2', 'option3', 'option4', 'option5'];\n    var variableToShow = 'someothervariable';\n    var requiredCount = 2;\n    var actualCount = 0;\n    for (var x = 0; x < mandatoryVars.length; x++) {\n        if (g_form.getValue(mandatoryVars[x]) == 'true') {\n            actualCount++;\n        }\n    }\n    if (requiredCount <= actualCount) {\n        g_form.setDisplay(variableToShow, true);\n    } else {\n        g_form.setDisplay(variableToShow, false);\n    }\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Smart-Field-Suggestions/README.md",
    "content": "# Smart Field Suggestions Based on Keywords\n\n## Category\nClient-Side Components / Client Scripts\n\n## Description\nThis is an onChange Client Script designed for the Incident table that dynamically suggests and populates the Category, Subcategory, and Priority fields based on keywords detected in the Short Description field. By matching keywords, it prompts users to confirm applying suggestions aligned with backend choice values for seamless integration.\n\n## Use Case\nDuring incident creation or update, manually categorizing tickets correctly is critical for IT operations efficiency. This snippet automates early triage by analyzing user-entered short descriptions, providing actionable suggestions to improve categorization accuracy, accelerate routing, and enhance resolution speed.\n\n## How to Use\n- Add this script as an \"onChange\" client script on the Incident table's `short_description` field.\n- Ensure the Category, Subcategory, and Priority fields have choice lists aligned with backend values specified in the snippet.\n- Modify the keyword list to align with your organizational terminologies if needed.\n- The user will be prompted with suggestions and may confirm or dismiss them, allowing balanced automation and human control.\n\n## Why This Use Case is Unique and Valuable\n\n- Dynamically assists in categorizing incidents early, improving routing and resolution time.\n- Uses only platform APIs (`g_form`) without custom backend code or external integrations, making it lightweight and maintainable.\n- Uses real backend choice values ensuring seamless compatibility with existing configurations, reducing errors.\n- Provides prompt suggestions with user confirmation, balancing automation and user control.\n- Easily adaptable for other fields, keywords, or use cases beyond Incident management.\n- Designed without fragile DOM manipulations, following ServiceNow best practices, tailored for real environments.\n\n## Compatibility\nThis client script is compatible with all standard ServiceNow instances without requiring ES2021 features.\n\n## Files\n- `Smart Field Suggestions Based on Keyword.js` — the client script implementing the logic.\n\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Smart-Field-Suggestions/Smart Field Suggestions Based on Keyword.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || !newValue || newValue.length < 10) {\n        return;\n    }\n\n    var keywords = [\n        { \n            pattern: /password|login|access/i, \n            category: 'inquiry | Help ',             \n            subcategory: 'antivirus',        \n            priority: '3', \n            suggestion: 'This appears to be a Inquiry issue.' \n        },\n        { \n            pattern: /slow|performance|hanging/i, \n            category: 'software',          \n            subcategory: 'email',          \n            priority: '2', \n            suggestion: 'This appears to be a Software issue.' \n        },\n        { \n            pattern: /printer|print|printing/i, \n            category: 'hardware',            \n            subcategory: 'monitor',         \n            priority: '3', \n            suggestion: 'This appears to be a Hardware issue.' \n        },\n        { \n            pattern: /database|data/i, \n            category: 'database',          \n            subcategory: 'db2',             \n            priority: '3', \n            suggestion: 'This appears to be an Database issue.' \n        },\n        { \n            pattern: /network|internet|wifi|connection/i, \n            category: 'network',             \n            subcategory: 'vpn',               \n            priority: '2', \n            suggestion: 'This appears to be a network issue.' \n        }\n        \n    ];\n\n    var lowerDesc = newValue.toLowerCase();\n    var matched = null;\n\n    for (var i = 0; i < keywords.length; i++) {\n        if (keywords[i].pattern.test(lowerDesc)) {\n            matched = keywords[i];\n            break;\n        }\n    }\n\n    g_form.hideFieldMsg('short_description', true);\n    g_form.clearMessages();\n\n    if (matched) {\n        g_form.showFieldMsg('short_description', matched.suggestion, 'info', false);\n\n        if (confirm(matched.suggestion + \"\\n\\nApply these suggestions?\")) {\n            g_form.setValue('category', matched.category);\n            g_form.setValue('subcategory', matched.subcategory); // Make sure you use backend value for subcategory!\n            g_form.setValue('priority', matched.priority);\n            g_form.addInfoMessage('Suggestions applied automatically!');\n        } else {\n            g_form.addInfoMessage('Suggestions dismissed.');\n            g_form.hideFieldMsg('short_description', true);\n        }\n    } else {\n        g_form.addInfoMessage('No keywords matched in description.');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/State changes to On Hold then worknotes should be mandatory/README.md",
    "content": "## This is OnChange client Script\n# whenever the 'State' on incident table will change to 'on Hold' the 'work notes' will become mandatory\nWhile using this script the table should be selected as 'incident' and type should be 'onChange' and field should be selected as 'State'. then write the script.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/State changes to On Hold then worknotes should be mandatory/worknotes.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n   if (isLoading || newValue === '') {\n      return;\n   }\nif(newValue == '3') // here 3 is the value of On Hold\n{\n\tg_form.setMandatory('work_notes',true);\n}\n   else {\n\tg_form.setMandatory('work_notes',false);\n   }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Sync Ajax with no getXMLWait/README.md",
    "content": "# onSubmit Ajax validation with no getXMLWait\n\nUsing getXMLWait() ensures the order of execution, but can cause the application to seem unresponsive, significantly degrading the user experience of any application that uses it.\n\nAlso, the getXMLWait method is not available in scoped applications.\n\nThis snippet simulates the behavior of the getXMLWait method."
  },
  {
    "path": "Client-Side Components/Client Scripts/Sync Ajax with no getXMLWait/script.js",
    "content": "function onSubmit() {\n\tif (g_scratchpad.isFormValid) {\n\t\treturn true;\n\t}\n\n\tvar actionName = g_form.getActionName();\n\tvar ga = new GlideAjax('scriptIncludeName');\n\tga.addParam('sysparm_name', 'methodName');\n\tga.addParam('sysparm_additional_parm', 'parmValue');\n\tga.getXMLAnswer(function (answer) {\n\t\tif (answer == 'true') {\n\t\t\tg_scratchpad.isFormValid = true;\n\t\t\t// It will trigger the same UI action that was used to submit the form\n\t\t\tg_form.submit(actionName);\n\t\t}\n\t});\n\n\treturn false;\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Toggle Annotation On Forms With Script/README.md",
    "content": "# Use Case\nThis method can be used to show/hide/toggle form annotations through client-side script.\n\n# Limitation\nThis script works only with form annotations of the following types:\n- Info Box Blue\n- Info Box Red\n- Section Details\n- Text\n\n# Usage\n\n### Show form annotations\n```javascript\nSN.formAnnotations.show();\n```\n\n### Hide form annotations\n```javascript\nSN.formAnnotations.hide();\n```\n\n### Toggle form annotations\n```javascript\nSN.formAnnotations.toggle();\n```\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Toggle Annotation On Forms With Script/script.js",
    "content": "// Show form annotations\nSN.formAnnotations.show();\n\n// Hide form annotations\nSN.formAnnotations.hide();\n\n// Toggle form annotations\nSN.formAnnotations.toggle();\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Toggle form section visibility/README.md",
    "content": "\n# Toggle Form Section Visibility Client Script\n\n## Overview\n\nThis client script enhances the user experience in ServiceNow by providing a dynamic way to toggle the visibility of a form section based on the state of a checkbox or switch field. It simplifies complex forms and allows users to control which sections they want to view, making the form more user-friendly.\n\n## How It Works\n\nWhen a user interacts with the designated checkbox field, the corresponding form section is either displayed or hidden in real-time. This behavior improves form navigation and streamlines the user experience.\n\n### Configuration\n\nTo use this client script in your ServiceNow instance, follow these steps:\n\n1. **Create a Client Script:**\n\n   - Log in to your ServiceNow instance as an admin or developer.\n   - Navigate to \"System Definition\" > \"Client Scripts.\"\n   - Create a new client script and provide it with a meaningful name (e.g., \"Toggle Section Visibility\").\n\n2. **Copy and Paste the Script:**\n\n   - Copy the JavaScript code provided in this README.\n   - Paste the code into your newly created client script.\n\n3. **Customize Field and Section:**\n\n   - Modify the script to specify the checkbox field that triggers the visibility toggle and the ID of the section you want to control.\n\n4. **Activate and Test:**\n\n   - Save and activate the client script.\n   - Test the functionality by creating or editing a form with the designated checkbox and section.\n\n## Example Usage\n\nImagine you have a form with a checkbox labeled \"Show Additional Details.\" When users check this box, the \"Additional Details\" section of the form becomes visible, and when unchecked, it is hidden. This feature simplifies long forms and allows users to focus on the information that matters to them.\n\n## Benefits\n\n- Improves user experience by offering dynamic form sections.\n- Simplifies complex forms, making them more user-friendly.\n- Enhances form navigation and efficiency.\n- Reduces clutter on forms and improves user satisfaction.\n\n## Code Explanation\n\n- The toggleFormSection function is defined to be executed when the checkbox field changes.\n- It retrieves the checkbox field's control and the section's HTML element by their respective IDs.\n- When the checkbox is checked (checkboxField.checked is true), it sets the section's display style property to 'block', making the section visible.\n- When the checkbox is unchecked, it sets the section's display property to 'none', hiding the section.\n- The g_form.observe method attaches the toggleFormSection function to the change event of the checkbox field, so it triggers whenever the checkbox state changes."
  },
  {
    "path": "Client-Side Components/Client Scripts/Toggle form section visibility/toggleFormSection.js",
    "content": "// Client Script to Toggle Form Section Visibility\n\nfunction toggleFormSection() {\n    var checkboxField = g_form.getControl('checkbox_field'); // Replace 'checkbox_field' with your field name\n    var section = gel('section_id'); // Replace 'section_id' with the ID of the section to toggle\n\n    if (checkboxField.checked) {\n        section.style.display = 'block'; // Show the section when the checkbox is checked\n    } else {\n        section.style.display = 'none'; // Hide the section when the checkbox is unchecked\n    }\n}\n\n// Attach the toggleFormSection function to the checkbox field's change event\ng_form.observe('change', 'checkbox_field', toggleFormSection);\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Translate Message/README.md",
    "content": "# Translate your messages in client script using getMessage method\n\n  *[getMessage() code snippet](getMessage.js)\n  \n  *[Translate message in client script doc](https://docs.servicenow.com/bundle/rome-platform-administration/page/administer/localization/task/t_TranslateAClientScriptMessage.html)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Translate Message/getMessage.js",
    "content": "/* Translate messages according to the logged in user's preferred language in client script using getMessage() method.\n   Note: Make sure to add an entry under [sys_ui_message] table and add the key in your client script Message fied (Not available in form by default) for preventing an extra round trip to server for fetching the message.\n\n Code :- */\n\nvar msg = getMessage('message_key'); //message_key defined in [sys_ui_message] table and added to the Message field of the client script. Fetching and storing the translated message to msg variable.\ng_form.addInfoMessage(msg); // Showing the translated message as an info message.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Update Category from Short Description Keywords/README.md",
    "content": "## Client Script that looks for category keywords in the Short Description updates the category field\n\n* Name: Set Category From Desc Keyword \n* Table: Incident\n* Type: onChange\n* Field Name: Short Description\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Update Category from Short Description Keywords/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '')\n        return;\n    //Query the short description field\n\n    var x = g_form.getValue('short_description');\n    var shortDescription = x.toLowerCase();\n\n\n\n    //Define a mapping of keywords to assignment groups\n    var keywordMapping = {\n        \"network\": \"network\",\n        \"ip\": \"network\",\n        \"software\": \"software\",\n        \"adobe\": \"software\",\n        \"outlook\": \"software\",\n        \"hardware\": \"hardware\",\n        \"laptop\": \"hardware\",\n        \"printer\": \"hardware\",\n        \"database\": \"database\",\n        \"oracle\": \"database\",\n        \"how\": \"inquiry\",\n        \"support\": \"inquiry\",\n\n    };\n\n    //Loop through the keywords and check if they are present in the short description\n    for (var keyword in keywordMapping) {\n        if (shortDescription.indexOf(keyword) !== -1) {\n            //Set the Category based on the matching keyword\n            g_form.setValue('category', keywordMapping[keyword]);\n            break;\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Use case of addOption() and removeOption()/README.md",
    "content": "# onChange client script for table 'change_request' where field is 'priority'\nif priority is critical, impact can be high and medium i.e, low will be removed from choice list using removeOption()\nand for other priority ,impact can be high, medium and low i.e, low option will be added, using addOption()\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Use case of addOption() and removeOption()/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n   if (isLoading || newValue === '') {\n\treturn;\n   }\n   if(newValue == 1) {\n\tg_form.removeOption('impact',3); // 3 is the value for impact 'low'\n   } else {\n\tg_form.addOption('impact',3,'3 - Low');\n   }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate Interaction record for FCR(First Call Resolution)/readme.md",
    "content": "README — Client Script: Validate Interaction Resolution\n📌 Purpose\nThis Client Script ensures proper validation when resolving an Interaction record in ServiceNow.\nIt prevents a user from marking an Interaction as Closed Complete without proper justification.\n\n🎯 What It Does\n\nWhen a user attempts to submit the form:\n✔ Allows submission only if:\nInteraction Type is \"walkup\"\nAnd Related Task Boolean is true\n\nOR\n\n✔ If work notes are provided for First Contact Resolution (FCR)\n❌ Prevents submission if:\nState = Closed Complete\nWork Notes are empty\nAnd no related task condition is met\n\n🧠 Validations Performed\nField\tCondition\tAction\nstate\tclosed_complete\tTrigger validation\ntype\twalkup AND u_boolean_no_related_task = true\tSubmission allowed ✅\nwork_notes\tMust not be empty\tShow error & stop submission ❌\n🔔 User Feedback\n\nIf work notes are missing:\nDisplays inline field message\n\nShows popup alert:\n\"Provide Worknotes for FCR Interaction\"\n\n📍 Script Location\n\nClient Script → Type: onSubmit()\nApplicable to Interaction table (interaction)\n\n📌 Script Code\n//Client Script to validate an Interaction record is resolved with out any related record created.\nfunction onSubmit() {\n    var relatedTask = g_form.getValue('u_boolean_no_related_task');\n    var state = g_form.getValue('state');\n    var type = g_form.getValue('type');\n    var workNotes = g_form.getValue('work_notes'); // Get the value of work notes\n\n    // Clear previous field messages\n    g_form.clearMessages();\n\n    // Check if state is changing to 'Closed Complete'\n    if (state == 'closed_complete') {\n        // Check additional conditions\n        if (type == 'walkup' && relatedTask == 'true') {\n            return true; // Allow form submission\n        } else if (!workNotes) { // Check if work notes is empty\n            g_form.showFieldMsg('work_notes', 'Provide Worknotes for FCR Interaction', 'error');\n            alert('Provide Worknotes for FCR Interaction');\n            return false; // Prevent form submission\n        }\n    }\n    return true; // Allow form submission for other states\n}\n\n✅ Benefits\n\nMaintains consistent resolution standards\nEnsures justification/documentation for FCR interactions\nReduces incorrect closure of requests without related actions\n\n\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate Interaction record for FCR(First Call Resolution)/script.js",
    "content": "//Client Script to validate an Interaction record is resolved with out any related record created.\nfunction onSubmit() {\n    var relatedTask = g_form.getValue('u_boolean_no_related_task');\n    var state = g_form.getValue('state');\n    var type = g_form.getValue('type');\n    var workNotes = g_form.getValue('work_notes'); // Get the value of work notes\n\n    // Clear previous field messages\n    g_form.clearMessages();\n\n    // Check if state is changing to 'Closed Complete'\n    if (state == 'closed_complete') {\n        // Check additional conditions\n        if (type == 'walkup' && relatedTask == 'true') {\n            return true; // Allow form submission\n        } else if (!workNotes) { // Check if work notes is empty\n            g_form.showFieldMsg('work_notes', 'Provide Worknotes for FCR Interaction', 'error');\n\t\t\talert('Provide Worknotes for FCR Interaction');\n            return false; // Prevent form submission\n        }\n    }\n    return true; // Allow form submission for other states\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate Short Description/README.md",
    "content": "This Client Script validates the \"Short Description\" field before the form is submitted.\n If the description is more than 100 characters, it displays an alert and prevents submission. \nThis helps maintain data quality by ensuring adequate information is provided.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate Short Description/ShortDescriptionLength.js",
    "content": "\nfunction onSubmit() {\n    var shortDescription = g_form.getValue('short_description');\n    if (shortDescription.length > 100) {\n        alert('Short Description must be not be more than 100 characters long.');\n        return false; // Prevent form submission\n    }\n    return true; // Allow form submission\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate Short Description/validShortDescription.js",
    "content": "function onSubmit() {\n    var shortDescription = g_form.getValue('short_description');\n    if (shortDescription.length < 10) {\n        alert('Short Description must be at least 10 characters long.');\n        return false; // Prevent form submission\n    }\n    return true; // Allow form submission\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate Short Description/validateSpecialChar.js",
    "content": "// Client Script to Validate Special Charecters\nfunction onSubmit() {\n    var shortDescription = g_form.getValue('short_description');\n  var specialCharsRegex = /[^a-zA-Z0-9\\s]/g;\nvar specialChars = description.match(specialCharsRegex);\n  if (specialChars) {\n        alert('Description contains invalid characters: ' + specialChars.join(', '));\n        return false;\n    } else {\n        return true;\n    }\n    \n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate date is in future without GlideAjax/OnChange Client Script.js",
    "content": "/*\n    Client script that validates a date is in future without the need of a GlideAjax and Script Include\n*/\n\nfunction onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    var fieldToValidate = '<your_field_name>'\n\n    var currentDate = formatDate(new Date(), g_user_date_format);\n    var currentDateInMs = getDateFromFormat(currentDate, g_user_date_format);\n    var dateToValidateInMs = getDateFromFormat(g_form.getValue(fieldToValidate), g_user_date_format);\n\n    if (dateToValidateInMs <= currentDateInMs) {\n        g_form.showFieldMsg(fieldToValidate, \"Enter a valid future date.\", 'error');\n        return false;\n    }\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/Validate date is in future without GlideAjax/README.md",
    "content": "# Client Script - Date in future\n\nA client script that validates that a specified date is in future without the need for a GlideAjax and Script Include\n\n## Usage\n\n- Create a new client script\n- Set the type to OnChange\n- Select a date field that you want to validate\n- Navigate to the form and set the date field to the past\n- There will be a validation error close to the field\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Verify if e-mail already exists with Ajax call/README.md",
    "content": "**Client Script**\n\nClient script for verification if changed e-mail adders on user record is not already existing in sys_user table (real-time information about duplicated e-mail). Check is performed using asynchronous Ajax call and processed in callback function. In case e-mail already exists in sys_user table, message is displayed under email field with information which has have that e-mail.\n\n**How to use**\n\nYou need to prepare both Script Include (which is processing check on backed) and Client Script (which is sending Ajax call after change on email field and display message).\n\nExample Script Include configuration (code in [scriptInclude.js](scriptInclude.js)):\n![Coniguration_SI](ScreenShot_2.PNG)\n\nExample Client Script configuration (code in [clientScript.js](clientScript.js)):\n![Coniguration_CS](ScreenShot_1.PNG)\n\n**Example effect of execution**\n\n![Coniguration_SI](ScreenShot_3.PNG)\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Verify if e-mail already exists with Ajax call/clientScript.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n\n    //Return if page is loading or new value of e-mail is empty\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    //Make Ajax call to check if e-mail already exists in sys_user table\n    var ga = new GlideAjax('user_CheckEmail'); //user_CheckEmail - Script Include name\n    ga.addParam('sysparm_name', 'validateEmail'); //sysparm_name - Parameter with function name in Script Include\n    ga.addParam('sysparm_emailString', newValue); //sysparm_emailString - Parameter with new value of e-mail \n    ga.getXMLAnswer(verifyDuplicates); //verifyDuplicates - Name of asynchronous callback function \n\n    //Asynchronous callback function to process response\n    function verifyDuplicates(response) {\n\n        //If repsonse is not null (in case if e-mail was not find)\n        if (response) {\n\n            //Parse response and show message about found duplicate\n            var data = JSON.parse(response);\n            g_form.showFieldMsg('email', 'User with that e-mail already exists: ' + data.name + '(' + data.sys_id + ')', 'error');\n        }\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Verify if e-mail already exists with Ajax call/scriptInclude.js",
    "content": "var user_CheckEmail = Class.create();\nuser_CheckEmail.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    validateEmail: function() {\n\n        //Get new value of e-mail field\n        var emailString = this.getParameter('sysparm_emailString');\n\n        //Query user table to verify if new e-mail already exists\n        var user = new GlideRecord('sys_user');\n        user.addQuery('email', emailString);\n        user.query();\n\n        //If e-mail already exists, return user name and sys_id\n        if (user.next()) {\n\n            var results = {\n                \"sys_id\": user.getValue(\"sys_id\"),\n                \"name\": user.getValue(\"name\")\n            };\n            return JSON.stringify(results);\n\n        //If e-mail not exists, return null\n        } else {\n            return null;\n        }\n    }\n});\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Verify whether a date falls within a hour range/README.md",
    "content": "A code snippet that verifies whether a date falls within a specific hour range.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Verify whether a date falls within a hour range/verifyWhetherADateFallsWithinAHourRange.js",
    "content": "/**\n * Verify whether a date falls within a hour range\n */\nfunction onChange() {\n  var dateNow = new Date();\n\tvar dateOpenedAt = new Date(g_form.getValue('opened_at'));\n\tvar differenceInMilliseconds = dateOpenedAt.getTime() - dateNow.getTime();\n\tvar differenceInHours = diffInMs / (1000 * 60 * 60);\n\t\n\tif (differenceInHours < 24) {\n\t  g_form.showFieldMsg('opened_at', 'Please choose a date and time that is at least 24 hours in the future.');\n\t\treturn false;\n\t}\n\t\n\treturn true;\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Whitespace Validation/README.md",
    "content": "This Client Script will validate whether the field contains any whitespace.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Whitespace Validation/whitespaceValidation.js",
    "content": "// Client Script to Validate Whitespaces\nvar reg = /\\s/;\nvar value = g_form.getValue('field_name');\nvar k = reg.test(value);\n\nif (k == true) {\n    alert('Field Name cannot have spaces!'); // Alert if field contains whitespace\n    g_form.setValue('field_name', ''); // Empty field for any whitespaces\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Zurich - Upgraded info messages/README.md",
    "content": "**This feature will be used in the instance of Zurich++ release**\n\nDemonstrate different messages that has been introduced as part of Zurich release.\n\nUse Case: Display different information messages based on priority of the incident that will be showed on load and state is not Closed, Resolved or Cancelled.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/Zurich - Upgraded info messages/infoMessages.js",
    "content": "function onLoad() {\n    var state = g_form.getValue('state'); //Get value of 'state' field\n\n    if (state != '6' && state != '7' && state != '8') {\n        var priority = g_form.getValue('priority'); // Get value of 'priority' field\n        switch (priority) {\n            case '1':\n                g_form.addErrorMessage('Critical Incident'); \n                break; \n            case '2':\n                g_form.addHighMessage('High Priority Incident'); // addHighMessage() method will display message in orange color\n                break;\n            case '3':\n                g_form.addModerateMessage('Medium Priority Incident'); // addModerateMessage() method will display message in purple color\n                break;\n            case '4':\n                g_form.addLowMessage('Low Priority Incident'); // addLowMessage() method will display message in grey color\n                break;\n        }\n    } else if (state == '6' || state == '7') {\n        g_form.addSuccessMessage('Incident closed'); // addSuccessMessage() method will display message in green color\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/field-character-counter/README.md",
    "content": "# Field Character Counter\n\n## Use Case\nProvides real-time character count feedback for text fields in ServiceNow forms. Shows remaining characters with visual indicators to help users stay within field limits.\n\n## Requirements\n- ServiceNow instance\n- Client Script execution rights\n- Text fields with character limits\n\n## Implementation\n1. Create a new Client Script with Type \"onChange\"\n2. Copy the script code from `script.js`\n3. Configure the field name and character limit in the script\n4. Apply to desired table/form\n5. Save and test\n\n## Configuration\nEdit these variables in the script:\n\n```javascript\nvar fieldName = 'short_description';  // Your field name\nvar maxLength = 160;                  // Your character limit\n```\n\n## Features\n- Real-time character counting as user types\n- Visual indicators: info (blue), warning (yellow), error (red)\n- Shows \"X characters remaining\" or \"Exceeds limit by X characters\"\n- Automatically clears previous messages\n\n## Common Examples\n```javascript\n// Short Description (160 chars)\nvar fieldName = 'short_description';\nvar maxLength = 160;\n\n// Description (4000 chars)\nvar fieldName = 'description';\nvar maxLength = 4000;\n\n// Work Notes (4000 chars)\nvar fieldName = 'work_notes';\nvar maxLength = 4000;\n```\n\n## Message Thresholds\n- **50+ remaining**: Info message (blue)\n- **1-20 remaining**: Warning message (yellow)\n- **Over limit**: Error message (red)\n\n## Notes\n- Uses standard ServiceNow APIs: `g_form.showFieldMsg()` and `g_form.hideFieldMsg()`\n- Create separate Client Scripts for multiple fields\n- Works with all text fields and text areas\n- Character count includes all characters (spaces, punctuation, etc.)"
  },
  {
    "path": "Client-Side Components/Client Scripts/field-character-counter/script.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading) return;\n    \n    // USER CONFIGURATION: Set field name and character limit\n    var fieldName = 'Description';  // Change to your field name\n    var maxLength = 80;                  // Change to your character limit\n    \n    var currentLength = newValue ? newValue.length : 0;\n    var remaining = maxLength - currentLength;\n    \n    // Clear any existing messages\n    g_form.hideFieldMsg(fieldName);\n    \n    // Show appropriate message based on remaining characters\n    if (remaining < 0) {\n        g_form.showFieldMsg(fieldName, 'Exceeds limit by ' + Math.abs(remaining) + ' characters', 'error');\n    } else if (remaining <= 20) {\n        g_form.showFieldMsg(fieldName, remaining + ' characters remaining', 'warning');\n    } else if (remaining <= 50) {\n        g_form.showFieldMsg(fieldName, remaining + ' characters remaining', 'info');\n    }\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/g_form console access in workspace/README.md",
    "content": "# Access g_form instance inside Agent Workspace from DevTools Console\nWhen developing forms in ServiceNow it can be useful to try stuff out directly in the DevTools Console.\nIn UI16 this was pretty straightforward because g_form was available globally, Agent Workspace makes this a little bit more complicated.\nSo this script provides access to the g_form object of the currently active tab in a Workspace.\n\nJust copy the Script in the DevTools Console and run `var g_form = getGlideFormAW()` \nnow you should be able to do stuff like `g_form.setValue(\"short_description\", \"Lorem ipsum\")`"
  },
  {
    "path": "Client-Side Components/Client Scripts/g_form console access in workspace/script.js",
    "content": "function getGlideFormAW() {\n    document.getElementsByTagName(\"sn-workspace-content\")[0].shadowRoot.querySelectorAll(\"now-record-form-connected\")[0]\n\n    var firstContentChild = document.getElementsByTagName(\"sn-workspace-content\")[0].shadowRoot\n        .querySelectorAll(\".chrome-tab-panel.is-active\")[0].firstChild;\n\n    var snWorkspaceFormEl;\n    if (firstContentChild.tagName == \"NOW-RECORD-FORM-CONNECTED\") {\n        snWorkspaceFormEl = firstContentChild.shadowRoot.querySelectorAll(\".sn-workspace-form\")[0];\n    } else {\n        snWorkspaceFormEl = firstContentChild.shadowRoot.querySelectorAll(\"now-record-form-connected\")[0]\n            .shadowRoot.querySelectorAll(\".sn-workspace-form\")[0];\n    }\n    if (!snWorkspaceFormEl) throw \"Couldn't find sn-workspace-form\";\n\n    var reactInternalInstanceKey = Object.keys(snWorkspaceFormEl).find(function (objKey) {\n        if (objKey.indexOf(\"__reactInternalInstance$\") >= 0) {\n            return true;\n        }\n        return false;\n    });\n    return snWorkspaceFormEl[reactInternalInstanceKey].return.stateNode.props.glideEnvironment._gForm;\n}"
  },
  {
    "path": "Client-Side Components/Client Scripts/onfocus and onblur/README.md",
    "content": "Using \"onfocus\" & \"onblur\" Show/Hide field messages while updating a field.\n\nExample:On load Client script on the incident form\n\n![image](https://user-images.githubusercontent.com/42912180/195825979-e69e5798-a241-4fe8-8f49-7f70f1f3ae6e.png)\n\n\n\n**Quick video how it works:**\n\n\nhttps://user-images.githubusercontent.com/42912180/195825799-aff13ca5-0b85-4660-98a3-ea1af8b61974.mp4\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/onfocus and onblur/script.js",
    "content": "g_form.getElement(\"Field Name\").onfocus = focus;\ng_form.getElement(\"Field Name\").onblur = blur;\n}\n\n//function definition \n\nfunction focus() {\n  g_form.showFieldMsg(\"Field Name\", \"Message you want to display\");\n}\n\nfunction blur() {\n  g_form.hideFieldMsg(\"Field Name\");\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/state-edit-for-grpmem/README.md",
    "content": "This code will make state field editable only for group members, with the help of scratchpad variable that returns true or false from display business rule.\n\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/state-edit-for-grpmem/grpmemstateedit.js",
    "content": "//Create a Display Business Rule with the following code and then the following code on \"On-Load\" client script\n\n//Display Business Rule Code:\n/*****\ng_scratchpad.grpmember = gs.getUser().isMemberOf(current.assignment_group); //This returns true or false . If user is part of the group it returns true, if user is not part of the group it returns false and assign it to scratchpad variable.\n*****/\n\nif(g_scratchpad.grpmember == false)\n{\ng_form.setReadonly(\"state\", true);\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/validate phone number/README.md",
    "content": "Purpose: Ensures that users enter their phone numbers in a specific format. Phone number must be in format(123) 456-7890.\nThe regex:-  \n1) ^\\(\\d{3}\\) \\d{3}-\\d{4}$ validates phone numbers in the format:\n2) An area code enclosed in parentheses (e.g., (123)),\n3) Followed by a space,\n4) Then three digits,\n5) A hyphen,\n6) And finally four digits.\n\nFunctionality: This script is triggered when the phone field changes. It uses a regular expression to validate the format. If the format is incorrect, it displays an error message and clears the field.\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/validate phone number/Readme.md",
    "content": "Phone Number Validation — Client Script \nOverview\n\nThis Client Script validates that users enter their phone numbers in the strict format: (123) 456-7890.\n\nIt is triggered whenever the Phone field changes on a sys_user record. If the input does not match the required format, the script:\n\nDisplays an inline error message directly below the field.\n\nClears the invalid input so the user can re-enter the correct value.\n\nThis script is designed to be dynamic, simple, and user-friendly.\n\nFeatures\n\nEnsures phone numbers follow the exact format (123) 456-7890.\n\nProvides immediate feedback via field-level error messages.\n\nClears invalid entries automatically to prevent submission errors.\n\nWorks on Classic UI forms and provides clear messaging to the user.\n\nUsage Instructions\n1. Create the Client Script\n\nNavigate to System Definition → Client Scripts.\n\nClick New to create a client script.\n\n2. Configure the Script\n\nName: Phone Number Validation\n\nTable: sys_user \n\nType: onChange\n\nField: phone\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/validate phone number/validate phone number.js",
    "content": "// Client Script: Validate Phone Number\n// Table: sys_user\n// Type: onChange\n// Field: phone\n\nfunction onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue === '') return;\n\n    var phoneRegex = /^\\(\\d{3}\\) \\d{3}-\\d{4}$/; // Format: (123) 456-7890\n    if (!phoneRegex.test(newValue)) {\n        g_form.showFieldMsg('phone', 'Phone number must be in the format (123) 456-7890', 'error');\n        g_form.setValue('phone', '');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/Client Scripts/validate phone number/validate_phone_format_(123)_456-7890_no_regex.js.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || !newValue) return;\n\n    var fieldName = control.name;\n\n    // Split the string\n    var area = newValue.substring(1, 4);\n    var firstThree = newValue.substring(6, 9);\n    var lastFour = newValue.substring(10, 14);\n\n    if (\n        newValue[0] !== '(' || newValue[4] !== ')' || newValue[5] !== ' ' || newValue[9] !== '-' ||\n        isNaN(parseInt(area)) || isNaN(parseInt(firstThree)) || isNaN(parseInt(lastFour))\n    ) {\n        g_form.showFieldMsg(\n                fieldName,\n                'Phone Number must be in the format (123) 456-7890',\n                'error',\n                false\n            );\n        g_form.setValue(fieldName, '');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Add Loggedin user as Incident assigned to/ReadMe.md",
    "content": ">**UI Action**\nWhen a new Incident record is created, user can come to incident ticket and assigned to themself. Once they click on UI Action.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Add Loggedin user as Incident assigned to/code.js",
    "content": "var currentUser = gs.getUserID(); //Getting loggedIn User Id\n\n//Checing wheather user is available or not in Assignee field\nif(current.assigned_to == \"\"){ //checking assigned to is there or not\n\tcurrent.assigned_to = currentUser; //Setting the current loggedIn user\n\tcurrent.update(); //updating the record.\n\tgs.addInfoMessage(\"Incident has been assigned to You.\");\n\taction.setRedirectURL(current);\n} else {\n\tgs.addErrorMessage(\"Incident is already assigned\");\n\taction.setRedirectURL(current);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Add Show Workflow Related link/README.md",
    "content": "There are many cases where,we would have workflows on custom tables or non task tables , where we would like to see the \"Show Workflow\" Related Link for ease of accessibility to the workflow.\nThe shared code will help show this related link on any rable record that has a workflow associated with it.\n\nBelow has to be set for this to work on UI action form:\n\n•\tTable: Select the table that you would like this UI action to be available on (preferably table with workflows)\n\n•\tOnclick : showWorkflow()\n\n•\tActive : True\n\n•\tShow Update : True\n\n•\tClient : True\n\n•\tForm Link : True\n\n•\tCondition : new global.Workflow().hasWorkflow(current)\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Add Show Workflow Related link/script.js",
    "content": "// -----------------------------\n// Open workflow in a new window\n// -----------------------------\nfunction showWorkflow() {\n    var url = new GlideURL('/context_workflow.do');\n    url.addParam('sysparm_stack', 'no');\n    url.addParam('sysparm_document', g_form.getUniqueValue());\n\turl.addParam('sysparm_table', g_form.getTableName());\n\tg_navigation.open(url.getURL(), \"_blank\");\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Add collapsible element in knowledge article/README.md",
    "content": "This code snippet will allow you to use collapsible element within knowledge atricle which will make articles clean, organized and effective.\n\n![Demo](https://github.com/abhrajyotikanrar/code-snippets/assets/25823899/e3ad356e-a5c5-4f2d-aafa-20f89b0da248)\n\nPlease check out the above demo on how this code-snippet can be used.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Add collapsible element in knowledge article/UI_Action.js",
    "content": "/*\nThis script should be placed in the UI action on the table kb_knowledge form view.\nThis UI action should be marked as client.\nUse addCollapsible() function in the Onclick field.\n*/\n\nfunction addCollapsible() {\n    var gm = new GlideModal(\"add_collapsible\");\n    gm.setTitle('Add collapsible');\n    gm.setWidth(1000);\n    gm.render();\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Add collapsible element in knowledge article/add_collapsible.js",
    "content": "******HTML*****\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\n\t<style>\n\t\t.hide{\n\t\t\tvisibility: hidden;\n\t\t}\n\t\t.show{\n\t\t\tvisibility: visible;\n\t\t}\n\t</style>\n\n\t<label for=\"collapsibleTitle\">Title</label>\n    <input type=\"text\" class=\"form-control\" id=\"collapsibleTitle\" placeholder=\"Your collapsible title goes here\" />\n\n\t<br />\n\n\t<label for=\"collapsibleContent\">Content body</label>\n    <textarea class=\"form-control\" id=\"collapsibleContent\" rows=\"3\"></textarea>\n\n\t<br />\n\n\t<div class=\"alert alert-danger hide\" id=\"alert\" role=\"alert\">\n\t\t<b>Title</b> and <b>Content body</b> should not be empty.\n\t</div>\n\n\t<br/>\n\t\n\t<button type=\"button\" class=\"btn btn-primary\" style=\"float: right;\" onclick=\"updateForm()\">Add collapsible</button>\n</j:jelly>\n****************\n\n**Client Script**\nfunction updateForm() {\n    $j(\"#alert\").removeClass(\"show\");\n    $j(\"#alert\").addClass(\"hide\");\n\n    var title = $j(\"#collapsibleTitle\").val();\n    var content = $j(\"#collapsibleContent\").val();\n\n    if (title.trim() == \"\" || content.trim() == \"\") {\n        $j(\"#alert\").removeClass(\"hide\");\n        $j(\"#alert\").addClass(\"show\");\n\n        return;\n    }\n\n    var articleText = g_form.getValue(\"text\");\n    var collapsibleElement = \"<details style='width: 100%; border: 1px solid #ccc;'><summary style='cursor: pointer; padding: 15px; background: #e8e8e8;'>\" + title.trim() + \"</summary><div style='padding: 15px;'>\" + content.trim() + \"</div></details>\";\n\n\tg_form.setValue(\"text\", articleText + collapsibleElement);\n\tGlideDialogWindow.get().destroy()\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Call Subflow/README.md",
    "content": "# Call Subflow from UI Action\n\nThis UI Action enables calling a subflow from ServiceNow Flow Designer using the FlowAPI.\n\n### Instruction\n\nThe call can either be done synchronously or asynchronously, depending on the requirement. \n```javascript\n//Synchronous Call: \nsn_fd.FlowAPI.getRunner().subflow('global.subflow_name').inForeground().withInputs(inputs).run();\n//Asynchronous Call: \nsn_fd.FlowAPI.getRunner().subflow('global.subflow_name').inBackground().withInputs(inputs).run();\n```\n\nThe API call requires the subflow internal name and scope. The internal name is shown as a second column in the subflow overview in ServiceNow Flow Designer, while the scope is shown in the third column. In the above example the subflow internal name is **subflow_name** and it was created in the **global** scope.\n\n### Benefits\n- Enable Citizen Developers to create complex UI Actions with low code Flow Designer capabilities instead of scripting\n- Run complex server side UI Actions asynchronously via Flow Designer for better user experience (avoids long loading times in the current user session)\n- Re-use already created subflows and actions as well as provided spokes in UI actions\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Call Subflow/script.js",
    "content": "(function() {\n\t\n\ttry {\n    \t\t// Provide subflow inputs\n\t\tvar inputs = {};\n\t\tinputs['incident'] = current; //the current incident record can be provided as input to the subflow\n    \n    \t\t//The subflow can either be executed synchronously (running in foreground) or asynchronously (running in background)\n    \t\t//If the flow runs asynchronously the FlowAPI will not provide the outputs of the subflow\n    \n    \t\t//Asynchronous call\n\t\t// sn_fd.FlowAPI.getRunner().subflow('global.subflow_name').inBackground().withInputs(inputs).run();\n    \n    \t\t//Synchronous call\n\t\t// sn_fd.FlowAPI.getRunner().subflow('global.subflow_name').inForeground().withInputs(inputs).run();\n    \n    \t\t//In this case we are calling the subflow global.create_problem_from_incident synchronously and then access the subflow outputs\n\t\tvar result = sn_fd.FlowAPI.getRunner().subflow('global.create_problem_from_incident').inForeground().withInputs(inputs).run();\n\t\tvar outputs = result.getOutputs();\n\n\t\t// Get subflow outputs:\n\t\tvar problem_number = outputs['problem_number'];\n\t\tvar assignment_group = outputs['assignment_group'];\n\t\t\n\t} catch (ex) {\n\t\tvar message = ex.getMessage();\n\t\tgs.error(message);\n\t}\n\t\n})();\n"
  },
  {
    "path": "Client-Side Components/UI Actions/CallingPopUpBoxInListView/README.md",
    "content": "Overview\nThis document explains how to implement a custom UI action that triggers a UI page from the list view of a table in ServiceNow. Specifically, it demonstrates how to open a modal dialog when multiple items are selected in the list view. The modal dialog will display a UI page and pass the selected record sys_ids as parameters. This allows users to update multiple records simultaneously through the UI page, streamlining processes such as mass updates. This approach enhances user efficiency by enabling the execution of actions on multiple records at once, reducing manual effort and improving workflow automation\nPurpose of the UI Action\nThe purpose of this UI action is to allow users to select multiple records from the list view and trigger a modal popup that displays a custom UI page. The user can interact with the popup to perform actions to update field value by slecting field values from the pop up. The selected records are passed to the UI page as parameters, ensuring the action is applied to all the checked items.\n\nDocument: Using UI Action to Call a Custom UI Page in List View\nOverview\nThis document explains how to implement a custom UI action that calls a UI page from the list view of a table in ServiceNow. Specifically, it demonstrates how to open a modal dialog when multiple items are selected in the list view. The modal dialog will display a UI page and pass selected record sys_ids as parameters. This can be useful for tasks like requesting an exception, remediation, or other custom actions that require user interaction.\n\nPurpose of the UI Action\nThe purpose of this UI action is to allow users to select multiple records from the list view and trigger a modal popup that displays a custom UI page. The user can interact with the popup to perform actions such as remediation, request handling, or exception requests, depending on the implementation of the UI page. The selected records are passed to the UI page as parameters, ensuring the action is applied to the correct items.\n\nHow It Works\nUI Action Creation:\nA UI action is created in ServiceNow, configured to be available in the list view of a specific table. In this example, the UI action is configured on the Incident table.\n\nJavaScript Function:\nThe function showExceptiondialog() is triggered when the UI action button is clicked. This function does the following:\n\nCheck for selected records:\nThe script checks if any records have been selected in the list view. If no records are selected, an error message is shown to the user.\nOpen Modal Popup:\nIf records are selected, the function opens a modal dialog using either GlideModal or GlideDialogWindow, depending on the environment. The dialog displays a UI page specified by name (incident_pop_up in the example).\nPass Selected Records:\nThe selected record sys_ids are passed as a parameter (sysparm_sys_id) to the UI page, allowing the page to perform actions on those records.\nUI Action Configuration\nTable:\nThe UI action is configured for a specific table, such as the Incident table (incident).\n\nList Context:\nThe UI action should be available in the list view, where multiple records can be selected.\n\nOnClick Event:\nThe UI action calls the JavaScript function showExceptiondialog() when clicked.\n Testing\nNavigate to List View:\nGo to the list view of the table where the UI action is configured (e.g., Incident table).\n\nSelect Records:\nCheck one or more records in the list view.\n\nClick the UI Action Button:\nClick the UI action button that triggers the showExceptiondialog() function. If no records are selected, an error message will appear. If records are selected, the modal dialog will open and display the custom UI page.\n\nVerify Modal:\nEnsure that the modal popup displays the correct UI page and that the selected records’ sys_ids are passed as parameters.\nBenefits\nUser-Friendly:\nThe modal popup provides an intuitive interface for users to perform actions on multiple selected records without leaving the list view.\n\nCustomizable:\nThe UI page can be customized to handle a variety of actions based on the selected records. For example, it can be used for remediation tasks, exception requests, approvals, or other workflows.\n\nEfficiency:\nUsers can quickly perform actions on multiple records at once, reducing the need for repetitive manual operations.\n\nError Handling:\nThe script includes error handling to ensure users are prompted if they attempt to perform an action without selecting records.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/CallingPopUpBoxInListView/calling_pop_up_box_in_list_view.js",
    "content": "function showExceptiondialog() {\n    // Alert to display the checked records\n    alert(g_list.getChecked());\n    \n    // Check if any records are selected in the list\n    if ((g_list.getChecked()).length > 0) {\n        var title = getMessage(\"Remediation Task\");\n        var dialogClass = GlideModal || GlideDialogWindow;\n        \n        // Initialize the modal dialog with a custom UI page\n        var dialog = new dialogClass(\"incident_pop_up\", true, 750);\n        dialog.setTitle(title);\n        \n        // Pass selected record sys_ids as parameters to the UI page\n        dialog.setPreference(\"sysparm_sys_id\", g_list.getChecked());\n        \n        // Render the modal dialog\n        dialog.render();\n    } else {\n        // Show error messages if no records are selected\n        g_form.addErrorMessage(getMessage('Please Select Vulnerable Items before creating remediation'));\n        alert('Please Select Vulnerable Items before creating remediation');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Cancel Flow Executions/README.md",
    "content": "# CancelFlow UI Action\n\nA ServiceNow utility that dynamically cancels flows associated with the current record, ensuring seamless process management.\n\n## Challenge\n\nManaging running flows in ServiceNow can be challenging, particularly when multiple flows are tied to a single record. This utility streamlines the process by offering a dynamic solution to identify and cancel running flows, minimizing manual intervention and ensuring seamless operations.\n\nThis tool is especially useful in scenarios where you need to halt the current execution and initiate a new flow or process. Additionally, it can be leveraged to forcefully terminate the automation lifecycle when necessary, providing greater control over flow management.\n\n## Description\n\nThis UI Action is designed to identify and cancel all running flows associated with the current record in a ServiceNow instance. It provides a user-friendly interface for administrators and developers to manage flow cancellations efficiently. This utility is particularly useful in scenarios where flows need to be terminated to prevent conflicts or errors during record updates.\n\n## Functionality\n\nThe CancelFlow UI Action provides the following capabilities:\n- Dynamically identifies running flows for the current record.\n- Cancels the identified flows programmatically.\n- Displays success or error messages to the user for better visibility.\n- Ensures smooth handling of flow cancellations without manual intervention.\n\n## Usage Instructions\n\n### UI Action Script\n\nAdd the given script to your UI Action:\n\n\n### Example Usage\n\n1. Open the record where you want to cancel the associated flows.\n2. Click on the **Cancel Flow** UI Action button.\n3. The system will identify and cancel all running flows for the current record.\n4. The same can be used in Business rules as well based on trigger conditions\n\n\n### Visibility for  UI Action\n\nIn certain scenarios, it may be necessary to restrict the visibility of this operation to specific user groups. For example, only HR administrators or members of a designated group (e.g., \"X Group\") should have access to this functionality. These requirements can be addressed by configuring the **Condition** field in the UI Action. You can tailor the conditions to align with your specific use case, ensuring that only authorized users can execute this operation. One edge case about not having an active flow execution can also be handled in the condition which will restrict the visibility if no active flow execution is present.\n\n\n## Dependencies\n\n- `sn_fd.FlowAPI`\n\n## Category\n\nClient-Side Components / UI Actions \n"
  },
  {
    "path": "Client-Side Components/UI Actions/Cancel Flow Executions/cancelFlow.js",
    "content": "function cancelRunningFlows() {\n\n  try {\n    var grFlowExecution = new GlideRecord(\"sys_flow_context\");\n    grFlowExecution.addQuery(\"source_record\", current.sys_id);\n    grFlowExecution.query();\n\n    while (grFlowExecution.next()) {\n      sn_fd.FlowAPI.cancel(grFlowExecution.getUniqueValue(), \"Canceling Flows\");\n    }\n  } catch (error) {\n    gs.error(\"Error cancelling flows: \" + error.message);\n  }\n}\n\n\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Cancel Incident/README.md",
    "content": "# Cancel Incident UI Action\n\nA UI Action in ServiceNow is a script that defines an action or button within the platform's user interface. It enables users to perform specific operations on forms and lists, such as creating, updating, or deleting records, or executing custom scripts. UI Actions enhance the user experience by providing functional buttons, links, or context menus.\n\n## Overview\n\nThis UI Action allows users to cancel incidents directly from the incident form. It provides a confirmation dialog to prevent accidental cancellations and updates the incident state to \"Cancelled\" (state value 8) when confirmed.\n\n## Features\n\n- **Confirmation Dialog**: Uses GlideModal to display a confirmation prompt before cancelling\n- **State Management**: Updates incident state to \"Cancelled\" (value 8)\n- **Client-Side Validation**: Runs client-side for better user experience\n- **Conditional Display**: Only shows when incident state is \"New\" (state value 1)\n\n## Configuration\n\nCreate a UI Action with the following field values:\n\n**Name**: Cancel Incident\n\n**Action Name**: cancel_incident\n\n**Table**: Incident [incident]\n\n**Client**: checked (true)\n\n**Onclick**: cancelIncident();\n\n**Condition**: current.state == '1'\n\n**Script**: Use the provided script.js file\n\n## Usage\n\n1. Navigate to an incident record in \"New\" state\n2. Click the \"Cancel Incident\" button\n3. Confirm the action in the modal dialog\n4. The incident state will be updated to \"Cancelled\"\n\n## Technical Details\n\n- **Client-Side Function**: `cancelIncident()` - Displays confirmation modal\n- **Server-Side Function**: `serverCancel()` - Updates the incident state\n- **Modal Configuration**: Uses `glide_ask_standard` modal with custom title\n- **State Value**: Sets incident state to '8' (Cancelled)\n\n## Prerequisites\n\n- User must have write access to the incident table\n- Incident must be in \"New\" state (state = 1) for the UI Action to be visible\n\n## Notes\n\n- This UI Action only appears on incident forms when the state is \"New\"\n- The confirmation dialog helps prevent accidental cancellations\n- The server-side script executes only after user confirmation\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Cancel Incident/SETUP.md",
    "content": "# Setup Instructions for Cancel Incident UI Action\n\nThis document provides detailed step-by-step instructions for implementing the Cancel Incident UI Action in your ServiceNow instance.\n\n## Prerequisites\n\n- Administrative access to ServiceNow instance\n- Access to System Definition > UI Actions module\n- Understanding of ServiceNow UI Actions and client-server scripting\n\n## Step-by-Step Setup\n\n### 1. Navigate to UI Actions\n\n1. In ServiceNow, go to **System Definition > UI Actions**\n2. Click **New** to create a new UI Action\n\n### 2. Configure Basic Settings\n\nFill in the following fields:\n\n| Field | Value | Description |\n|-------|-------|-------------|\n| **Name** | Cancel Incident | Display name for the UI Action |\n| **Table** | Incident [incident] | Target table for the UI Action |\n| **Action name** | cancel_incident | Unique identifier for the action |\n| **Active** | ✓ (checked) | Enables the UI Action |\n\n### 3. Configure Display Settings\n\n| Field | Value | Description |\n|-------|-------|-------------|\n| **Form button** | ✓ (checked) | Shows button on form view |\n| **Form link** | ☐ (unchecked) | Optional: Show as link instead |\n| **List banner button** | ☐ (unchecked) | Not needed for this action |\n| **List choice** | ☐ (unchecked) | Not needed for this action |\n\n### 4. Configure Client Settings\n\n| Field | Value | Description |\n|-------|-------|-------------|\n| **Client** | ✓ (checked) | Enables client-side execution |\n| **Onclick** | `cancelIncident();` | Client-side function to call |\n\n### 5. Configure Conditions\n\n| Field | Value | Description |\n|-------|-------|-------------|\n| **Condition** | `current.state == '1'` | Only show for \"New\" incidents |\n\n### 6. Add the Script\n\nCopy the entire content from `script.js` and paste it into the **Script** field of the UI Action.\n\n### 7. Configure Advanced Settings (Optional)\n\n| Field | Value | Description |\n|-------|-------|-------------|\n| **Order** | 100 | Display order (adjust as needed) |\n| **Hint** | Cancel this incident | Tooltip text |\n| **Comments** | UI Action to cancel incidents in New state | Internal documentation |\n\n## Verification Steps\n\n### 1. Test the UI Action\n\n1. Navigate to an incident in \"New\" state\n2. Verify the \"Cancel Incident\" button appears\n3. Click the button and confirm the modal appears\n4. Test both \"OK\" and \"Cancel\" in the confirmation dialog\n\n### 2. Verify State Changes\n\n1. After confirming cancellation, check that:\n   - Incident state changes to \"Cancelled\"\n   - Work notes are added with cancellation details\n   - Success message appears\n\n### 3. Test Edge Cases\n\n1. Try accessing the UI Action on incidents in other states (should not appear)\n2. Test with different user roles to ensure proper permissions\n3. Verify error handling works correctly\n\n## Troubleshooting\n\n### Common Issues\n\n**UI Action doesn't appear:**\n- Check that the incident is in \"New\" state (state = 1)\n- Verify the condition field: `current.state == '1'`\n- Ensure the UI Action is marked as Active\n\n**Script errors:**\n- Check browser console for JavaScript errors\n- Verify the script is properly copied from `script.js`\n- Ensure proper syntax and formatting\n\n**Permission issues:**\n- Verify user has write access to incident table\n- Check ACL rules for incident cancellation\n- Ensure proper role assignments\n\n### Debug Mode\n\nTo enable debug logging, add this line at the beginning of the `serverCancel()` function:\n\n```javascript\ngs.info('Debug: Starting incident cancellation for ' + current.number);\n```\n\n## Security Considerations\n\n- The UI Action respects existing ACL rules\n- Only users with incident write permissions can cancel incidents\n- All cancellations are logged for audit purposes\n- Work notes provide cancellation history\n\n## Customization Options\n\n### Modify Confirmation Message\n\nEdit line 33 in the script to customize the confirmation dialog:\n\n```javascript\ngm.setPreference(\"question\", \"Your custom message here\");\n```\n\n### Change Cancellation Reason\n\nModify the work note in the `serverCancel()` function (line 79):\n\n```javascript\nvar workNote = 'Custom cancellation reason: ' + gs.getUserDisplayName() + ' on ' + gs.nowDateTime();\n```\n\n### Add Additional Validations\n\nAdd custom validation logic in the `cancelIncident()` function before showing the modal.\n\n## Support\n\nFor issues or questions:\n1. Check ServiceNow system logs\n2. Review browser console for client-side errors\n3. Test in a development instance first\n4. Consult ServiceNow documentation for UI Actions\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Cancel Incident/script.js",
    "content": "/**\n * Client-side function to initiate incident cancellation\n * Displays a confirmation modal before proceeding with the cancellation\n */\nfunction cancelIncident() {\n    try {\n        // Validate that we have a valid form and record\n        if (!g_form || !g_form.getUniqueValue()) {\n            alert('Error: Unable to access incident record. Please refresh the page and try again.');\n            return;\n        }\n\n        // Check if incident is in the correct state for cancellation\n        var currentState = g_form.getValue('state');\n        if (currentState !== '1') {\n            alert('Error: This incident cannot be cancelled. Only incidents in \"New\" state can be cancelled.');\n            return;\n        }\n\n        // Create confirmation modal with improved messaging\n        var gm = new GlideModal(\"glide_ask_standard\", false, 600);\n        gm.setPreference(\"title\", \"Cancel Incident Confirmation\");\n        gm.setPreference(\"warning\", true);\n        gm.setPreference(\"onPromptComplete\", function() {\n            // Show loading message\n            g_form.addInfoMessage('Cancelling incident...');\n            \n            // Submit the form to trigger server-side processing\n            gsftSubmit(null, g_form.getFormElement(), 'cancel_incident');\n        });\n        \n        // Set the confirmation message\n        gm.setPreference(\"question\", \"Are you sure you want to cancel this incident?\\n\\nThis action will change the incident state to 'Cancelled' and cannot be easily undone.\");\n        \n        // Render the modal\n        gm.render();\n        \n    } catch (error) {\n        // Handle any unexpected errors\n        console.error('Error in cancelIncident function:', error);\n        alert('An unexpected error occurred. Please contact your system administrator.');\n    }\n}\n\n/**\n * Server-side execution block\n * This code runs on the server when the UI Action is submitted\n */\nif (typeof window == 'undefined') {\n    serverCancel();\n}\n\n/**\n * Server-side function to cancel the incident\n * Updates the incident state to 'Cancelled' and adds a work note\n */\nfunction serverCancel() {\n    try {\n        // Validate that we have a current record\n        if (!current || !current.isValidRecord()) {\n            gs.addErrorMessage('Error: Invalid incident record.');\n            return;\n        }\n\n        // Double-check the current state before cancelling\n        if (current.state.toString() !== '1') {\n            gs.addErrorMessage('Error: This incident cannot be cancelled. Only incidents in \"New\" state can be cancelled.');\n            return;\n        }\n\n        // Store original values for logging\n        var incidentNumber = current.number.toString();\n        var originalState = current.state.getDisplayValue();\n        \n        // Update the incident state to 'Cancelled' (state value 8)\n        current.state = '8';\n        \n        // Add a work note to document the cancellation\n        var workNote = 'Incident cancelled by ' + gs.getUserDisplayName() + ' on ' + gs.nowDateTime();\n        if (current.work_notes.nil()) {\n            current.work_notes = workNote;\n        } else {\n            current.work_notes = current.work_notes + '\\n\\n' + workNote;\n        }\n        \n        // Update the record\n        current.update();\n        \n        // Log the action for audit purposes\n        gs.info('Incident ' + incidentNumber + ' cancelled by user ' + gs.getUserName() + \n                '. State changed from \"' + originalState + '\" to \"Cancelled\"');\n        \n        // Provide user feedback\n        gs.addInfoMessage('Incident ' + incidentNumber + ' has been successfully cancelled.');\n        \n    } catch (error) {\n        // Handle server-side errors\n        gs.error('Error cancelling incident: ' + error.message);\n        gs.addErrorMessage('An error occurred while cancelling the incident. Please contact your system administrator.');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Clone incident on Agent Workspace/README.md",
    "content": "Agent can use this UI Action on incident form to clone/copy any existing incident.\n\nThis UI Action will create a copy of incident once agent confirm the action.\n\nCaller field will not be copeied to newly created incident, only basic information of ticket like Company, Short Description, Category, Sub-Category \n\nCreate an UI Action with below field values:\n\nName - Clone Incident\n\nAction Name - clone_incident\n\nTable - Incident\n\nClient - checked (true)\n\nOnclick - cloneIncident();\n\nWorkspace Form Button - checked (true)\n\nScript - use clone_incident.js\n\nWorkspace Client script - use workspace_client_script.js\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Clone incident on Agent Workspace/clone_incident.js",
    "content": "function cloneIncident() {\n    var answer = confirm(getMessage(\"Are you sure you want to Clone this Incident?\"));\n    if (answer)\n        gsftSubmit(null, g_form.getFormElement(), 'clone_incident');\n    else\n        return false;\n}\n\nif (typeof window == 'undefined') {\n    //Clone Incident\n    var grInc = new GlideRecord('incident');\n    grInc.initialize();\n    grInc.company = current.company;\n    grInc.short_description = current.short_description;\n    grInc.description = current.description;\n    grInc.contact_type = \"Self-service\";\n    grInc.category = current.category;\n    grInc.subcategory = current.subcategory;\n    grInc.setDisplayValue('assignment_group', \"Assignment Group Name\"); // or use grInc.assignment_group = current.assignment_group.toString();\n    /*\n\tuncomment this code if comments need to be copied\n    //Remove Timestamp from Comments\n    var getComments = current.comments.getJournalEntry(1);\n    var regex = new RegExp(\"\\n\");\n    var returnComments = getComments;\n    var getRegex = getComments.search(regex);\n    if (getRegex > 0) {\n        returnComments = getComments.substring(getRegex + 1, getComments.length);\n    }\n    gr.comments = returnComments;\n    */\n    grInc.insert();\n    action.setRedirectURL(grInc);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Clone incident on Agent Workspace/workspace_client_script.js",
    "content": "function onClick() {\ngetMessage(\"Are you sure you want to Clone this Incident?\", function (msg) {\n\t\tg_modal.confirm(getMessage(\"Confirmation\"), msg, function (confirmed) {\n\t\t\tif (confirmed) {\n\t\t\t\tg_form.submit('clone_incident');\n\t\t\t}\n\t\t});\n\t});\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Close Related HR cases & HR tasks/README.md",
    "content": "Scenario:-\n\nTable: HR Case\n\nCreate a form button named \"Check related item and Close Complete\" feature and list down the related child HR cases and HR tasks\nin the pop-up message.\nUpon confirmation, it will close the current case and other listed items.\n\nThis will help in reducing the manual effort of closing items manually.\n\nScripts:\nClient UI script to handle the confirmation popup and state of current case.\n\nGlideAJAX enabled script include to fetch the data and close the related items.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Close Related HR cases & HR tasks/Script Include.js",
    "content": "var close_item = Class.create();\nclose_item.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\tgetRelatedItems: function() {\n        var caseId = this.getParameter('sysparm_case_id');\n        var results = [];\n\n        // Get child HR cases\n        var childCases = new GlideRecord('sn_hr_core_case');\n        childCases.addQuery('parent', caseId);\n        childCases.query();\n        while (childCases.next()) {\n            results.push({ type: 'HR Case', number: childCases.getValue('number') });\n        }\n\n        // Get tasks\n        var tasks = new GlideRecord('sn_hr_core_task');\n        tasks.addQuery('hr_case', caseId);\n        tasks.query();\n        while (tasks.next()) {\n            results.push({ type: 'HR Task', number: tasks.getValue('number') });\n        }\n\n        return JSON.stringify(results);\n    },\n\tcloseRelatedItems: function() {\n        var caseId = this.getParameter('sysparm_case_id');\n\n        // Close child cases\n        var childCases = new GlideRecord('sn_hr_core_case');\n        childCases.addQuery('parent', caseId);\n        childCases.query();\n        while (childCases.next()) {\n            childCases.setValue('state', '3');\n            childCases.update();\n        }\n\n        // Close tasks\n        var tasks = new GlideRecord('sn_hr_core_task');\n        tasks.addQuery('hr_case', caseId);\n        tasks.query();\n        while (tasks.next()) {\n            tasks.setValue('state', '3');\n            tasks.update();\n        }\n\n        return \"done\";\n\t\t\n    },\n    type: 'close_task'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Close Related HR cases & HR tasks/UI Action script.js",
    "content": "// Demo- OnClick function to execute\nfunction demo() {\n    var ga = new GlideAjax('sn_hr_core.close_items');\n    ga.addParam('sysparm_name', 'getRelatedItems');\n    ga.addParam('sysparm_case_id', g_form.getUniqueValue());\n    ga.getXMLAnswer(function(response) {\n      // If there exist related items\n        var items = JSON.parse(response);\n        if (items.length > 0) {\n            var msg = \"This case has related items:\\n\";\n            items.forEach(function(item) {\n                msg += \"- \" + item.type + \": \" + item.number + \"\\n\";\n            });\n            msg += \"\\nDo you want to close them as well?\";\n            if (confirm(msg)) {\n        // close current HR case\n\t\t\t\tg_form.setValue('state', '3');\n\t\t\t\tg_form.save();\n            }\n        } else {\n          // If no related item is associated\n            if (confirm(\"No related items found. Close this case?\")) {\n                g_form.setValue('state', '3');\n                g_form.save();\n            }\n        }\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Close child incident/README.md",
    "content": "This is a ui actions that close the child incident directly from the parent incident\n\nTwo actions for this :\n1. Client side ui action from which button is shown and onClick of that button server side action will be performed\n2. Server side ui action that update the state of that child incident to closed\n\n### update\nUpdated client script to replace JavaScript function confirm() with GlideModal() API.\nTo complete a on issue #745 (Close child incident UI Action)\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Close child incident/clientScript.js",
    "content": "function popUpClientScript(){\n\t\n\tvar actionCallbackOK = function() {\n        \tgsftSubmit(null,g_form.getFormElement(),'sys_demoaction'); // calling server ui action to update state\n    \t};\n    \tvar actionCallbackCancel = function() {\n         \t//do nothing\n    \t};\n\n\tvar gm = new GlideModal('glide_confirm_basic',false); //UI page with logic to confirm\n    \tgm.setTitle(\"Are you sure you want to close the attached child incident\"); // confirm message to ask for confirmation\n\tgm.setPreference('onPromptComplete', actionCallbackOK.bind(this)); //bind to local function to take action when selected Ok\n    \tgm.setPreference('onPromptCancel', actionCallbackCancel.bind(this)); //bind to local function to take action when selected Cancel\n    \tgm.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Close child incident/serverScript.js",
    "content": "var gr = new GlideRecord('incident');\ngr.addQuery('parent_incident',current.sys_id); //querying over particular parent incident\ngr.query();\nwhile(gr.next()){\n\tgr.state = '7'; //updating the state of the child incident to closed\n\tgr.update();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/CloseChildCases/CloseChildCases.js",
    "content": "(function executeAction() {\n    var grCase = new GlideRecord('sn_customerservice_case');\n    grCase.addQuery('parent', current.sys_id);\n    grCase.query();\n    \n    var counter = 0;\n    while (grCase.next()) {\n        if (grCase.state != 3) { // 3 = Closed\n            grCase.resolution_code = '16';\n            grCase.close_notes = 'This case was auto closed from the parent case.';\n            grCase.state = 3;\n            grCase.update();\n            counter++;\n        }\n    }\n\n    //  Show info message only if any cases were closed\n    if (counter > 0) {\n        gs.addInfoMessage(counter + ' child case(s) have been closed.');\n    }\n\n    action.setRedirectURL(current);\n})();\n"
  },
  {
    "path": "Client-Side Components/UI Actions/CloseChildCases/README.md",
    "content": "Name: Close all Child Case\nTable:sn_customerservice_case\nCondition: (gs.hasRole('sn_customerservice_agent') || gs.hasRole('admin') ) && (new GlideRecord('sn_customerservice_case').addQuery('parent', current.sys_id).query().hasNext())\n\nUse Case: \nProvide UI action button to close all the associated child cases from the parent Case.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Convert Request to Incident/README.md",
    "content": "This is a UI Action that creates an Incident using the field values of the current Request and closes the Request as \"Closed Skipped\".\nIt also compliles all the worknotes and comments into a single worknote on the Incident.\n\nThis action has an OnClick function as well as a server-side function that runs using:\n\nif (typeof current != 'undefined')\n\nThe OnClick function opens a confirmation window to protect against misclicks.\n\nSetting up the UI Action:\n\n![alt text](https://github.com/ezratkim/code-snippets/blob/main/UI%20Actions/Convert%20Request%20to%20Incident/UIActionScreenshot.png)\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Convert Request to Incident/script.js",
    "content": "//Prompts confirmation window on click\nfunction ReqWarning() {\n    var answer = confirm(\"Please confirm Request to Incident Action. \\n This will set the current Request to 'Closed Skipped' and create a new Incident.\");\n\n    if (answer == false) {\n        return false;\n    }\n\n    gsftSubmit(null, g_form.getFormElement(), 'create_inc_cancel_req');\n\n}\n\n// Ensure this runs on the server side\nif (typeof current != 'undefined') {\n    // Create a new incident record\n    var inc = new GlideRecord('incident');\n    inc.initialize();\n\n    // Map fields from the task to the incident\n    inc.short_description = current.short_description;\n\tinc.description = current.special_instructions;\n    inc.caller_id = current.requested_for;\n\tinc.watch_list = current.watch_list;\n\tinc.assigned_to = current.assigned_to;\n\tinc.state = 1; // Sets state to 'New'\n\tinc.contact_type = 'self-service';\n\tinc.assignment_group = 'group_sys_id'; // Assign to your preferred assignment group's sys_id\n\n\t// Construct the initial work note for the Incident with a link back to the original\n\tvar callerName = current.requested_for.getDisplayValue();\n\tvar currentLink = \"[code]<a href='\" + current.getLink() + \"'>\" + current.number + \"</a>[/code]\";\n\tvar initialJournalEntry = gs.getMessage(\"This incident was converted from {0} on behalf of {1}\" , [currentLink, callerName]);\n\tinc.work_notes = initialJournalEntry;\n\t\n    // Initialize a variable to compile the work notes and comments\n    var compiledEntries = \"Compiled Work Notes and Comments:\\n=================================\\n\\n\";\n\t\n    // Query combined work notes and comments from the current task\n    var journal = new GlideRecord('sys_journal_field');\n    journal.addQuery('element_id', current.sys_id);\n    journal.addQuery('element', 'IN', 'work_notes,comments'); // Fetch work notes and comments\n    journal.orderBy('sys_created_on'); // Ensures chronological order\n    journal.query();\n\n    while (journal.next()) {\n\t\tvar entryType = journal.element == 'work_notes' ? 'Work Note' : 'Comment';\n\t\tvar entryTimestamp = journal.sys_created_on.getDisplayValue();\n\n\t\tvar userRecord = new GlideRecord('sys_user');\n\t\tvar entryCreatedBy = 'Unknown User';  // Default value in case user is not found\n\n\t\t// Query the sys_user table based on the user_name stored in sys_created_by\n\t\tuserRecord.addQuery('user_name', journal.sys_created_by);\n\t\tuserRecord.query();\n\n\t\tif (userRecord.next()) {\n\t\t\tvar firstName = userRecord.first_name;\n\t\t\tvar lastName = userRecord.last_name;\n\n\t\t\t// Concatenate first name and last name to form the full name\n\t\t\tentryCreatedBy = firstName + ' ' + lastName;\n\t\t}\n\t\tvar entryText = journal.value;\n\n\t\t// Format the entry with structured and visually separated format\n\t\t\tcompiledEntries += entryType + \" - \" + entryTimestamp + \" - \" + entryCreatedBy + \":\\n\" +\n\t\t\t\t\t\t\t\"--------------------------------------------------------\\n\" +\n\t\t\t\t\t\t\t\"\\\"\" + entryText + \"\\\"\\n\" +\n\t\t\t\t\t\t\t\"--------------------------------------------------------\\n\\n\";\t\n\t}\n\n    // Add the compiled entries as a work note to the new incident\n    if (compiledEntries != \"Compiled Work Notes and Comments:\\n=================================\\n\\n\") {\n        inc.work_notes = compiledEntries;\n    }\n\n    // Insert the new incident record to get a sys_id for work notes and comments transfer\n    var incSysId = inc.insert();\n\n    // Check if successful incident record creation, set closing fields, and inform user\n    if (incSysId) {\n\t\t// Set fields on current record\n\t\tcurrent.request_state = 'closed_cancelled';\n\t\tcurrent.state = 7; // Sets state to 'Closed Skipped'\n\t\tvar incLink = \"[code]<a href='\" + inc.getLink() + \"'>\" + inc.number + \"</a>[/code]\";\n\t\tcurrent.work_notes = gs.getMessage(\"Converted to Incident: \" + incLink);\n\t\tcurrent.update();\n\n        gs.addInfoMessage(\"Incident created from Request: \" + inc.number);\n\n\t\t// Redirect to the newly created incident\n\t\taction.setRedirectURL(inc);\n    } else {\n\t\tgs.addErrorMessage(\"Failed to convert\");\n\t}\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Copy Bulk SysIDs/Copy Bulk Sysids.js",
    "content": "var sysIds = g_list.getChecked();\ncopyToClipboard(sysIds);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Copy Bulk SysIDs/README.md",
    "content": "# Copy SysIDs in Bulk — ServiceNow Utility\n\n> Simplify copying checked sys_ids from a list view with a one-click UI Action.  \n\n---\n\n## Purpose / Use Case\n\nOften, you may need to extract sys_ids from records listed in a ServiceNow list view (for scripting, validations, data workflows, etc.). Instead of exporting CSVs or manually gathering IDs, this utility enables direct copying of the selected records’ sys_ids (comma-separated) from the list itself.\n\n---\n\n## How It Works\n\nIt adds a global UI Action (on lists) that, when clicked, collects the sys_ids of checked records and copies them to the clipboard using a small client-side script.\n\n---\n\n## Installation Steps\n\n1. Navigate to **System Definition > UI Actions**.\n2. Create a **new UI Action** with these settings:\n   - **Name**: e.g. `Copy Bulk SysIDs`\n   - **Table**: `Global` (so it works on every list)\n   - **Check** the **Client** and **List** checkboxes (so it appears in list context on client side)  \n3. In the **Onclick / Client script** field, paste:\n\n   ```javascript\n   var sysIds = g_list.getChecked();\n   copyToClipboard(sysIds);\n\n## Result\n<img width=\"1829\" height=\"901\" alt=\"image\" src=\"https://github.com/user-attachments/assets/bdbd7c11-9a1a-42a3-972e-6920228fe065\" />\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Copy Variable Set/README.md",
    "content": "This UI action will help create a copy of the Variable set, including the Catalog Client Script, Catalog UI actions and Variable.\n\nBelow Configurations need to be performed on the UI action form on creation\n\nTable : Variable Set\nActive: True\nShow Update : True\nClient : True\nAction name : copyQuestionSet\nOn Click : clientConfirm()\n\n### update\nTo complete a task on issue #745 \nReplace JavaScript function confirm() with GlideModal() API.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Copy Variable Set/scripts.js",
    "content": "/****************Client Code****************/\n\nfunction clientConfirm() {\n\n        var actionCallbackOK = function() {\n        \t gsftSubmit(null, g_form.getFormElement(), 'copyQuestionSet');\n    \t};\n    \tvar actionCallbackCancel = function() {\n         \treturn false;\n    \t};\n\n\t    var gm = new GlideModal('glide_confirm_basic',false); //UI page with logic to confirm\n    \tgm.setTitle(\"This will create a copy of this variable set including all variables, choices, UI policies, UI policy actions and client scripts. Do you want to proceed?\"); // confirm message to ask for confirmation\n\t    gm.setPreference('onPromptComplete', actionCallbackOK.bind(this)); //bind to local function to take action when selected Ok\n    \tgm.setPreference('onPromptCancel', actionCallbackCancel.bind(this)); //bind to local function to take action when selected Cancel\n    \tgm.render();\n}\n\n/****************Server Code****************/\n//set some new default values\nvar name = current.title;\ncurrent.title = 'Copy of ' + name;\n\n//insert a copy of the variable set\nvar oldid = current.sys_id.toString();\nvar newid = current.insert();\nvar allVars = {};\n\nif (typeof window == 'undefined') {\n    main(oldid, newid);\n}\n\nfunction main(oldid, newid) {\n\n    createVariables(oldid, newid);\n    createCatalogClientScript(oldid, newid);\n    createCatalogUiPolicy(oldid, newid);\n}\n\n//creates a copy of the variables and associates them to the new variable set\nfunction createVariables(oldid, newid) {\n    var vars = new GlideRecord('item_option_new');\n    vars.addQuery('variable_set', oldid);\n    vars.query();\n    while (vars.next()) {\n        var varoldid = vars.sys_id.toString();\n        vars.variable_set = newid;\n        var varnewid = vars.insert();\n        allVars['IO:' + varoldid] = 'IO:' + varnewid.toString();\n\n        var qc = new GlideRecord('question_choice');\n        qc.addQuery('question', varoldid);\n        qc.query();\n        while (qc.next()) {\n            qc.question = varnewid;\n            qc.insert();\n        }\n    }\n}\n\n//creates a copy of the client scripts and associates to the variable set.\nfunction createCatalogClientScript(oldid, newid) {\n    var ccs = new GlideRecord('catalog_script_client');\n    ccs.addQuery('variable_set', oldid);\n    ccs.query();\n    while (ccs.next()) {\n        if (ccs.type == 'onChange') {\n            var cv = ccs.cat_variable;\n            ccs.cat_variable = allVars[cv];\n        }\n        ccs.variable_set = newid;\n        ccs.insert();\n    }\n}\n\n//creates a copy of the UI Policies and associates them to the new variable set\nfunction createCatalogUiPolicy(oldid, newid) {\n    var cup = new GlideRecord('catalog_ui_policy');\n    cup.addQuery('variable_set', oldid);\n    cup.query();\n    while (cup.next()) {\n        var uipoldid = cup.sys_id.toString();\n        cup.variable_set = newid;\n        var newuip = cup.insert();\n\n        var cupa = new GlideRecord('catalog_ui_policy_action');\n        cupa.addQuery('ui_policy', uipoldid);\n        cupa.query();\n        while (cupa.next()) {\n            cupa.ui_policy = newuip;\n            cupa.variable_set = newid;\n            var cv = cupa.catalog_variable;\n            cupa.catalog_variable = allVars[cv];\n            cupa.insert();\n        }\n    }\n}\n\n//Return the user to the new variable set record\naction.setRedirectURL(current);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Copy incident details and create a child incident/README.md",
    "content": "# Copy incident details and create a child incident\n\nUse case : A button on the header of incident form to copy details(fields) of current incident and create a child incident with those copied details.\n\nSolution : Added a code snippet for UI action script that copies few fields of current incident and creates a child for the current incident with those details.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Copy incident details and create a child incident/ui_action_script.js",
    "content": "var gr = new GlideRecord('incident');\ngr.initialize();\ngr.short_description = current.short_description;  //copy short description field\ngr.caller_id = current.caller_id;  //copy caller id field\n//you can copy few more fields as per requirement\ngr.parent_incident = current.sys_id;\ngr.work_notes = \"This incident is a child and copy of \" + current.number;  //you can customize work notes if needed\ngr.insert();\n\naction.setRedirectURL(gr);  //use this line if you want to redirect to newly created child incident after execution\naction.setRedirectURL(current)  //use this line if you want to stay in parent incident after execution.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Incident from Record - Open in both Platform and Workspace/README.md",
    "content": "Script that can be used in the Script section of a UI Action to create an Incident, or any record type, from another record and will open the record that was just created. \nThis script will work both in Platform UI and Workspace UI, as long as one of the Workspace Form/Menu buttons are checked. This reduced the need to create a script\nfor both Platform and Workspace within one UI Action.  For more information: https://www.ashleysn.com/post/action-opengliderecord\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Incident from Record - Open in both Platform and Workspace/script.js",
    "content": "//Placed in the Script field of the UI Action, in order to work on Workspace the Workspace Form Action button/or menu must be checked\nvar incGr = new GlideRecord('incident');\nincGr.newRecord();\nincGr.setValue('caller_id', current.getValue('contact')); //This can be whatever field your record is housing the user in, this example is from the Case record.\nincGr.setValue('short_description', current.short_description);\nincGr.insert();\naction.openGlideRecord(incGr);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create New blank incident from the incident/README.md",
    "content": "A UI Action in ServiceNow is a script that defines an action or button within the platform's user interface. \nIt enables users to perform specific operations on forms and lists, such as creating, updating, or deleting records, or executing custom scripts. \nUI Actions enhance the user experience by providing functional buttons, links, or context menus.\n\nIn this UI action script when clicked Creates New blank incident form, from the incident. \n\naction.setRedirectURL() is a method used in server-side scripting within UI Actions to redirect users to a specific URL after a UI action is performed. \nIt is commonly used to navigate users to different records, forms, or list views after they have completed an action.\n\nSyntax -  action.setRedirectURL(URL);\nParameters:\nURL: The URL to which the user will be redirected. This can be a string representing a GlideURL object or a hardcoded URL. It must point to a valid ServiceNow page (record, list, form, etc.).\nReturn:\nNone. It performs a redirection after the script completes.\n\nGlideURL is a class in ServiceNow used for constructing URLs dynamically in server-side scripts. It allows developers to programmatically create and manipulate URLs to redirect users, perform navigation, or link to specific ServiceNow resources (e.g., forms, lists, reports).\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create New blank incident from the incident/script.js",
    "content": "//UI Action - Create New blank incident from the incident.\n\nvar newFormURL = new GlideURL('incident.do');\nnewFormURL.addParam('sys_id', '-1');  // Open a new blank form\naction.setRedirectURL(newFormURL.toString());\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Problem Record from any Table/CreateProblemRecord.js",
    "content": "//This UI action helps in generating a problem record from a chnage or an incident form\nvar createPrb = new GlideRecord(\"problem\"); // Gliding the problem table\ncreatePrb.initialize();\ncreatePrb.short_description = current.short_description; // taking current records short description as problem short description(problem statement)\ncreatePrb.first_reported_by_task = current.getUniqueValue();\ncreatePrb.cmdb_ci = current.cmdb_ci; //taking the affected in configuration item\ncreatePrb.insert(); //inserting the record into the problem table\ngs.addInfoMessage(\"problem number\" + createPrb.number.getDisplayValue()); // informing the user with the created problem record number for easy reference.\naction.setRedirectURL(current);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Problem Record from any Table/README.md",
    "content": "This UI action helps in creating a problem record from an incident or even from a change table, and we can modify this code by changing the table name we can use it for any sort of table to create a problem record.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Problem Task from the Problem/README.md",
    "content": "UI Action Create Problem Task from the Problem\n\nA UI Action in ServiceNow is a script that defines an action or button within the platform's user interface. \nIt enables users to perform specific operations on forms and lists, such as creating, updating, or deleting records, or executing custom scripts. \nUI Actions enhance the user experience by providing functional buttons, links, or context menus.\n\nUsing this UI action script we can create a Problem task from a Problem and associate it to the current problem.\n\nUI Action will be available as Form Button on the Problem Form.\nWhen Clicked Problem Task will be Created and associated to the current Problem.\n\nShort description of the problem task is \"Problem Task Created for problem \" + current.number\nSame will be added to the Description as well.\nsys_id of the current problem record will be added to the problem field of the Problem Task. This will create relation between Problem and Problem Task.\nProblem Task Type will be General\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Problem Task from the Problem/script.js",
    "content": "//UI Action - Create a Problem task from a Problem. Problem Task Type is General\n\nvar gr = new GlideRecord('problem_task');\ngr.initialize();\ngr.short_description = \"Problem Task Created for problem \" + current.number;\ngr.description = current.short_description;\ngr.problem = current.sys_id;\ngr.problem_task_type = 'general';\ngr.insert();\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Update Set on DEV/README.md",
    "content": "# Create Update Set on DEV\n\nA client UI Action for the Story form that opens up a new browser window with the Create New Update Set form on a specified DEV instance with Update Set name pre-filled with the Story number and short description.\n\nHelps reducing copy/paste work and to keep up with Update Set naming standards.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create Update Set on DEV/script.js",
    "content": "function openDevUpdateSetForm() {\n\n    // Name of the DEV instance where Update sets should be created:\n    var dev_instance_name = 'my_org_dev_instance';\n    // Update set name format:\n    var update_set_name = g_form.getValue('number') + ' ' + g_form.getValue('short_description');\n\n    var instanceURL = 'https://' + dev_instance_name + '.service-now.com/nav_to.do?uri=';\n    var updatesetURL = '/sys_update_set.do?sys_id=-1&sysparm_query=name=' + update_set_name;\n    var encodedUpdateSetURL = encodeURIComponent(updatesetURL);\n    var gotoURL = instanceURL + encodedUpdateSetURL;\n\n    g_navigation.open(gotoURL, '_blank');\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create incident task and relate to incident/README.md",
    "content": "This UI Action loads a modal for to create a new incident task that is linked to the incident that you generated it from.\n\nSuggested values:\nName: Create Incident Task\nTable: Incident\nClient: true\nList v2: true\nForm Link: true\n\nOnclick: createIncidentTask()\nCondition: current.state != 7"
  },
  {
    "path": "Client-Side Components/UI Actions/Create incident task and relate to incident/script.js",
    "content": "function createIncidentTask() {\n\t\n\tvar sysID = g_form.getUniqueValue();\n\t\n\tvar gm = new GlideModalForm('Create Incident Task', 'incident_task');\n\tgm.setPreference('focusTrap', true);\n\tgm.setPreference('table', 'incident_task');\n\tgm.setPreference('sysparm_query', 'incident='+sysID);\n\tgm.setWidth(650);\n\n\t//Opens the dialog\n\tgm.render();\n\t\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create story/Create story from other task.js",
    "content": "createStory();\n\nfunction createStory() {\n\n  // (1) Copy item fields into a new story\n  var story = new GlideRecord(\"rm_story\");\n  story.priority = current.priority;\n  story.short_description = current.short_description;\n  story.assignment_group = current.assignment_group;\n  story.assigned_to = current.assigned_to;\n  story.description = current.description;\n  story.work_notes = current.work_notes;\n  story.type=\"Development\";\n  story.opened = current.opened;\n  story.opened_by = current.opened_by;\n  story.product = null;\n  story.state = -6;  //default to draft\n  story.original_task = current.sys_id;\n  var storySysID = story.insert();\n  \n  current.agile_story = storySysID;\n  current.update();\n\n  // (2) Redirect webpage to the new story (Ensure story displayed in scrum view)\n  gs.addInfoMessage(gs.getMessage(\"Story {0} created\", story.number)); \n  action.setRedirectURL(story);\n  var redirectURL = action.getRedirectURL();\n  redirectURL = redirectURL.replace(\"sysparm_view=\", \"sysparm_view=scrum\");\n  action.setRedirectURL(redirectURL);\n  action.setReturnURL(current);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Create story/README.md",
    "content": "Code Snippet for UI Action to create an Agile Story from another task.  For example Incident -> Story, Task -> Story, Requested Item -> Story\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Display a 2-choice confirmation dialog/README.md",
    "content": "# Display a 2-choice confirmation dialog\nWhen you press the button on the Form screen, a two-choice dialog is displayed.\n\nClick the Complete button to execute Serverside processing.\n\n## Please set the following values.\n* UI Action \n  * Name: Example Dialog\n  * Table: incident (anything) \n  * Action name: example_dialog\n  * Client: ture\n  * Form button: ture\n  * Onclick: onClickExampleDialog()\n  * Script\n\n```javascript\nfunction onClickExampleDialog() {\n    var dialogClass = typeof GlideModal != 'undefined' ? GlideModal : GlideDialogWindow;\n    var dialog = new dialogClass('glide_modal_confirm');\n    dialog.setTitle('Dialog title');\n    dialog.setPreference(\"focusTrap\", true); // Restrict focus from moving out of Dialog\n    dialog.setPreference('body', 'Approve this change?');\n    dialog.setPreference('buttonLabelCancel', 'Cancel'); // Cancel button label\n    dialog.setPreference('buttonLabelComplete', 'Complete'); // Complete button label\n    dialog.setPreference('buttonClassComplete', 'btn btn-destructive'); // Complete button CSS\n    dialog.setPreference('onPromptComplete', dialogComplete.bind(this)); // Complete button function\n    dialog.setPreference('onPromptCancel', dialogCancel.bind(this)); // Cancel button function\n    dialog.render();\n    return true;\n}\n\n// Complete button function\nfunction dialogComplete() {\n    //Press Submit Button and call UIAction(Server side 'example_dialog') again.\n    gsftSubmit(null, g_form.getFormElement(), 'example_dialog');\n}\n// Cancel button function\nfunction dialogCancel() {\n    //alert('Dialog Cancel');\n}\n\n//Judge Server side\nif (typeof window == 'undefined') {\n    serversideTask();\n}\n// Server side function\nfunction serversideTask() {\n    current.update();\n    gs.info('Serverside Task');\n    action.setRedirectURL(current);\n}\n```\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Display a 2-choice confirmation dialog/choice_dialog.js",
    "content": "// Display a 2-choice confirmation dialog.\n// When you press the button on the Form screen, a two-choice dialog is displayed.\n// Click the Complete button to execute Serverside processing.\n// Please set the following values.\n// * UI Action\n// * Name: Example Dialog\n// * Table: incident (anything)\n// * Action name: example_dialog\n// * Client: ture\n// * Form button: ture\n// * Onclick: onClickExampleDialog()\n// * Script\nfunction onClickExampleDialog() {\n    var dialogClass = typeof GlideModal != 'undefined' ? GlideModal : GlideDialogWindow;\n    var dialog = new dialogClass('glide_modal_confirm');\n    dialog.setTitle('Dialog title');\n    dialog.setPreference(\"focusTrap\", true); // Restrict focus from moving out of Dialog\n    dialog.setPreference('body', 'Approve this change?');\n    dialog.setPreference('buttonLabelCancel', 'Cancel'); // Cancel button label\n    dialog.setPreference('buttonLabelComplete', 'Complete'); // Complete button label\n    dialog.setPreference('buttonClassComplete', 'btn btn-destructive'); // Complete button CSS\n    dialog.setPreference('onPromptComplete', dialogComplete.bind(this)); // Complete button function\n    dialog.setPreference('onPromptCancel', dialogCancel.bind(this)); // Cancel button function\n    dialog.render();\n    return true;\n}\n\n// Complete button function\nfunction dialogComplete() {\n    //Press Submit Button and call UIAction(Server side 'example_dialog') again.\n    gsftSubmit(null, g_form.getFormElement(), 'example_dialog');\n}\n// Cancel button function\nfunction dialogCancel() {\n    //alert('Dialog Cancel');\n}\n\n//Judge Server side\nif (typeof window == 'undefined') {\n    serversideTask();\n}\n// Server side function\nfunction serversideTask() {\n    current.update();\n    gs.info('Serverside Task');\n    action.setRedirectURL(current);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Email Watermark Utility/GenericEmailUtility.js",
    "content": "var GenericEmailUtility = Class.create();\nGenericEmailUtility.prototype = {\n    initialize: function() {},\n  \n     // Generate an Outlook (mailto) link with watermark tracking\n    get_Outlook_link: function() {\n        try {\n            const email_payload = JSON.stringify({\n                \"REQUESTOR_ID\": \"\",\n                \"TITLE\": \"\",\n                \"BODY\": \"\",\n                \"REQUEST_ID\": \"\",\n                \"TABLE_ID\": \"\"\n            });\n\n            var mailtoLink = false;\n            const raw_data = this.getParameter(\"sysparm_email_body\") || email_payload;\n\n            if (global.JSUtil.notNil(raw_data)) {\n                var email_data = JSON.parse(raw_data);\n\n                const to = this.getEmail(email_data.REQUESTOR_ID);\n                const cc = gs.getProperty(\"instanceEmailAddress\"); // instance default CC\n                const subject = email_data.TITLE || '';\n                const body = email_data.BODY || '';\n\n                const watermark = this.getWatermark(email_data.REQUEST_ID, email_data.TABLE_ID);\n\n                // Construct mailto link\n                mailtoLink = 'mailto:' + to + '?cc=' + cc;\n\n                if (subject)\n                    mailtoLink += '&subject=' + encodeURIComponent(subject);\n\n                if (body)\n                    mailtoLink += '&body=' + encodeURIComponent(body);\n\n                if (watermark)\n                    mailtoLink += encodeURIComponent(\"\\n\\nRef: \" + watermark);\n            }\n\n            return mailtoLink;\n\n        } catch (ex) {\n            gs.error(\"Error in get_Outlook_link(): \" + ex.message);\n            return false;\n        }\n    },\n\n     // Fetch watermark ID (creates one if missing)\n    getWatermark: function(record_id, table_name) {\n        var wm = new GlideRecord('sys_watermark');\n        wm.addQuery('source_id', record_id);\n        wm.orderByDesc('sys_created_on');\n        wm.query();\n\n        if (wm.next()) {\n            return wm.getValue('number');\n        }\n\n        wm.initialize();\n        wm.source_id = record_id;\n        wm.source_table = table_name;\n        wm.insert();\n\n        return wm.getValue('number');\n    },\n\n     // Retrieve user’s email address\n    getEmail: function(user_id) {\n        if (global.JSUtil.notNil(user_id)) {\n            var user = new GlideRecordSecure('sys_user');\n            if (user.get(user_id))\n                return user.email.toString();\n        }\n        return '';\n    },\n\n    type: 'GenericEmailUtility'\n};\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Email Watermark Utility/README.md",
    "content": "# Outlook Email Watermark Utility for ServiceNow\n\n# Overview\nThis reusable utility allows users to send emails **outside ServiceNow** (e.g., using Outlook or any default mail client) while still  maintaining the conversation within ServiceNow.  \nBy embedding a unique watermark reference, any replies to the email will automatically append to the original record's activity feed.\n\nThis helps teams collaborate externally without losing internal record visibility — ideal for customers or vendors who communicate via Outlook.\n\n---\n\n# Objective\n- Enable ServiceNow users to send Outlook emails directly from a record.  \n- Maintain conversation history in ServiceNow using watermark tracking.  \n- Make the solution **generic**, reusable across tables (Incident, Change, Request, etc.).  \n- Prevent dependency on outbound mail scripts or custom integrations.  \n\n# Components\n\n## 1. Script Include: GenericEmailUtility\nHandles the logic for:\n- Constructing the mailto: link.  \n- Fetching recipient and instance email addresses.  \n- Generating or retrieving the watermark ID.  \n- Returning a formatted Outlook link to the client script.\n\n## Key Methods\n1. get_Outlook_link() - Builds the full Outlook mail link with subject, body, and watermark.\n2. getWatermark(record_id, table_name) - Ensures a watermark exists for the record.\n3. getEmail(user_id) - Fetches the email address for the target user.\n\n## 2. UI Action (Client Script)\nExecutes on the record form when the button/link is clicked.  \nIt gathers record data, constructs a payload, calls the Script Include using GlideAjax, and opens Outlook.\n\n## Key Steps\n1. Collect field data like requestor, short description, and description.  \n2. Pass record details to the Script Include (GenericEmailUtility).  \n3. Receive a ready-to-use Outlook link.  \n4. Open the mail client with prefilled details and watermark reference.  \n\n## How It Works\n1. User clicks \"Send Outlook Email\" UI Action on a record.\n2. Script gathers record data and passes it to GenericEmailUtility.\n3. The utility builds a 'mailto:' link including the watermark.\n4. Outlook (or default mail client) opens with pre-filled To, CC, Subject, and Body fields.\n5. When the recipient replies, ServiceNow uses the watermark to append comments to the correct record.\n\n## Example Usage\n**User clicks “Send Outlook Email”** on a Request record:  \nOutlook opens prefilled like this:\n\n<img width=\"288\" height=\"65\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b58c5e0a-d80a-40ca-9ab5-f188a1203169\" />\n\n\n<img width=\"710\" height=\"496\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5cbc7645-4233-4826-99f7-e2948bb5ab78\" />\n\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Email Watermark Utility/Send Email UI Action.js",
    "content": "function onClick(g_form) {\n    var separator = \"\\n--------------------------------\\n\";\n    var email_body = \"Record URL:\\n\" + g_form.getDisplayValue('number') + separator;\n    email_body += \"Short Description:\\n\" + g_form.getValue('short_description') + separator;\n    email_body += \"Description:\\n\" + g_form.getValue('description') + separator;\n\n    var email_data = {};\n    email_data.REQUESTOR_ID = g_form.getValue('caller_id') || g_form.getValue('opened_by') || g_form.getValue('requested_for');\n    email_data.TITLE = g_form.getValue('short_description') || 'ServiceNow Communication';\n    email_data.BODY = email_body;\n    email_data.REQUEST_ID = g_form.getUniqueValue();\n    email_data.TABLE_ID = g_form.getTableName();\n\n    var ga = new GlideAjax('GenericEmailUtility');\n    ga.addParam('sysparm_name', 'get_Outlook_link');\n    ga.addParam('sysparm_email_body', JSON.stringify(email_data));\n    ga.getXMLAnswer(function(response) {\n        var mailto_link = response;\n        if (mailto_link && mailto_link != 'false') {\n            window.open(mailto_link);\n        } else {\n            g_form.addErrorMessage('Unable to generate Outlook link.');\n        }\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Expire Timer in Flows/README.md",
    "content": "This UI Action adds an admin-only button to Flow Context records in ServiceNow. It is designed to expire active timers within a flow, allowing developers and testers to bypass waiting stages during sub-production testing. This helps accelerate flow validation and debugging during development cycles, especially useful during events like Hacktoberfest.\n\nFlows in ServiceNow often include Wait for Condition or Wait for Duration steps that can delay testing. This UI Action provides a quick way to expire those timers, enabling the flow to proceed immediately without waiting for the configured duration or condition. Features\n\nAdds a button labeled \"Expire Timers\" to Flow Context records. Visible only to users with the admin role. Executes a script to expire all active timers associated with the selected Flow Context. Ideal for sub-production environments (e.g., development, test, or staging). Speeds up flow development and validation.\n\n<img width=\"1584\" height=\"165\" alt=\"image\" src=\"https://github.com/user-attachments/assets/64d02ab2-de5b-48b6-82ac-d00771f43898\" />\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Expire Timer in Flows/script.js",
    "content": "/*\nThis script should be placed in the UI action on the table sys_flow_context form view.\nThis UI action should be marked as client.\nUse runClientCode() function in the Onclick field.\n*/\nfunction runClientCode() {\n\n    if (confirm('Are you sure you want to Expire the Timer activity ?\\n\\nThis Action Cannot Be Undone!')) {\n        //Call the UI Action and skip the 'onclick' function\n        gsftSubmit(null, g_form.getFormElement(), 'ExpireTimer'); //MUST call the 'Action name' set in this UI Action\n    } else {\n        return false;\n    }\n}\n\nif (typeof window == 'undefined') {\n    ExpireTimer();\n}\n\nfunction ExpireTimer() {\n    var grTrigger = new GlideRecord('sys_trigger');\n    grTrigger.addQuery('name', 'flow.fire');\n    grTrigger.addQuery('script', 'CONTAINS', current.sys_id);\n    grTrigger.addQuery('state', 0);\n    grTrigger.setLimit(1);\n    grTrigger.query();\n    if (grTrigger.next()) {\n        var grEvent = new GlideRecord('sysevent');\n        grEvent.initialize();\n        grEvent.setNewGuid();\n        grEvent.setValue('name', 'flow.fire');\n        grEvent.setValue('queue', 'flow_engine');\n        grEvent.setValue('parm1', grTrigger.getValue('job_context').toString().slice(6));\n        grEvent.setValue('parm2', '');\n        grEvent.setValue('instance', current.sys_id);\n        grEvent.setValue('table', 'sys_flow_context');\n        grEvent.setValue('state', 'ready');\n        grEvent.setValue('process_on', new GlideDateTime().getValue()); //aka run immediately\n        grEvent.insert();\n        grTrigger.deleteRecord();\n        gs.addInfoMessage(\"You have chosen to end any timers related to this flow.\");\n    }\n\n\n    action.setRedirectURL(current);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Field Review of User Record when on form using action button/README.md",
    "content": "## Field Review of User Record when on form using action button\n\nDisplays informational messages suggesting improvements to field formatting on the User Table (**sys_user**) form when the **Fields Check** button is clicked.\n\n- Helps maintain consistency in user data by checking capitalization of names and titles, validating email format, ensuring phone numbers contain only digits, and preventing duplicate phone entries. \n- Also suggests users not to leave the **user_name** field empty.\n- Shows Info messages below each field highlighting fields that may need attention.\n- Simple Prerequisite is that: when form loads give Info message to check **Field Check** button to bring user's attention\n- Uses a Client-side UI Action (**Fields Check**) that to review entered data and display friendly suggestions\n  - Name: Fields Check\n  - Table: User (sys_user)\n  - Client: true\n  - Form button: true\n  - Onclick: onClickCheckDetails()\n\n---\n\n### Grab user's attention on Field Check Button using Info message at top\n\n![Field Review on User Table_1](Field_Review_userTable_1.png)\n\n---\n\n### After clicking Field Check Button where suggestions are displayed below fields\n\n![Field Review on User Table_2](Field_Review_userTable_2.png)\n\n---\n\n### When user fixes the suggested issues and click the **Fields Check** button again, a message confirms that all fields are correctly formatted\n\n![Field Review on User Table_3](Field_Review_userTable_3.png)\n\n---\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Field Review of User Record when on form using action button/actionButtonScript.js",
    "content": "function onClickCheckDetails() {\n    // Friendly helper for field normalization guidance\n    g_form.hideAllFieldMsgs();\n    g_form.clearMessages();\n\n    // --- Get Field values ---\n    var firstName = g_form.getValue('first_name');\n    var lastName = g_form.getValue('last_name');\n    var title = g_form.getValue('title');\n    var userId = g_form.getValue('user_name');\n    var email = g_form.getValue('email');\n    var businessPhone = g_form.getValue('phone');\n    var mobilePhone = g_form.getValue('mobile_phone');\n\n    // --- Regex patterns ---\n    var capitalRegex = /^[A-Z][a-zA-Z\\s]*$/;           // Names & titles start with a capital\n    var emailRegex = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Za-z]{2,}$/; \n    var phoneRegex = /^\\d+$/;                          \n\n    var suggestions = [];\n\n    if (firstName && !capitalRegex.test(firstName)) {\n        g_form.showFieldMsg('first_name', 'Suggestion: Start the name with a capital letter.', 'info');\n        suggestions.push('First Name');\n    }\n\n    if (lastName && !capitalRegex.test(lastName)) {\n        g_form.showFieldMsg('last_name', 'Suggestion: Start the name with a capital letter.', 'info');\n        suggestions.push('Last Name');\n    }\n\n    if (title && !capitalRegex.test(title)) {\n        g_form.showFieldMsg('title', 'Suggestion: Titles usually start with a capital letter.', 'info');\n        suggestions.push('Title');\n    }\n\n    if (!userId) {\n        g_form.showFieldMsg('user_name', 'Suggestion: Do not keep the User ID empty.', 'info');\n        suggestions.push('User ID');\n    }\n\n    if (email && !emailRegex.test(email)) {\n        g_form.showFieldMsg('email', 'Suggestion: Please use a valid email format like name@example.com.', 'info');\n        suggestions.push('Email');\n    }\n\n    if (businessPhone && !phoneRegex.test(businessPhone)) {\n        g_form.showFieldMsg('phone', 'Suggestion: Use digits only avoid letters.', 'info');\n        suggestions.push('Business Phone');\n    }\n\n    if (mobilePhone && !phoneRegex.test(mobilePhone)) {\n        g_form.showFieldMsg('mobile_phone', 'Suggestion: Use digits only avoid letters.', 'info');\n        suggestions.push('Mobile Phone');\n    }\n\n    /\n    if (businessPhone && mobilePhone && businessPhone === mobilePhone) {\n        g_form.showFieldMsg('phone', 'Work and mobile numbers appear identical, use different Numbers!', 'info');\n        suggestions.push('Phone Numbers');\n    }\n\n    if (suggestions.length > 0) {\n        g_form.addInfoMessage('Quick review complete! Please check: ' + suggestions.join(', ') + '.');\n    } else {\n        g_form.addInfoMessage('looks good! Nicely formatted data.');\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Force to Update Set/README.md",
    "content": "# Introduction\n\nManually Add a Record to an Update Set\n\n1. Check to make sure the current table isn’t already recording updates\n2. Push the current record into the currently-selected update set\n3. Reload the form and add an info. message indicating the addition\n\n# Settings\nName: Force to Update Set\nTable: Wherever you need it!\nAction name: force_update\nForm link: True\nCondition: gs.hasRole(‘admin’)\nScript: \n//Commit any changes to the record\ncurrent.update();\n\n//Check to make sure the table isn't synchronized already\nvar tbl = current.getTableName();\nif(tbl.startsWith('wf_') || tbl.startsWith('sys_ui_') || tbl == 'sys_choice' || current.getED().getBooleanAttribute('update_synch') || current.getED().getBooleanAttribute('update_synch_custom')){\n   gs.addErrorMessage('Updates are already being recorded for this table.');\n   action.setRedirectURL(current); \n}\nelse{\n   //Push the update into the current update set\n   var um = new GlideUpdateManager2();\n   um.saveRecord(current);\n\n   //Query for the current update set to display info message\n   var setID = gs.getPreference('sys_update_set');\n   var us = new GlideRecord('sys_update_set');\n   us.get(setID);\n\n   //Display info message and reload the form\n   gs.addInfoMessage('Record included in <a href=\"sys_update_set.do?sys_id=' + setID + '\">' + us.name + '</a> update set.');\n   action.setRedirectURL(current);\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Force to Update Set/script.js",
    "content": "//Check to make sure the table isn't synchronized already\nvar tbl = current.getTableName();\nif(tbl.startsWith('wf_') || tbl.startsWith('sys_ui_') || tbl == 'sys_choice' || current.getED().getBooleanAttribute('update_synch') || current.getED().getBooleanAttribute('update_synch_custom')){\n   gs.addErrorMessage('Updates are already being recorded for this table.');\n   action.setRedirectURL(current); \n}\nelse{\n   //Push the update into the current update set\n   var um = new GlideUpdateManager2();\n   um.saveRecord(current);\n\n   //Query for the current update set to display info message\n   var setID = gs.getPreference('sys_update_set');\n   var us = new GlideRecord('sys_update_set');\n   us.get(setID);\n\n   //Display info message and reload the form\n   gs.addInfoMessage('Record included in <a href=\"sys_update_set.do?sys_id=' + setID + '\">' + us.name + '</a> update set.');\n   action.setRedirectURL(current);\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Generate QR for Assets/ReadMe.md",
    "content": "# 🧩 ServiceNow Asset QR Code Generator (UI Action)\n\nThis repository contains a **ServiceNow UI Action** script that generates and displays a QR Code for an Asset record from list view.  \nWhen the user selects a record and clicks the UI Action, a modal window pops up showing a dynamically generated QR Code that links to asset details.\n\n\nA supporting **Script Include** (server-side) is required in your ServiceNow instance but **is not included** in this repository.\nAt the bottom of file , a sample Script Include Code is given , check for the reference.\n\n---\n\n## 🚀 Features\n\n- Generates a QR Code for the selected Asset record.\n- Displays the QR Code inside a ServiceNow modal (`GlideModal`).\n- Uses **QrIckit API** for quick and free QR code generation.\n- Clean, modular client-side code that integrates seamlessly with UI Actions.\n- Includes a `qr-code-image` file showing example QR Code generated.\n\n---\n\n## 🧠 How It Works\n\n1. The `onClickQR()` function is triggered when the user clicks a UI Action button.\n2. It calls `generateQRCodeForAsset(sys_id)` and passes the record’s `sys_id`.\n3. A `GlideAjax` request fetches asset data from a **Script Include** on the server.\n4. That data is encoded and sent to the **QrIckit** API to generate a QR Code image.\n5. A ServiceNow modal (`GlideModal`) displays the generated QR Code to the user.\n\n---\n\n\n**Note :**\n1) As the UI action calls a Script Include , in this folder no script include is present\n2) You can modify script include part as required(i.e Which fields are to be shown when QR is scanned)\n3) A sample Client Callable Script-Include is given here.\n\n``` Script Include Code\n     var GenerateAssetQR = Class.create();\nGenerateAssetQR.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getAssetQRData: function() {\n        var sys_id = this.getParameter('sysparm_sys_id');\n        var asset = new GlideRecord('alm_asset');\n        if (asset.get(sys_id)) {\n            return 'Asset: ' + asset.name + ', Serial: ' + asset.serial_number;\n        }\n        return 'Invalid asset record.';\n    }\n});\n```\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Generate QR for Assets/ui-action-script.js",
    "content": "function onClickQR() {\n    generateQRCodeForAsset(g_sysId);//get the sysid of selected record\n}\n\nfunction generateQRCodeForAsset(sys_id) {\n    var ga = new GlideAjax('GenerateAssetQR');//Script Include which stores data to be presented when QR-Code is Scanned\n    ga.addParam('sysparm_name', 'getAssetQRData');\n    ga.addParam('sysparm_sys_id', sys_id);\n\n    ga.getXMLAnswer(function(response) {\n        var qrData = response;\n        var qrURL = 'https://qrickit.com/api/qr.php?d=' + encodeURIComponent(qrData) + '&addtext=Get Asset Data';\n         //QrIckit is a tool using which Customized QR-Codes can be generated\n        var modalHTML = `\n            <div style=\"text-align:center\">\n                <img id=\"qrCodeImage\" src=\"${qrURL}\" alt=\"QR Code\" style=\"margin-bottom:10px;\" />\n                <p>Scan to view asset details</p>\n            </div>\n        `;\n\n        var gModal = new GlideModal(\"QR Code\");\n        gModal.setTitle('Asset QR Code');\n        gModal.setWidth(500);\n        gModal.renderWithContent(modalHTML);\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Generate a PDF/README.md",
    "content": "# Introduction\nGenerating a PDF using PDFGenerationAPI\nCalling an OOTB convertToPDFWithHeaderFooter() method to generate a PDF with your header and footer.\nPDFGenerationAPI – convertToPDFWithHeaderFooter(String html, String targetTable, String targetTableSysId, String pdfName, Object headerFooterInfo, String fontFamilySysId, Object documentConfiguration)\n\n# Example:\n<img width=\"704\" height=\"496\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9d086ee3-db76-482d-ab11-4b0540a5e2fb\" />\n\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Generate a PDF/serverscript.js",
    "content": "//Table: Change Request\n// UI action button on the change form that exports all the related incidents into a PDF format.\n//ServiceNows PDFGenerationAPI allows you to customize the page size, header, footer, header image, page orientation, and more.\n\nvar inc = new GlideRecord('incident'),\n    incidentsList = [],\n    v = new sn_pdfgeneratorutils.PDFGenerationAPI,\n    html = '',\n    hfInfo = new Object(),\n    result;\ninc.addQuery('rfc', current.sys_id);\ninc.query();\nwhile (inc.next()) {\n    incidentsList.push(inc.number);\n    incidentsList.push(inc.getDisplayValue('caller_id')); \n    incidentsList.push(inc.getDisplayValue('category'));\n    incidentsList.push(inc.getDisplayValue('subcategory'));\n    incidentsList.push(inc.getValue('priority')); \n    incidentsList.push(inc.getValue('short_description')); \n    incidentsList.push(inc.getValue('description')); \n    incidentsList.push(inc.getDisplayValue('assignment_group')); \n}\nvar json = {    \n    incidents: incidentsList.toString()\n};\n\n JSON.stringify(json);\nhtml = '<h1 style=\"margin: 0in; text-align: center; line-height: 107%; break-after: avoid; font-size: 20pt; font-family: Calibri Light, sans-serif; color: #2e74b5; font-weight: normal;\" align=\"center\"><strong><span style=\"font-size: 16.0pt; line-height: 107%; font-family: Arial Narrow, sans-serif; color: #002060;\">Incidents Related to the Change: ' + current.number + ' </span></strong></h1><br>';\n\n\nhtml += '<div style=\"width: 100%\"><p><strong><span style=\"font-size: 11pt; font-family: Arial Arrow; color: #002060;\">Incidents List</span><span style=\"font-size: 2pt; font-family: Palatino; color: #002060;\">&nbsp;&nbsp;</span></strong></p></div>';\nhtml += '<table style=\"table-layout: fixed; width: 100%; border-collapse: collapse;\" border=\"1\"><tr style=\"text-align: center;background-color: #B4C6E7;font-size: 10pt; font-family: Arial Arrow; word-wrap: break-word;\"><td><strong>Number</strong></td><td><strong>Caller</strong></td><td><strong>Category</strong></td><td><strong>Sub Category</strong></td><td><strong>Priority</strong></td><td><strong>Short Description</strong></td><td><strong>Description</strong></td><td><strong>Assignment Group</strong></td></tr>' + getIncidentsTable(json.incidents) + '</table>';\nhfInfo[\"FooterTextAlignment\"] = \"BOTTOM_CENTER\";\nhfInfo[\"FooterText\"] = \"Incidents List\";\nhfInfo[\"PageOrientation\"] = \"LANDSCAPE\";\nhfInfo[\"GeneratePageNumber\"] = \"true\";\n\nresult = v.convertToPDFWithHeaderFooter(html, 'change_request', current.sys_id, \"Incidents Related to the Change:_\" + current.number, hfInfo);\naction.setRedirectURL(current);\n\nfunction getIncidentsTable(incidents) {\n    if (incidents == '')\n        return '';\n    var table = '',\n        i;\n    incidents = incidents.split(',');\n    for (i = 0; i < incidents.length; i += 8)\n        table += '<tr style=\"text-align: center;font-size: 10pt; font-family: Arial Arrow; word-wrap: break-word;\"><td>' + incidents[i] + '</td><td>' + incidents[i + 1] + '</td><td>' + incidents[i +2] + '</td><td>' + incidents[i + 3] + '</td><td>' + incidents[i + 4] + '</td><td>' + incidents[i + 5] + '</td><td>' + incidents[i + 6] + '</td><td>' + incidents[i + 7] + '</td></tr>';\n    return table;\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/GlideModalForm - Open New Record and Pass Query/README.md",
    "content": "This is an example of using the GlideModalForm API to open a brand new record on a specific table, passing along query parameters to it to assist with loading filling out the form\n\nWithin the UI Action settings it's recommended to ensure:\n- Active is true\n- Either show insert and/or show update is true\n- Client is true\n- Your appropriate List v2 or V3 compatible checkbox is true\n- Onclick contains your function name that matches the function within your Script field -- the code related to this snipped would be: **functionName()**\n- Set true to however you wish to display this UI Action to the user; whether that's via Form button, Form context menu, Form link, etc.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/GlideModalForm - Open New Record and Pass Query/code.js",
    "content": "function functionName() { //specify the function name you listed within the UI Action \"onClick\" field\nvar tableName = \"table_name\"; //specify what table the new record should be created on\nvar dialog = new GlideModalForm('modal_form_title', tableName); //set your modal form title here\ndialog.setSysID(-1); //sys_id -1 will open a brand new record\ndialog.addParm('sysparm_view', 'view_name'); //optional: you can specify a specific view name here\ndialog.addParm('sysparm_view_forced', 'true'); //optional: you can force the view so it overrides\ndialog.addParm('sysparm_form_only', 'true'); //optional: you can specify to show the form only, removing related lists from the screen\nvar sDesc = g_form.getValue('short_description'); //example retrieving the short description on the current record\nvar query = \"short_description=\" + sDesc; //example setting the query JavaScript variable to the sDesc JavaScript variable that contains our current record's short description\ndialog.addParm('sysparm_query', query); //sets the query to the JavaScript variable from the line above, this will populate the related field(s) on the new form with the values specified\ndialog.render(); //displays the modal form to the user\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/GlideModalUiPagePopUp/README.md",
    "content": "Overview\nModal alerts and confirmations in ServiceNow are interactive user interface elements that allow users to receive important messages or confirm actions before proceeding. These modals enhance the user experience by providing clear feedback during key interactions, ensuring that users are well-informed before performing potentially critical actions, such as closing a record or saving changes.\n\nTwo key modal functions used in ServiceNow are:\n\ng_modal.alert(): Displays an informational message to the user.\ng_modal.confirm(): Requests user confirmation before proceeding with an action.\nKey Features:\nClarity: Provides clear messaging for users, helping them understand the consequences of their actions.\nUser Control: Allows users to confirm or cancel actions, reducing mistakes.\nCustomizable: Modal messages can be tailored to different scenarios, ensuring contextual and relevant feedback.\nTesting\nTo test the modal alerts and confirmations, follow these steps:\n\n1. User Scenario Setup:\nCreate a form with an assigned_to field and ensure that different users can access the form.\nSet up a button or UI action that triggers the onClick() function.\n2. Test Case 1: Unauthorized User\nLog in as a user who is not assigned to the task.\nClick the button to trigger the action.\nExpected Result: The user should receive an alert stating \"Only the assigned user can perform this action.\" The process should stop here.\n3. Test Case 2: Authorized User\nLog in as the user who is assigned to the task.\nClick the button to trigger the action.\nExpected Result: A confirmation dialog should appear asking, \"Are you sure you want to take this action?\"\nIf the user clicks \"Cancel,\" no action should be taken.\nIf the user clicks \"OK,\" the task's state should be set to \"Closed Complete,\" and the form should be saved.\n4. Edge Cases:\nTest what happens if the task has no assigned user.\nTest if the modals display correctly on different devices (desktop, mobile, etc.).\n5. Performance:\nEnsure that the modals load quickly and do not interfere with other form functions.\nHow It Works\nThe modal alert and confirmation functions are built on the ServiceNow UI framework and use JavaScript to control the interactions.\n\nAlert Modal (g_modal.alert()):\nThis function is used to inform the user without requiring any further input.\nIt takes three arguments: the title of the alert, the message to display, and an optional callback function that can execute code after the alert is closed.\nOnce triggered, the user sees a message box with only an \"OK\" button.\nConfirmation Modal (g_modal.confirm()):\nThis function prompts the user to confirm their action with two options: \"OK\" or \"Cancel.\"\nIt takes three arguments: the title of the confirmation modal, the message, and a callback function.\nThe callback function receives a confirmed argument that is true if the user clicks \"OK\" and false if they click \"Cancel.\"\nThis is useful in scenarios where user confirmation is critical (e.g., deleting a record, submitting a form).\nProcess Flow:\nThe system checks if the logged-in user has permission to perform the action (e.g., checking if the user matches the assigned_to field).\nIf the user is not authorized, an alert modal is displayed, and the process stops.\nIf the user is authorized, a confirmation modal is displayed to ask for final approval.\nIf confirmed, the system proceeds with the action (e.g., changing the record state and saving the form).\nBenefits\n1. Improved User Experience:\nModal alerts and confirmations provide immediate feedback to users. By using clear language in modals, users understand exactly what actions they can or cannot perform.\nConfirmation dialogs prevent accidental actions, which is especially useful in critical workflows (e.g., closing or deleting records).\n2. Reduced Errors:\nAlerts help users understand why an action is restricted, while confirmations ensure that actions are intentional.\nThis reduces the risk of accidental data changes or loss.\n3. Increased Control:\nBy requiring confirmation before a critical action is taken, users feel more in control of their tasks. They are prompted to reconsider their choices, which minimizes hasty decisions.\n4. Customization:\nModal alerts and confirmations can be easily customized for various forms and records, allowing tailored feedback depending on the context of the action.\n5. Asynchronous Operations:\nModals are asynchronous, meaning they don't block the user interface while waiting for input. This ensures that other parts of the application continue functioning smoothly.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/GlideModalUiPagePopUp/glide_modal_ui_pop_up.js",
    "content": "function onClick(g_form) {\n    // Check if the current user is the assigned user\n    if (g_user.userID != g_form.getValue('assigned_to')) {\n        // Alert if the user is not the assigned user\n        g_modal.alert('Only the assigned user can perform this action.');\n        return;\n    }\n\n    // Confirmation message\n    var msg = getMessage(\"Are you sure you want to take this action?\");\n    \n    // Confirmation modal before closing the task\n    g_modal.confirm(getMessage(\"Confirmation\"), msg, function (confirmed) {\n        if (confirmed) {\n            // If confirmed, close the task\n            g_form.setValue('state', 'closed_complete');\n            g_form.save();\n        }\n    });\n\n    return false;\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Go to Agent Workspace Home Page/README.md",
    "content": "\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Go to Agent Workspace Home Page/ui_action_script.js",
    "content": "function onClick() {\n\ttop.window.location ='now/workspace/agent/home';\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Group Membership Admin Util/README.md",
    "content": "# Group Membership Utility\n\nThe **Group Membership Utility** is a ServiceNow server-side tool designed to streamline the management of user membership in assignment groups. It provides administrators with two UI actions on the Assignment Group table to add or remove themselves from a group, ensuring efficient group membership management. Super helpful when doing group membership testing.\n\n## Challenge\n\nManaging assignment group memberships manually can be time-consuming and frustrating when doing group change related testings.\n\n## Features\n\n- **Add Me**: Adds the current user to the selected assignment group, ensuring quick inclusion.\n- **Remove Me**: Removes the current user from the selected assignment group, simplifying group updates.\n- **Admin-Only Visibility**: Both actions are restricted to users with administrative privileges i.e admin user role, ensuring controlled access.\n\n## Functionality\n\nThe Group Membership Utility provides the following capabilities:\n- Detects the current user's membership status in the selected group.\n- Dynamically enables or disables the **Add Me** and **Remove Me** actions based on the user's membership.\n- Ensures visibility of these actions only for users with administrative privileges.\n\n## Visibility\n\nAdd below condition script for the \"Add Me\" UI action \n```javascript\ngs.hasRole('admin') && !gs.getUser().isMemberOf(current.sys_id);\n```\nAdd below condition script for the \"Remove Me\" UI action \n```javascript\ngs.hasRole('admin') && gs.getUser().isMemberOf(current.sys_id);\n```\n\n## Usage Instructions\n\n1. Navigate to the Assignment Group table.\n2. Select a group.\n3. Use the **Add Me** button to add yourself to the group if you're not already a member.\n4. Use the **Remove Me** button to remove yourself from the group if you're already a member.\n\n\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Group Membership Admin Util/addMeUIActionScript.js",
    "content": "try {\n  var groupSysId = current.sys_id;\n  var userSysId = gs.getUserID();\n\n  // Validate input\n  if (!groupSysId || !userSysId) {\n    throw new Error(\"Group Sys ID and User Sys ID are required.\");\n  }\n\n  // Create a new record in the sys_user_grmember table\n  var gr = new GlideRecord(\"sys_user_grmember\");\n  gr.initialize();\n  gr.group = groupSysId;\n  gr.user = userSysId;\n  var sysId = gr.insert();\n\n  if (sysId) {\n    gs.addInfoMessage(\n      \"User successfully added to the group. Record Sys ID: \" + sysId\n    );\n  } else {\n    throw new Error(\"Failed to add user to the group.\");\n  }\n} catch (error) {\n  gs.addErrorMessage(\"Error adding user to group: \" + error.message);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Group Membership Admin Util/removeMeUIActionScript.js",
    "content": "try {\n    var groupSysId = current.sys_id;\n    var userSysId = gs.getUserID();\n\n    // Validate input\n    if (!groupSysId || !userSysId) {\n        throw new Error(\"Group Sys ID and User Sys ID are required.\");\n    }\n\n    // Query the sys_user_grmember table to find the record\n    var gr = new GlideRecord(\"sys_user_grmember\");\n    gr.addQuery(\"group\", groupSysId);\n    gr.addQuery(\"user\", userSysId);\n    gr.query();\n\n    if (gr.next()) {\n        // Delete the record\n        var deleted = gr.deleteRecord();\n        if (deleted) {\n            gs.addInfoMessage(\"User successfully removed from the group.\");\n        } else {\n            throw new Error(\"Failed to remove user from the group.\");\n        }\n    } else {\n        throw new Error(\"No matching record found for the user in the group.\");\n    }\n} catch (error) {\n    gs.addErrorMessage(\"Error removing user from group: \" + error.message);\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Group dependency/README.md",
    "content": "Easily assess where a user group is used across your ServiceNow instance — before you retire, modify, or repurpose it.\n\nThis solution adds a UI Action to the sys_user_group form that opens a clean, dynamic UI Page showing all the dependencies across modules like Tasks, Script Includes, Business Rules, Workflows, Catalog Items, Reports, and more.\n\nKey Features:\n\n• One-click access to group dependency insights\n\n• Displays usage across 10+ key modules\n\n• Modular architecture using UI Page, Script Include, and client-side UI Action\n\n• Easily extensible to include custom tables or rules\n\n\nUse Cases:\n\n• Pre-deactivation impact checks for groups\n\n• Governance and cleanup tasks\n\n• Platform documentation and audit support\n\n• Extensible framework for users, catalog items, or roles\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Group dependency/uiaction.js",
    "content": "/*\nThis script should be placed in the UI action on the table sys_user_group form view.\nThis UI action should be marked as client.\nUse popupDependency() function in the Onclick field.\ncondition - gs.hasRole('admin') \n*/\n\nfunction popupDependency() {\n    var groupSysId = gel('sys_uniqueValue').value;\n    var gdw = new GlideDialogWindow('display_group_dependency_list');\n    gdw.setTitle('Group Dependency');\n    gdw.setPreference('sysparm_group', groupSysId);\n    gdw.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Group dependency/uipage.js",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\n\t<j:set var=\"jvar_group_sysid\" value=\"${sysparm_group}\"/>\n\t<g:evaluate jelly=\"true\">\n\t\tvar groupSysId = trim(jelly.sysparm_group);\n\t\tvar groupName = '';\n\t\tvar taskRecords = 0;\n\t\tvar reportRecords = 0;\n\t\tvar workflowRecords = 0;\n\t\tvar workflowVersions = '';\n\t\tvar scriptIncludeRecords = 0;\n\t\tvar businessRulesRecords = 0;\n\t\tvar clientScriptRecords = 0;\n\t\tvar maintainItemRecords = 0;\n\t\tvar businessServiceRecords=0;\n\t\tvar systemPropertiesRecords=0;\n\t\tvar avaiforGroupsRecords = 0;\n\t\tvar grACLRecords = 0;\n\t\t\n\t\tvar grGroupName = new GlideRecord('sys_user_group');\n\t\tif (grGroupName.get('sys_id', groupSysId)) {\n\t\t\tgroupName = grGroupName.name;\n\t\t}\n\t\t\n\t\t// TASK\n\t\tvar grTask = new GlideRecord(\"task\");\n\t\tgrTask.addEncodedQuery('sys_created_onRELATIVEGE@month@ago@9^assignment_group.sys_id='+groupSysId+'^active=true^GROUPBYsys_class_name^ORDERBYsys_created_on');\n\t\tgrTask.query();  \n\t\ttaskRecords = grTask.getRowCount();\n\n\t\t// REPORTS\n\t\tvar grReport = new GlideRecord(\"sys_report\");\n\t\tgrReport.addEncodedQuery('filterLIKE'+groupSysId+'^ORfilterLIKEE'+groupName);\n\t\tgrReport.query();  \n\t\treportRecords = grReport.getRowCount();\n\n\t\t\n\t\t// WORKFLOWS\n\t\tvar grWFActivity = new GlideRecord('wf_activity');  \n\t\tgrWFActivity.addQuery('sys_id', 'IN', getWfActivities(groupSysId, groupName));  \n\t\tgrWFActivity.addQuery('workflow_version.published', 'true');  \n\t\tgrWFActivity.query();  \n\t\twhile (grWFActivity.next()) {  \n\t\t\n\t\t\n   var grh = new GlideRecord(\"wf_workflow_version\");\n   grh.addEncodedQuery(\"sys_id=\" + grWFActivity.workflow_version.sys_id);\n   grh.query();\n   while (grh.next()) {\n\n           workflowVersions += grWFActivity.workflow_version.sys_id + ',';\t\t\n}\n}\t\t\n\t\tworkflowRecords = getCount(workflowVersions);\n\t\tif(workflowRecords == 1){\n\t\tvar sci = sciWorkflow(grh.workflow.sys_id.toString());\n\t\tif(sci){\t\n\t\tworkflowRecords =0;\n\t\tworkflowVersions ='';\n\t\t}\n\t\t}\n\t\t\n\t\tfunction sciWorkflow(workflow){\nvar gr12 = new GlideRecord(\"sc_cat_item\");\ngr12.addEncodedQuery(\"active=false^workflow=\"+workflow);\ngr12.query();\nif (gr12.next()) {\n   return true;\n}\n\t\t}\n\t\t\n\t\tfunction getCount(sysid){\n        var gr = new GlideRecord('wf_workflow_version');\n        gr.addQuery('sys_id','IN',sysid);\n        gr.query();\n        return gr.getRowCount();\n        }\n\t\t\n\t\tfunction getWfActivities(group_id, group_name) {  \n\t\t\tvar grVariables = new GlideRecord('sys_variable_value');  \n\t\t\tgrVariables.addEncodedQuery('valueLIKE'+group_name+'^ORvalueLIKE'+group_id+'^document=wf_activity');\t\n\t\t\tgrVariables.query();  \n\t\t\tvar results = []; \n\t\t\twhile (grVariables.next()) {\n\t\t\t\tresults.push(grVariables.document_key + '');  \n\t\t\t}\n\t\t\treturn results;  \n\t\t}\n\t\t\n\t\t// SCRIPT INCLUDES\n\t\tvar grScriptInclude = new GlideRecord(\"sys_script_include\");\n\t\tgrScriptInclude.addEncodedQuery('scriptLIKE'+groupSysId+'^ORscriptLIKE'+groupName+'^active=true');\n\t\tgrScriptInclude.query();  \n\t\tscriptIncludeRecords = grScriptInclude.getRowCount();\n\t\t\n\t\t// BUSINESS RULES\n\t\tvar grBusinessRules = new GlideRecord(\"sys_script\");\n\t\tgrBusinessRules.addEncodedQuery('active=true^scriptLIKE'+groupName+'^ORscriptLIKE'+groupSysId);\n\t\tgrBusinessRules.query();  \n\t\tbusinessRulesRecords = grBusinessRules.getRowCount();\n\t\t\n\t\t// CLIENT SCRIPT\n\t\tvar grClientScript = new GlideRecord(\"sys_script_client\");\n\t\tgrClientScript.addEncodedQuery('sys_class_name=sys_script_client^active=true^scriptLIKE'+groupName+'^ORscriptLIKE'+groupSysId);\n\t\tgrClientScript.query();  \n\t\tclientScriptRecords = grClientScript.getRowCount();\n\t\t\n\t\t\n\t\t// MAINTAIN ITEMS (CATALOG ITEMS)\n\t\tvar grMaintainItems = new GlideRecord(\"sc_cat_item\");\ngrMaintainItems.addEncodedQuery('u_approval_group_1='+groupSysId+'^ORu_approval_group_2='+groupSysId+'^ORgroup='+groupSysId+'^ORu_fulfillment_group_2='+groupSysId+'^active=true');\t\n\t\tgrMaintainItems.query();  \n\t\tmaintainItemRecords = grMaintainItems.getRowCount();\n\t\t\n\t\t//CMDB CI's\n\t\t\n\t\tvar grBusinessServices = new GlideRecord('cmdb_ci');\ngrBusinessServices.addEncodedQuery('install_status!=7^change_control='+groupSysId+'^ORsupport_group='+groupSysId)\n\tgrBusinessServices.query();\n\t\tbusinessServiceRecords= grBusinessServices.getRowCount();\n\t\t\n\t\t//System Properties\n\tvar grsysProperties = new GlideRecord('sys_properties');\n\t\tgrsysProperties.addEncodedQuery('valueLIKE'+groupSysId);\n\t\tgrsysProperties.query();\n\t\tsystemPropertiesRecords = grsysProperties.getRowCount();\n\t\t\n\t\t//Available for Groups\n\t\tvar grAvaiForGroups = new GlideRecord(\"sc_cat_item_group_mtom\");\n\t\tgrAvaiForGroups.addEncodedQuery('sc_cat_item.active=true^sc_avail_group='+ groupSysId);\n\t\t\n\t\tgrAvaiForGroups.query();  \n\t\tavaiforGroupsRecords = grAvaiForGroups.getRowCount();\n\t\t\n\t\t//Available for Notifications\n\t\tvar grAvaiForNotifications = new GlideRecord(\"sysevent_email_action\");\n\t\tgrAvaiForNotifications.addEncodedQuery('active=true^conditionLIKE'+ groupSysId +'^ORrecipient_groupsLIKE'+ groupSysId + '^ORadvanced_conditionLIKE'+ groupSysId);\n\t\t\n\t\tgrAvaiForNotifications.query();  \n\t\tNotificationRecords = grAvaiForNotifications.getRowCount();\n\t\t\n\t\t//ACL for Groups\n\t\t\n\t\tvar grACL = new GlideRecord(\"sys_security_acl\");\n grACL.addEncodedQuery('scriptLIKE' + groupName + '^ORscriptLIKE' + groupSysId + '^ORconditionLIKE' + groupName + '^ORconditionLIKE' + groupSysId + '^active=true');\ngrACL.query();  \ngrACLRecords = grACL.getRowCount();\n\t\t\n\t</g:evaluate>\n\t\n\t<table width=\"500px\">\n\t\t<tr style=\"font-weight:bold\">\n\t\t\t<td>Module</td>\n\t\t\t<td>Records</td>\n\t\t\t<td>Details</td>\n\t\t</tr><tr class=\"breadcrumb\" >\n\t\t\t<td>TASK</td>\n\t\t\t<td>${taskRecords}</td>\n\t\t\t<td>\n\t\t\t\t<a href=\"task_list.do?sysparm_query=sys_created_onRELATIVEGE%40month%40ago%409%5Eassignment_group.sys_id%3D${groupSysId}%5Eactive%3Dtrue%5EGROUPBYsys_class_name%5EORDERBYsys_created_on\" target=\"blank\">View records</a>\n\t\t\t</td>\n\t\t</tr><tr>\n\t\t\t<td>Workflows</td>\n\t\t\t<td>${workflowRecords}</td>\n\t\t\t<td><a href=\"wf_workflow_version_list.do?sysparm_query=sys_idIN${workflowVersions}\" target=\"blank\">View records</a></td>\n\t\t</tr><tr class=\"breadcrumb\">\n\t\t\t<td>Reports</td>\n\t\t\t<td>${reportRecords}</td>\n\t\t\t<td><a href=\"sys_report_list.do?sysparm_query=filterLIKE${groupSysId}%5EORfilterLIKEE${groupName}\" target=\"blank\">View records</a></td>\n\t\t</tr><tr class=\"breadcrumb\">\n\t\t\t<td>Catalog Items</td>\n\t\t\t<td>${maintainItemRecords}</td>\n\t\t\t<td><a href=\"sc_cat_item_list.do?sysparm_query=u_approval_group_1%3D${groupSysId}%5EORu_approval_group_2%3D${groupSysId}%5EORgroup%3D${groupSysId}%5EORu_fulfillment_group_2%3D${groupSysId}%5Eactive%3Dtrue\" target=\"blank\">View records</a></td>\n\t\t<!--/tr><tr>\n\t\t\t<td>Script Includes</td>\n\t\t\t<td>${scriptIncludeRecords}</td>\n\t\t\t<td><a href=\"sys_script_include_list.do?sysparm_query=scriptLIKE${groupSysId}%5EORscriptLIKE${groupName}%5Eactive%3Dtrue\" target=\"blank\">View records</a></td>\n\t\t</tr><tr>\n\t\t\t<td>Business Rules</td>\n\t\t\t<td>${businessRulesRecords}</td>\n\t\t\t<td><a href=\"sys_script_list.do?sysparm_query=active%3Dtrue%5EscriptLIKE${groupName}%5EORscriptLIKE${groupSysId}\" target=\"blank\">View records</a></td>\n\t\t</tr><tr class=\"breadcrumb\" >\n\t\t\t<td>Client Scripts</td>\n\t\t\t<td>${clientScriptRecords}</td>\n\t\t\t<td><a href=\"sys_script_client_list.do?sysparm_query=sys_class_name%3Dsys_script_client%5Eactive%3Dtrue%5EscriptLIKE${groupName}%5EORscriptLIKE${groupSysId}\" target=\"blank\">View records</a></td>\n\t\t\t</tr><tr class=\"breadcrumb\" >\n\t\t\t<td>Configuration Items</td>\n\t\t\t<td>${businessServiceRecords}</td>\n\t\t\t<td><a href=\"cmdb_ci_list.do?sysparm_query=change_control%3D${groupSysId}%5EORsupport_group%3D${groupSysId}%5Einstall_status!%3D7\" target=\"blank\">View records</a></td>\n\t\t</tr><tr class=\"breadcrumb\" >\n\t\t\t<td>System Properties</td>\n\t\t\t<td>${systemPropertiesRecords}</td>\n\t\t\t<td><a href=\"sys_properties_list.do?sysparm_query=valueLIKE${groupSysId}\" target=\"blank\">View records</a></td>\n\t\t</tr>\n\t\t<tr class=\"breadcrumb\" >\n\t\t\t<td>Available for Groups</td>\n\t\t\t<td>${avaiforGroupsRecords}</td>\n\t\t\t<td><a href=\"sc_cat_item_group_mtom_list.do?sysparm_query=sc_cat_item.sys_class_name%3Dsc_cat_item%5EORsc_cat_item.sys_class_name%3Dsc_cat_item_producer%5EORsc_cat_item.sys_class_name%3Dstd_change_record_producer%5Esc_cat_item.active%3Dtrue%5Esc_avail_group%3D${groupSysId}\" target=\"blank\">View records</a></td>\n\t\t</tr>\n\t\t<tr class=\"breadcrumb\" >\n\t\t\t<td>Notifications</td>\n\t\t\t<td>${NotificationRecords}</td>\n\t\t\t<td><a href=\"sysevent_email_action_list.do?sysparm_query=active%3Dtrue%5EconditionLIKE${groupSysId}%5EORrecipient_groupsLIKE${groupSysId}%5EORadvanced_conditionLIKE${groupSysId}\" target=\"blank\">View records</a></td>\n\t\t</tr>\n\t\t\n\t\t\t<tr class=\"breadcrumb\" >\n\t\t\t<td>Access Controls (ACL)</td>\n\t\t\t<td>${grACLRecords}</td>\n\t\t\t<td><a href=\"sys_security_acl_list.do?sysparm_query=scriptLIKE${groupName}%5EORscriptLIKE${groupSysId}%5Eactive%3Dtrue%5ENQconditionLIKE${groupName}%5EORconditionLIKE${groupSysId}%5Eactive%3Dtrue\" target=\"blank\">View records</a></td>\n\t\t</tr>\n\t</table>\n\t<input type=\"hidden\" id=\"sysparm_group\" value=\"${sysparm_group} - ${taskRecords} \"/>\n\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Kill flow timers/README.md",
    "content": "# UI Action to kill flow timers\n\nUI page that shows you all timers that a flow context [sys_flow_context] is waiting for. Shows sys_id of the sys_trigger, the datetime when the wait will finish and how long until that time. Select the checkbox and submit the modal form to kill that timer.\n\n## How to use\n\n1. Create ui action on [sys_flow_context] and input the script from *UI action.js* in the script field. Check client and set Onclick as *openTimerDialog()*\n2. Create ui page with name *timer_kill_dialog* and copy the HTML, client script and processing script from the *UI page for ui action.js* file\n3. Navigate to active flow context that is waiting for timers and click the ui action from step 1 and kill timer/s. Flow will progress as soon as the flow engine processes the flow.fire events\n\n![image](timer.png)\n\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Kill flow timers/UI action.js",
    "content": "//Onlick: openTimerDialog()\n\n//open dialog using glidemodal and timer_kill_dialog ui page, pass in current sys_flow_context sysid\nfunction openTimerDialog() {\n\tvar dialog = new GlideModal(\"timer_kill_dialog\");\n\tdialog.setTitle(\"Kill timers\");\n\tdialog.setPreference('sysparm_id', g_form.getUniqueValue());\n\tdialog.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Kill flow timers/UI page for ui action.js",
    "content": "//name: timer_kill_dialog\n\n//HTML:\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\t<g:evaluate var=\"jvar_timers\" object=\"true\">\n    // executed server side, gets wait jobs for current flow context and returns object with information about timers\n\t\tvar array = [];\n\t\tvar waitJob = new GlideRecord(\"sys_trigger\");\n\t\twaitJob.addQuery(\"state\", \"0\")\n\t\t\t.addCondition(\"name\", \"flow.fire\")\n\t\t\t.addCondition(\"script\", \"CONTAINS\", RP.getParameterValue(\"sysparm_id\"));\n\t\twaitJob.query();\n\t\twhile (waitJob.next()) {\n\t\t\tvar obj = {};\n\t\t\tobj.sys_id = waitJob.getUniqueValue();\n\t\t\tobj.waitUntil = waitJob.getValue(\"next_action\");\n\t\t\tvar now = new GlideDateTime()\n\t\t\tobj.timeLeft = GlideDateTime.subtract(now, new GlideDateTime(waitJob.getValue(\"next_action\"))).getDisplayValue()\n\t\t\tarray.push(obj)\n\t\t}\n\t\tarray;\n\t</g:evaluate>\n\n\t<style>\n\t\ttable td {\n\t\t\tpadding: 8px;\n\t\t}\n\n\t\ttable thead td {\n\t\t\tfont-weight: bold;\n\t\t\tbackground-color: #f0f0f0;\n\t\t}\n\t</style>\n\n  <!-- submittable form that shows the wait jobs in [sys_trigger] for context and a checkbox to select timers to kill -->\n\t<g:ui_form>\n\t\t<input type=\"hidden\" id=\"sysids\" name=\"sysids\" value=\"\" />\n\t\t<div style=\"padding: 20px; font-family: sans-serif;\">\n\t\t\t<table>\n\t\t\t\t<tbody>\n\t\t\t\t\t<thead>\n\t\t\t\t\t\t<td>timer sys_id</td>\n\t\t\t\t\t\t<td>next action</td>\n\t\t\t\t\t\t<td>time left</td>\n\t\t\t\t\t\t<td>kill?</td>\n\t\t\t\t\t</thead>\n\t\t\t\t\t<j:forEach var=\"jvar_timer\" items=\"${jvar_timers}\">\n\t\t\t\t\t\t<tr>\n\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t${jvar_timer.sys_id}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t${jvar_timer.waitUntil}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t${jvar_timer.timeLeft}\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t\t<td>\n\t\t\t\t\t\t\t\t<g:ui_checkbox name=\"${jvar_timer.sys_id}\">\n\t\t\t\t\t\t\t\t</g:ui_checkbox>\n\t\t\t\t\t\t\t</td>\n\t\t\t\t\t\t</tr>\n\t\t\t\t\t</j:forEach>\n\t\t\t\t</tbody>\n\t\t\t</table>\n\t\t\t<g:dialog_buttons_ok_cancel ok=\"return okDialog()\" />\n\t\t</div>\n\t</g:ui_form>\n</j:jelly>\n\n//Client script:\n// handler for clicking ok on modal. gathers the sys_ids for the timers that have checked checkbox\nfunction okDialog() {\n\tvar c = gel('sysids');\n\tvar sysids = [];\n\t$j('input[type=\"checkbox\"]:checked').each(function () {\n\t\tvar checkboxId = $j(this).attr('id').replace(\"ni.\", \"\");\n\t\tsysids.push(checkboxId);\n\t});\n\tc.value = sysids.toString();\n\treturn true;\n}\n\n//Processing script:\n// queries for timer jobs and sets the job and new flow.fire event to process instantly -> timer on flow completes \nvar waitJob = new GlideRecord(\"sys_trigger\");\nwaitJob.addQuery(\"sys_id\", \"IN\", sysids);\nwaitJob.query();\nwhile (waitJob.next()) {\n\tvar currentScript = waitJob.getValue(\"script\");\n\tvar now = new GlideDateTime().getValue();\n\tvar replaceScript = currentScript.replace(/gr\\.setValue\\('process_on',\\s*'[^']*'\\)/, \"gr.setValue('process_on','\" + now + \"')\");\n\twaitJob.setValue(\"script\", replaceScript);\n\twaitJob.setValue(\"next_action\", now);\n\twaitJob.update();\n}\n//redirect back to bottom of nav stack\nvar urlOnStack = GlideSession.get().getStack().bottom();\nresponse.sendRedirect(urlOnStack);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Knowledge Link Validator/Readme.md",
    "content": "This utility script helps ServiceNow administrators and content managers ensure the integrity and usability of hyperlinks embedded within knowledge articles. It scans article content to identify and classify links pointing to catalog items and other knowledge articles, providing detailed insights into:\n\nCatalog Item Links: Detects and categorizes links as active, inactive, or not found.\nKnowledge Article Links: Flags outdated articles based on workflow state and expiration (valid_to).\nNon-Permalink KB Links: Identifies knowledge article links that do not follow the recommended permalink format (i.e., missing sysparm_article=KBxxxxxxx), even if they use kb_view.do.\nThe solution includes a Jelly-based UI that displays categorized results with direct links to the affected records, enabling quick remediation. It's ideal for improving content quality, ensuring consistent user experience, and maintaining best practices in knowledge management.\n\n<img width=\"815\" height=\"231\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7a1d8947-077b-45cd-8b5a-a2bc8e4b50e8\" />\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Knowledge Link Validator/uiaction.js",
    "content": "/*\nThis script should be placed in the UI action on the table kb_knowledge form view.\nThis UI action should be marked as client.\nUse validateLinksInArticle() function in the Onclick field.\n*/\n\nfunction validateLinksInArticle() {\n    var articleSysId = g_form.getUniqueValue();\n    var gdw = new GlideDialogWindow('validate_links_dialog');\n    gdw.setTitle('Validate Article Links');\n    gdw.setPreference('sysparm_article_id', articleSysId);\n    gdw.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Knowledge Link Validator/uipage.js",
    "content": "<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\">\n    <j:set var=\"jvar_article_id\" value=\"${sysparm_article_id}\" />\n    <g:evaluate jelly=\"true\">\n        <![CDATA[\n      var articleId = trim(jelly.sysparm_article_id);\n      var activeIds = [];\n      var inactiveIds = [];\n      var notFoundIds = [];\n      var outdatedArticles = [];\n      var badPermalinks = [];\n  var inActiveCount =0;\n  var activeCount = 0;\n  var notFoundCount =0;\n  var outdatedCount =0;\n  var badPermalinkCount =0;\n  var inactiveQuery;\n  var activeQuery;\n  var notFoundQuery;\n  var outdatedQuery;\n  var badPermalinkQuery;\n      if (articleId) {\n          var grArticle = new GlideRecord('kb_knowledge');\n          if (grArticle.get(articleId)) {\n              var content = (grArticle.text || '').toString();\n              // Extract hrefs from <a> tags\n              var regex = /<a[^>]+href=[\"']([^\"']+)[\"']/gi;\n              var urls = [];\n              var match;\n              while ((match = regex.exec(content)) !== null) {\n\t\t\t\t\n                  urls.push(match[1]);\n              }\n              for (var i = 0; i < urls.length; i++) {\n                  var url = urls[i];\n\t\t\t\t\n  // --- 1. Check if link is a Catalog Item ---\n                  var sysId = extractSysId(url, 'sysparm_id') || extractSysId(url, 'sys_id');\n                  if (sysId) {\n                      var grItem = new GlideRecord('sc_cat_item');\n                      if (grItem.get(sysId)) {\n                          if (grItem.active){\n                              activeIds.push(sysId);\n  activeCount++;\n  }\n                          else if(grItem.active == false){\n                              inactiveIds.push(sysId);\n  inActiveCount++;\n  }\n                      } else {\n                          notFoundIds.push(sysId);\n  notFoundCount++;\n                      }\n                  }\n  // --- 2. Check if link is a Knowledge Article ---\n // --- 1. Check for outdated knowledge articles via permalink ---\n\n// --- 1. Check for outdated knowledge articles via permalink ---\nvar decodedUrl = decodeURIComponent(url + '');\ndecodedUrl = decodedUrl.replace(/&amp;amp;amp;amp;/g, '&');\n\n// Extract KB number or sys_id\nvar kbNumber = extractSysId(decodedUrl, 'sysparm_article');\nvar kbSysId = extractSysId(decodedUrl, 'sys_kb_id') || extractSysId(decodedUrl, 'sys_id');\n\nvar grKb = new GlideRecord('kb_knowledge');\n\nif (kbNumber && grKb.get('number', kbNumber)) {\n    var isOutdated = false;\n    if (grKb.workflow_state != 'published') {\n        isOutdated = true;\n    } else if (grKb.valid_to && grKb.valid_to.getGlideObject()) {\n        var now = new GlideDateTime();\n        if (grKb.valid_to.getGlideObject().compareTo(now) <= 0) {\n            isOutdated = true;\n        }\n    }\n\n    if (isOutdated) {\n        outdatedArticles.push(grKb.sys_id.toString());\n        outdatedCount++;\n    }\n} else if (kbSysId && grKb.get(kbSysId)) {\n    var isOutdated = false;\n    if (grKb.workflow_state != 'published') {\n        isOutdated = true;\n    } else if (grKb.valid_to && grKb.valid_to.getGlideObject()) {\n        var now = new GlideDateTime();\n        if (grKb.valid_to.getGlideObject().compareTo(now) <= 0) {\n            isOutdated = true;\n        }\n    }\n\n    if (isOutdated) {\n        outdatedArticles.push(grKb.sys_id.toString());\n        outdatedCount++;\n    }\n}\n\n// --- 2. Check for non-permalink knowledge links ---\nif (\n    decodedUrl.indexOf('kb_knowledge.do?sys_id=') !== -1 || // form view\n    (\n        decodedUrl.indexOf('/kb_view.do') !== -1 && \n        decodedUrl.indexOf('sysparm_article=KB') === -1 // missing KB number\n    )\n) {\n    var kbSysId = extractSysId(decodedUrl, 'sys_kb_id') || extractSysId(decodedUrl, 'sys_id');\n    if (kbSysId) {\n        var grBadKB = new GlideRecord('kb_knowledge');\n        if (grBadKB.get(kbSysId)) {\n            badPermalinks.push(kbSysId);\n            badPermalinkCount++;\n        }\n    }\n}\n              }\n          }\n      }\n function extractSysId(url, param) {\n    try {\n        var decoded = decodeURIComponent(url + '');\n        decoded = decoded\n            .replace(/&amp;amp;amp;/g, '&')\n            .replace(/&amp;amp;/g, '&')\n            .replace(/&amp;/g, '&')\n            .replace(/&#61;/g, '=')\n            .replace(/&amp;#61;/g, '=');\n\n        var parts = decoded.split(param + '=');\n        if (parts.length > 1) {\n            var id = parts[1].split('&')[0];\n            return id && id.length === 32 ? id : null;\n        }\n    } catch (e) {\n        var parts = url.split(param + '=');\n        if (parts.length > 1) {\n            var id = parts[1].split('&')[0];\n            return id && id.length === 32 ? id : null;\n        }\n    }\n    return null;\n}\n      // Expose variables to Jelly\ninactiveQuery = \"sys_idIN\"+inactiveIds.join(',');\nactiveQuery = \"sys_idIN\"+activeIds.join(',');\nnotFoundQuery = \"sys_idIN\"+notFoundIds.join(',');\noutdatedQuery = \"sys_idIN\"+outdatedArticles.join(',');\nbadPermalinkQuery = \"sys_idIN\"+badPermalinks.join(',');\n  ]]>\n    </g:evaluate>\n    <table width=\"600px\" border=\"1\" style=\"border-collapse:collapse;\">\n        <tr style=\"font-weight:bold; background-color:#f2f2f2;\">\n            <td>Module</td>\n            <td>Records</td>\n            <td>Details</td>\n        </tr>\n        <tr class=\"breadcrumb\">\n            <td>Active Catalog Items</td>\n            <td>${activeCount}</td>\n            <td>\n                <a href=\"sc_cat_item_list.do?sysparm_query=${activeQuery}\" target=\"_blank\">View records</a>\n            </td>\n        </tr>\n        <tr class=\"breadcrumb\">\n            <td>Inactive Catalog Items</td>\n            <td>${inActiveCount}</td>\n            <td>\n                <a href=\"sc_cat_item_list.do?sysparm_query=${inactiveQuery}\" target=\"_blank\">View records</a>\n            </td>\n        </tr>\n        <tr class=\"breadcrumb\">\n            <td>Not Found Items</td>\n            <td>${notFoundCount}</td>\n            <td>\n                <a href=\"sc_cat_item_list.do?sysparm_query=${notFoundQuery}\" target=\"_blank\">View records</a>\n            </td>\n        </tr>\n        <tr class=\"breadcrumb\">\n            <td>Outdated Knowledge Articles</td>\n            <td>${outdatedCount}</td>\n            <td>\n                <a href=\"kb_knowledge_list.do?sysparm_query=${outdatedQuery}\" target=\"_blank\">View records</a>\n            </td>\n        </tr>\n        <tr class=\"breadcrumb\">\n            <td>Non-Permalink Knowledge Links</td>\n            <td>${badPermalinkCount}</td>\n            <td>\n                <a href=\"kb_knowledge_list.do?sysparm_query=${badPermalinkQuery}\" target=\"_blank\">View records</a>\n            </td>\n        </tr>\n    </table>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Mark Records Inactive - List Action/README.md",
    "content": "Above two scripts will help you to select records in list view and mark them inactive. You can create your UI action(list action) on any table and then you should be able to\nmark the records as inactive by calling the reusable script include. Process is pretty simple as shown below:\n1. Create a List action - list banner button or list choice.\n2. Check the client checkbox. Use the script and do any necessary modifications.\n3. Keep your script include ready with the function to make records inactive and done.\n\nYou can even modify the scrip include to change other fields too based on your requirements. And you do not need to pass any table name also. This is complete generic.\n\n**UPDATE:**\n_Replaced standard Javascript Window method 'alert' with GlideModal as per issue #745. This completes the task 'Mark Records Inactive UI Action'_\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Mark Records Inactive - List Action/listAction.js",
    "content": "// Create a List Action. Make it as List Banner Button or List choice. Keep the client checkbox checked and the use the below script.\n\nvar selRecords;\n\nfunction markInactive() {\n    selRecords = g_list.getChecked(); //Get the sysIds of selected records from list view\n\n    var ga_inactive = new GlideAjax('MarkRecordsInactive'); // call the script include for the same\n\n    ga_inactive.addParam('sysparm_name', 'markInactiveRecords');\n    ga_inactive.addParam('sysparm_ids', selRecords);\n    ga_inactive.addParam('sysparm_table', g_list.getTableName());\n\n    ga_inactive.getXML(ResponseFunction);\n\n    function ResponseFunction(response) {\n        var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n        //alert(answer.toString());\n        // Commented above code and replaced it with GlideModal\n        var gm = new GlideModal(\"glide_alert_standard\", false, 600);\n        gm.setTitle(\"Info Message\");\n        gm.setPreference(\"title\", answer.toString());\n        gm.setPreference(\"warning\", \"false\");\n        gm.render();\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Mark Records Inactive - List Action/scriptInclude.js",
    "content": "var MarkRecordsInactive = Class.create();\nMarkRecordsInactive.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    markInactiveRecords: function() {\n        var sysIds = this.getParameter('sysparm_ids');\n        var tableName = this.getParameter('sysparm_table');\n        \n\t\tvar recs = new GlideRecordSecure(tableName);\n\t\trecs.addEncodedQuery('sys_idIN'+sysIds);\n\t\trecs.query();\n\t\twhile(recs.next())\n\t\t\t{\n\t\t\t\trecs.active = false;\n\t\t\t\trecs.update();\n\t\t\t}\n\t\treturn 'All the selected records are marked Inactive';\n    },\n    type: 'MarkRecordsInactive'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Email Client using UI Action/README.md",
    "content": "When an agent needs a button on the form to open Email Client in a single click, This UI action can be used, which works similar to the out of box Email button. Some user may not prefer the out of box button because it is two clicks.\nTo use this UI action, mark the UI action as Client and add the openEmailClient function in the onClick Field in the UI action. Then use the script provided in the script field.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Email Client using UI Action/open_email_client.js",
    "content": "// This is a Client UI action. In the onClick field, call the function openEmailClient() and use below script in the script section.\nfunction openEmailClient(){\n  emailClientOpenPop('<table_name>');\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open LIST UI Action/README.md",
    "content": "This UI Action opens the LIST view for the current table in another tab.\nUI action will run on Onclick openinLIST()\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open LIST UI Action/UIActionscript.js",
    "content": "function openinLIST() {\n    var taskTable = g_form.getTableName();\n\n    // Construct the hardcoded LIST URL\n    var listURL = '/' + taskTable + '_list.do?sysparm_clear_stack=true';\n\n    // Open in new window\n    var w = getTopWindow();\n    w.window.open(listURL, '_blank');\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/README.md",
    "content": "This set of:\n\n- RESTMessage\n- Script Includes\n- Scripted REST API (SRAPI)\n- UI Action\n\nWill allow you to open a record in alternate corresponding instance(s) of ServiceNow.\nFor example, from a record in a PROD instance, you can open the correlating record in your DEV instance.\n\nThe 'Starting Instance', refers to the instance you are currently in, and the 'Target Instance' refers to the instance you want to open the record in.\nAny instance of `[Target Instance]` in these files will need to be replaced.\n\n- The RESTMessage will need to exist on the 'Starting' Instance\n- The Script Includes will need to exist on the 'Starting' Instance\n- The UI Action will need to exist on the 'Starting' Instance\n- The SRAPI will need to exist on the Target Instance\n\nAny file ending in `_config.md` relates to field configuration of the record."
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/RESTMessageV2/sys_rest_message_config.md",
    "content": "Name: Cross-Instance Record Checking\nEndpoint: [An Instance Url]/api/[api subpath, i.e., riot]/recordapi/record_exists\n\nSee `sys_rest_message_fn_config.md` for HTTP Method setup."
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/RESTMessageV2/sys_rest_message_fn_config.md",
    "content": "Name: [Target Instance] Record Exists\nHTTP Method: PATCH\nEndpoint: [Target Instance Url]/api/[api subpath]/.../record_exists\n\nHTTP Request Parameters:\n\n    {\n        \"table\": \"${table}\",\n        \"sysId\": \"${sysId}\"\n    }"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/Script Includes/README.md",
    "content": "\n!! The switch statement in CrossInstanceHelper.js will need to be reconfigured to match instance names, and RESTMessage naming."
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/Script Includes/sys_script_include.js",
    "content": "var CrossInstanceHelper = Class.create();\nCrossInstanceHelper.prototype = {\n    initialize: function() {\n    },\n\t\n\t//Defines a filter so that tables that will never exist in other environments are not checked.\n\t_goodTable: function(table){\n\t\t\n\t\tvar disallowedTables = [\n\t\t\t'sys_update_set_source',\n\t\t\t'sys_update_set',\n\t\t\t'sys_email',\n\t\t\t'syslog'\n\t\t];\n\t\t\n\t\t//If the table is disallowed, return false\n\t\tif(disallowedTables.indexOf(table) != -1) return false;\n\t\t\n\t\t//If the table extends task, or extends a table that extends task, return false\n\t\treturn !(new TableUtils('task').getTableExtensions().indexOf(table) != -1 || new TableUtils(table).getAbsoluteBase() == 'task');\n\t},\n\t\n\texists: function(instance, table, sys_id){\n\t\t\n\t\t//If the table is disallowed, return\n\t\tif(!_goodTable(table)) return false;\n\t\t\n\t\t//Determine endpoint from instance\n\t\tvar endpoint;\n\t\tinstance = instance.toLowerCase();\n\n        /*\n            These are example instance names and their corresponding endpoints. You will need to add your own instances and endpoints here.\n        */\n\t\tswitch(instance){\n\t\t\tcase 'prod':\n\t\t\t\tendpoint = 'PROD Exists';\n\t\t\t\tbreak;\n\t\t\tcase 'test':\n\t\t\t\tendpoint = 'TEST Exists';\n\t\t\t\tbreak;\n\t\t\tcase 'dev':\n\t\t\t\tendpoint = 'DEV Exists';\n\t\t\t\tbreak;\n\t\t}\n\t\t\n\t\t//Build the REST Message\n\t\tvar r = new sn_ws.RESTMessageV2('Cross-Instance Record Checking', endpoint);\n\t\tr.setStringParameterNoEscape('table', table);\n\t\tr.setStringParameterNoEscape('sysId', sys_id);\n\t\t\n\t\t//Execute the request\n\t\tvar response = r.execute();\n\t\t\n\t\t//Return whether or not the record exists (true if it exists)\n\t\treturn(response.getStatusCode().toString() == '200');\n\t},\n\n    type: 'CrossInstanceHelper'\n};"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/Script Includes/sys_script_include_config.md",
    "content": "Name: CrossInstanceHelper\nClient callable: false\n\nScript: See `sys_script_include.js` for code."
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/Scripted REST API/sys_ws_definition_config.md",
    "content": "General:\n\n    Name: RecordAPI\n    API ID: recordapi\n\nContent Negotiation:\n\n    Supported request formats: application/json,application/xml,text/xml\n    Supported response formats: application/json,application/xml,text/xml"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/Scripted REST API/sys_ws_operation/sys_ws_operation.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n\tfunction isNull(obj){\n\t\treturn(obj == '' || obj == undefined  || obj == 'undefined' || obj == null || obj == 'null');\n\t}\n\n\t//Get data from request\n\tvar requestBody = request.body;\n\tvar requestData = requestBody.data;\n\t\n\t//Get table name from request\n\tvar tableName = requestData.table;\n\n\t//Get sys_id from request\n\tvar sysId = requestData.sysId;\n\n\t//Make sure data exists\n\tif(isNull(tableName) || isNull(tableName.toString()) || isNull(sysId) || isNull(sysId.toString())){\n\t\tresponse.setStatus(400);\n\t\treturn;\n\t}\n\t\n\t//Start lookup\n\tvar rGr = new GlideRecord(tableName);\n\trGr.get('sys_id', sysId);\n\n\tvar recordExists = !rGr.sys_id == '';\n\n\tresponse.setBody(recordExists ? \"Resource exists.\" : \"Resource does not exist.\");\n\tresponse.setStatus(recordExists ? 200 : 404);\n\n})(request, response);"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/Scripted REST API/sys_ws_operation/sys_ws_operation_config.md",
    "content": "General:\n\n    HTTP Method: PATCH\n    Relative Path: /record_exists\n\nContent Negotiation:\n\n    Request formats: application/json,application/xml,text/xml\n    Response formats: application/json,application/xml,text/xml\n\nScript: see `sys_ws_operation.js` for code."
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/UI Action/sys_ui_action.js",
    "content": "function openInInstance(){\n\n    //Since we are in a client action, get the current record utilizing the URL\n    if(!top.location) return;\n    var href = top.location.href.toString();\n\n    //Grab record from current URL\n    var recordHref = href.split('.com/')[1];\n\n    //Open the record in the new instance\n    window.open('https://[Target Instance].service-now.com/nav_to.do?uri=' + recordHref, '_blank');\n\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record in Alternate Instance/UI Action/sys_ui_action_config.md",
    "content": "!! If your instances use vanity urls, or otherwise do not end in `.com`, the `split()` in code.js will need to be edited\n\nName: Open Record in [Target Instance]\nTable: Global [global]\nAction Name: Open Record in [Target Instance]\n\nClient: true\nList v2 Compatible: true\nList v3 Compatible: true\n\nIsolate script: false\n\nOnclick: openInInstance()\n\nCondition:\n\n    gs.getProperty('[Target Instance]') != '[target instance name]' && !current.isNewRecord() && new CrossInstanceHelper().exists('[Target Instance]', current.getTableName(), current.sys_id.toString())\n\nScript: See `sys_ui_action.js` for code."
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record producer from Form Button In Configurable workspace/OpenItem.js",
    "content": "/* \nUI Action\n   Client - true\n   action name - open_item\n   show update - true ( As per your requirement)\n   onClick - openItem();\n   Workspace Form button - true\n   Format for Configurable Workspace - true \n*/\n\n//Workspace client script: \nfunction onClick() {\n    var result = g_form.submit('open_item');\n    if (!result) {\n        return;\n    }\n    result.then(function() {\n        var params = {};\n        params.sysparm_parent_sys_id = g_form.getUniqueValue();\n        params.sysparm_shortDescription = g_form.getValue('short_description');\n        //add params as required, These params can be parsed and used in the record producer.\n        g_service_catalog.openCatalogItem('sc_cat_item', 'recordproducer_sysid_here', params); \n        //Use the record producer sys_id in second parameter.\n    });\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record producer from Form Button In Configurable workspace/ParseUrl.js",
    "content": "/*\nCatalog Client script on the record producer/catalog item you want to open from ui action\n   Name - ParseURL\n   Type - Onload\n   Applies on catalog item view - true\n*/\n\nfunction onLoad() {\n\n  \n\tg_form.setValue('task',parseURL('sysparm_parent_sys_id'));\n\tg_form.setValue('description',parseURL('sysparm_description));\n\t\n\tfunction parseURL(paramName) {\n\t\t\n\t\tvar url = decodeURIComponent(top.location.href); //Get the URL and decode it\n\t\tvar workspaceParams = url.split('extra-params/')[1]; //Split off the url on Extra params\n\t\tvar allParams = workspaceParams.split('/'); //The params are split on slashes '/'\n\t\t\n\t\t//Search for the parameter requested\n\t\tfor (var i=0; i< allParams.length; i++) {\n\t\t\tif(allParams[i] == paramName) {\n\t\t\t\treturn allParams[i+1];\n\t\t\t}\n\t\t}\n\t}\n}\n/*\npass the parameter name which was used in the ui action to parse the value here.\nExample - parseURL('sysparm_description) as I am passing description value in the params from ui action.\n*/\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open Record producer from Form Button In Configurable workspace/README.md",
    "content": "When we want to open a catalog item with details from current record and map it to the opened catalog form then we can use this code.\n\nThis UI Action and catalog client script Redirects you to the record producer or catalog item( based on the sys id provided) and auto-populates the fields from the parent record to the catalog item/record producer variables.\n\n1. UI Action\n   Client - true\n   action name - open_item\n   show update - true ( As per your requirement)\n   onClick - openItem();\n   Workspace Form button - true\n   Format for Configurable Workspace - true \n\n2. Catalog Client script.\n   Type - Onload\n   Applies on catalog item view - true\n   Name - ParseURL\n\nNote : Above UI Action works in Configurable workspace and opens the catalog item/record producer in workspace itself.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open a new blank form/README.md",
    "content": "UI Action - This script is server side script add it to the UI action to create a new blank form.\n\nWhen clicked on it will create a New Blank form and will redirect user to this newly created blank form.\n\nincident.do is used as URL paramenter will open new blank form for incident , we can change this to any other table ( for example - problem.do  will open new blank problem form)\n\naction.setRedirectURL() is a method used in server-side scripting within UI Actions to redirect users to a specific URL after a UI action is performed. \nIt is commonly used to navigate users to different records, forms, or list views after they have completed an action.\nSyntax -  action.setRedirectURL(URL);\nParameters:\nURL: The URL to which the user will be redirected. This can be a string representing a GlideURL object or a hardcoded URL. It must point to a valid ServiceNow page (record, list, form, etc.).\nReturn:\nNone. It performs a redirection after the script completes.\n\nGlideURL is a class in ServiceNow used for constructing URLs dynamically in server-side scripts. \nIt allows developers to programmatically create and manipulate URLs to redirect users, perform navigation, or link to specific ServiceNow resources (e.g., forms, lists, reports).\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open a new blank form/script.js",
    "content": "//Create New blank incident form\n//Server side Script\n\nvar newFormURL = new GlideURL('incident.do');\nnewFormURL.addParam('sys_id', '-1');  // Open a new blank form\naction.setRedirectURL(newFormURL.toString());\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open in Service Operations Workspace/README.md",
    "content": "# Open in Service Operation Workspace\n\nCreate UI Action with:\n\nTable: `Task`\n\nOnclick: `openInServiceOperationWorkspace()`\n\nWill open any task record in SOW\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Open in Service Operations Workspace/ui_action_script.js",
    "content": "function openInServiceOperationWorkspace() {\n    var taskSysID = g_form.getUniqueValue();\n    var taskTable = g_form.getTableName();\n    \n    // Construct the hardcoded Service Operation Workspace URL\n    var workspaceURL = '/now/sow/record/' + taskTable + '/' + taskSysID;\n    \n    // Open in new window\n    var w = getTopWindow();\n    w.window.open(workspaceURL, '_blank');\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Populate Due Date based on Priority/Readme.md",
    "content": "**Calculate the due date based on the Priority**\n\nScript Type: UI Action, Table: incident, Form button: True, Show update: True, Condition: (current.due_date == '' && current.priority != '5'), OnClick: functionName()\n\nScript Type: Script Include, Glide AJAX enabled: False\n\nSchedule- Name: Holidays, Time Zone: GMT\n\nSchedule Entry - Name:  New Year's Day, Type: Exclude, Show as: Busy, When: 31-12-2024, To: 01-01-2025\nSchedule Entry - Name:  Christmas Day, Type: Exclude, Show as: Busy, When: 24-12-2025, To: 25-12-2025\nSchedule Entry - Name:  Thanksgiving Day, Type: Exclude, Show as: Busy, When: 26-11-2025, To: 27-11-2025\nSchedule Entry - Name:  Diwali, Type: Exclude, Show as: Busy, When: 19-10-2025, To: 20-10-2025\n\n**Goal:** To Calculate Due-Date based on Priority with some conditions.\n\n**Walk through of code:** So in this use case the UI Action is been used and then Script Include for server calculate is used.So the main to calculate the due-date by the user trigger.\n\nUI Action- So this button will check the priority and check the due date field is empty or not if not then will fetch the value of \"Priority\" and \"Created date\" and pass the data to the Script Include for calculation once it gets the response will populate the value to the due_date field in the incident table and then update it.\n\nScript Include- The role of this is to get the \"Priority\" and \"Created date\" based on prioriy this will calculate the time and date by using th GlidDateTime API and the will do some additional changes based on each priorit which is mentioned below and then return the response back to the UI Action,\n\nSchedule & Schedule Entry- It is used for the P3 and P4 Priority which is mentioned below for the use case.To exclude the Holidays.\n\nThese are the use case which the above functionality works,\n\n**1-> P1** - add 4hrs to the Created date\n**2-> P2 **- add 4hrs to the Created date but if it's exceed the working hrs of of 5 PM the add to the next day or if the is before the working hours of 8 AM set 5 PM to the same Created date.\n**3-> P3 or P4** - Kind of low priority so add the due date to the next day but it should exclude the holidays and the weekend's and the populate the next business working day.\n**4-> P5 **- User manually will populate the due date based on the process.\n\nThe UI Action on the Incident Form\n<img width=\"815\" height=\"382\" alt=\"Button\" src=\"https://github.com/user-attachments/assets/68876b10-e6e0-43b9-9ecf-f6eb95b7ef87\" />\n\nUI Action which will call the Script Include\n<img width=\"556\" height=\"425\" alt=\"UI Action\" src=\"https://github.com/user-attachments/assets/2715232a-000b-4520-8b1a-f5bf72afdaa9\" />\n\nScript Include\n<img width=\"817\" height=\"416\" alt=\"SI\" src=\"https://github.com/user-attachments/assets/5ddb332c-d23f-4746-b014-1a71acb59186\" />\n\nSchedules and Schedule Entry\n<img width=\"839\" height=\"431\" alt=\"Schedules\" src=\"https://github.com/user-attachments/assets/f96ea4dc-e2d4-4d8f-87b7-67df66a2d8af\" />\n<img width=\"917\" height=\"370\" alt=\"Schedule Entry\" src=\"https://github.com/user-attachments/assets/c4fec5ce-8ee4-46cc-8673-2d22a27290f1\" />\n\nOutput\n<img width=\"828\" height=\"356\" alt=\"Priority 1\" src=\"https://github.com/user-attachments/assets/7f4049b6-294e-4064-bab1-e2d1ab938bfd\" />\n<img width=\"817\" height=\"416\" alt=\"Priority 2\" src=\"https://github.com/user-attachments/assets/37f0cffd-f05c-4f4d-bf35-36090f02ee3b\" />\n<img width=\"815\" height=\"334\" alt=\"Priority 4\" src=\"https://github.com/user-attachments/assets/0ab4ab27-4726-4ea7-b99e-7e35f9b0f4c7\" />\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Populate Due Date based on Priority/ScriptInclude.js",
    "content": "/* \r\n\r\nInput\r\n1. Created Date\r\n2. Priority\r\n\r\nOutput\r\n1. Due Date\r\n\r\nBased on Priority equivalent due dates\r\n\r\nP1 - add 4hrs to the Created date\r\nP2 - add 4hrs to the Created date but if it's exceed the working hrs of of 5 PM the add to the next day or if the is before the working hours of 8 AM set 5 PM to the same Created date.\r\nP3 or P4 - Kind of low priority so add the due date to the next day but it should exclude the holidays and the weekend's and the populate the next business working day.\r\n\r\n*/\r\n\r\n\r\n// This SI findDueDate() function will help to calculate the duration based on the each priority.\r\n\r\nvar CalculateDueDates = Class.create();\r\nCalculateDueDates.prototype = {\r\n    initialize: function() {},\r\n\r\n    findDueDate: function(priority, created) {\r\n        var dueDateVal;\r\n\r\n\r\n\t\t// For the Priority 1 and adding 4 hours in reagrd less of 8-5 working hours and then holidays\r\n        if (priority == 1) {\r\n            var now = new GlideDateTime(created);\r\n            now.addSeconds(60 * 60 * 4); // Add 4 hours\r\n            dueDateVal = now;\r\n            return dueDateVal;\r\n\r\n        } \r\n\r\n\t\t// For the Priority 2 and adding the 4 hours if exceed the workin hours then add the next day before 5'o Clock\r\n\t\telse if (priority == 2) {\r\n            var dueDate = new GlideDateTime(created);\r\n            dueDate.addSeconds(60 * 60 * 4); // Add 4 hours\r\n            dueDate = dueDate+'';\r\n            var hours = Number((dueDate + '').slice(11, 13));\r\n        \r\n            if (hours >= 0 && hours < 12) {\r\n                gs.addInfoMessage('if Inside 8-5/7');\r\n                dueDateVal = dueDate.slice(0, 10) + \" 17:00:00\";\r\n                return dueDateVal;\r\n\r\n            } else if (hours >= 17 && hours <= 23) {\r\n                var nextDate = new GlideDateTime(created);\r\n                nextDate.addDaysUTC(1);\r\n                var newDue = new GlideDateTime(nextDate.getDate().getValue() + \" 17:00:00\");\r\n                dueDateVal = newDue;\r\n                return dueDateVal;\r\n            } else {\r\n                dueDateVal = dueDate;\r\n                return dueDateVal;\r\n            }\r\n\r\n        } \r\n\t\t\r\n\t\t// For the Priority 3 or 4 add the next day and then if the due date is holiday or weekend populate the next working day in a respective field\r\n\t\telse if (priority == 3 || priority == 4) {\r\n            var schedule = new GlideSchedule();\r\n\t\t\t// cmn_schedule for the Holidays\r\n            var scheduleId = 'bd6d74b2c3fc72104f7371edd40131b7';\r\n            schedule.load(scheduleId);\r\n\r\n            var nextDay = new GlideDateTime(created);\r\n            nextDay.addDaysUTC(1);\r\n\r\n\r\n            //Checking for weekends\r\n            var dayOfWeek = nextDay.getDayOfWeekUTC();\r\n\r\n            var isWeekend = (dayOfWeek == 6 || dayOfWeek == 7);\r\n\r\n\r\n            // Loop until next working day (weekdays excluding holidays)\r\n            while (schedule.isInSchedule(nextDay) || isWeekend) {\r\n                nextDay.addDaysUTC(1);\r\n                dayOfWeek = nextDay.getDayOfWeekUTC();\r\n                isWeekend = (dayOfWeek == 6 || dayOfWeek == 7);\r\n            }\r\n\r\n            // Set to 12:00 PM on that valid day\r\n            var validDate = new GlideDateTime(nextDay.getDate().getValue() + \" 17:00:00\");\r\n            return validDate;\r\n        }\r\n    },\r\n\r\n    type: 'CalculateDueDates'\r\n};"
  },
  {
    "path": "Client-Side Components/UI Actions/Populate Due Date based on Priority/UI Action.js",
    "content": "/* \r\nTable - incident\r\nShow Update - True\r\nForm Button - True\r\nCondition - (current.due_date == '' && current.priority != '5')\r\n\r\nInput\r\n1. Created Date\r\n2. Priority\r\n\r\nValidation\r\nWill not appeare if the value is already there and the priority is 5\r\n\r\nOutput\r\n1. Due Date\r\n\r\n*/\r\n\r\n\r\n// The function duedate is used to pass the priority and then created display value to the script include where the calculate of Due date is done will get the response and the set the value to the due_date field of incident.\r\nfunction duedate() {\r\n\r\n    var priority = current.getValue('priority');\r\n    var created = current.getDisplayValue('sys_created_on');\r\n    var si = new CalculateDueDates();\r\n    var response = si.findDueDate(priority, created);\r\n    var gdt = new GlideDateTime();\r\n    gdt.setDisplayValue(response);\r\n    current.setValue('due_date', gdt);\r\n    current.update();\r\n    action.setRedirectURL(current);\r\n\r\n}\r\nduedate();"
  },
  {
    "path": "Client-Side Components/UI Actions/Preview context record during approval/README.md",
    "content": "While approving any request it was very hard until now to preview the record for which the approval was required. This UI action created on sysapproval_approver table will enable previewing the record before approval so that the approver can make an easy informed decision.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Preview context record during approval/TestScriptInclude.js",
    "content": "var TestScriptInclude = Class.create();\nTestScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\tgetDocumentClass: function(){\n\t\tvar sysId = this.getParameter(\"sysparm_sys_id\");\n\t\tvar gr = new GlideRecord(\"sysapproval_approver\");\n\t\tif(gr.get(sysId)){\n\t\t\treturn JSON.stringify({\n\t\t\t\t\"table\": gr.source_table.toString(),\n\t\t\t\t\"document\": gr.document_id.toString(),\n\t\t\t\t\"title\": gr.document_id.getDisplayValue()\n\t\t\t});\n\t\t}\n\t},\n\n    type: 'TestScriptInclude'\n});"
  },
  {
    "path": "Client-Side Components/UI Actions/Preview context record during approval/UI_Action.js",
    "content": "/*\nThis script should be placed in the UI action on the table sysapproval_approver.\nThis UI action should be marked as client callable script include.\nUse openContextRecord() function in the Onclick field.\n*/\n\nfunction openContextRecord() {\n    var rec = g_form.getDisplayValue(\"document_id\");\n    var gaSi = new GlideAjax(\"TestScriptInclude\");\n    gaSi.addParam(\"sysparm_name\", \"getDocumentClass\");\n    gaSi.addParam(\"sysparm_sys_id\", g_form.getUniqueValue());\n    gaSi.getXMLAnswer(function(response) {\n        var answer = JSON.parse(response);\n        var gp = new GlideModalForm(answer.title, answer.table, function(){});\n        gp.addParm('sys_id', answer.document);\n        gp.render();\n    });\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Printer Friendly Version/README.md",
    "content": "## Overview\n\n\nThis code snippet UI Action will allow you to have a printer friendly version of whatever record you might are trying to print. \nThis UI action uses the GlideNavigation API which you can find here [GlideNavigation API](https://developer.servicenow.com/dev.do#!/reference/api/zurich/client/c_GlideNavigationV3API#r_GNV3-openPopup_S_S_S_B)\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Printer Friendly Version/printer_friendly_verison.js",
    "content": "printView()\n\nfunction printView() {\n\n  var table = g_form.getTableName();\n  var recordID = g_form.getUniqueValue();\n  var view = {{Insert the view you want to print here}}; //You can pass in an empty string and it will still work\n  var windowName = {{Insert the name you want your window to display}}; //You can pass in an empty string and it will still work\n  var features = 'resizeable,scrollbar'; //You can pass in an empty string and it will still work\n  var urlString = '/' + table + \".do?sys_id=\" + recordID + \"&sysparm_view=\" + view + \"&sysparm_media=print\";\n  var noStack = true; //Flag that indicates whether to append sysparm_stack=no to the URL\n\n  g_navigation.openPopup(urlString, windowName, features, noStack);\n\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Publish a Retired Knowledge Article again/README.md",
    "content": "**UI Action**: \nPublish a Retired Knowledge Article again after it is already retired.\n\n**How it works:**\n1. The code finds existing articles in the Knowledge base that share the same Article ID but are not the current article and are 'retired'.\n2. It updates the workflow state of these found articles to 'outdated'.\n3. For the current article, it sets the workflow_state to 'pubblished', records the current date and updates the 'published' field with the date.\n4. It also removes the pre-existing 'retired' date from the 'retired' field.\n5. It redirects the user to the updated current record.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Publish a Retired Knowledge Article again/publishretiredkb.js",
    "content": "var kbArticle = new GlideRecord('kb_knowledge');\nkbArticle.setWorkflow(false);\nkbArticle.addQuery('article_id', current.article_id);\nkbArticle.addQuery('sys_id', \"!=\", current.sys_id); //articles that are not the current one\nkbArticle.addQuery('workflow_state', 'retired');\nkbArticle.query();\nwhile (kbArticle.next()) {\n    kbArticle.workflow_state = 'outdated';  //setting the articles as outdated\n    kbArticle.update();\n}\ncurrent.workflow_state = 'published';  //publishing retired kb article again\ncurrent.published = new GlideDate();\ncurrent.retired = \"\";  //clearing retired field value\ncurrent.update();\naction.setRedirectURL(current);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Select Random User From Group/README.md",
    "content": "# Random User Assignment\n\nThis function allows you to select a random user from a specified group. Use case could be on all task-like records that need to be worked on by someone and you want to select the person randomly.\n\n\n\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Select Random User From Group/script.js",
    "content": "/**\n * Get a random user from a specific user group\n *\n * @param {string} group - The sys_id of the group to select a user from\n * @returns {string|null} - The sys_id of a randomly selected user from the specified group or null if no user is found\n */\n\nfunction getRandomUserFromGroup(group) {\n\n    var users = [];\n\n    var grMember = new GlideRecord(\"sys_user_grmember\");\n    grMember.addNotNullQuery(\"user\");\n    grMember.addQuery(\"group\", group);\n    grMember.query();\n\n    while (grMember.next()) {\n        users.push(grMember.getValue(\"user\"));\n    }\n\n    if (users.length > 0) {\n        // Select a random user from the \"users\" array\n        return users[Math.floor(Math.random() * users.length)];\n    } else {\n        // Return null if no user is found in the specified group\n        return null;\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Send notification if the incident remains unassigned/README.md",
    "content": "This is a UI Action script that adds a button to the Incident form. When clicked, it will check if the incident has been unassigned for more than 5 days. \nIf this condition is met, the button will trigger a notification to the manager of the incident's assignment group, informing them that the incident is still unassigned.\n\nBelow are the conditions when UI action will be created:\nTable: Incident\nShow Insert: False\nShow Update: True\nForm Button: Checked (to add the button on the form)\nCondition: current.assigned_to.nil() && current.assignment_group\n\nThe code contains below vaidations:\n- The script checks if the incident has been unassigned for more than 5 days by comparing the current date with the sys_created_on date using gs.daysAgo().\n- It verifies that the incident has an assignment group. If it does not, it displays an error message.\n- Queries the sys_user_group table to get the assignment group’s manager. If a manager is found, it sets up a notification to send an email directly to the manager.\n- Provides feedback to the user if the notification was sent or if there were issues (like missing assignment group or manager).\n- Redirects the user back to the current incident form after the UI Action runs.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Send notification if the incident remains unassigned/script.js",
    "content": "// Check if the incident has been unassigned for more than 5 days\nvar unassignedDuration = gs.daysAgo(current.sys_created_on);\nif (unassignedDuration < 5) {\n    gs.addErrorMessage(\"The incident has been unassigned for less than 5 days.\");\n    action.setRedirectURL(current);\n    return;\n}\n\n// Check if the incident has an assignment group\nif (current.assignment_group.nil()) {\n    gs.addErrorMessage(\"No assignment group is set for this incident.\");\n    action.setRedirectURL(current);\n    return;\n}\n\n// Get the assignment group's manager\nvar assignmentGroup = new GlideRecord('sys_user_group');\nif (assignmentGroup.get(current.assignment_group)) {\n    var manager = assignmentGroup.getValue('manager');\n\n    if (manager) {\n        // Create a notification\n        var notification = new GlideEmailOutbound();\n        notification.setFrom('no-reply@xyz.com');\n        notification.setSubject(\"Alert! Incident \" + current.number + \" is still unassigned\");\n        notification.setBody(\"The incident \" + current.number + \" has been unassigned for more than 5 days. Please assign it promptly.\");\n        notification.setTo(manager);\n\n        // Send the email\n        notification.send();\n\n        gs.addInfoMessage(\"Notification sent to the assignment group's manager.\");\n    } else {\n        gs.addErrorMessage(\"The assignment group has no manager defined.\");\n    }\n} else {\n    gs.addErrorMessage(\"Could not find the assignment group.\");\n}\n\naction.setRedirectURL(current);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Send notification to the assigned user/README.md",
    "content": "Please note - Users should endeavor to use Flow, Notifications, etc., but UI Action option is also available.\n-------------------------------------------------------------------------------------------------------------------------------------------\nA UI Action in ServiceNow is a script that defines an action or button within the platform's user interface. It enables users to perform specific operations on forms and lists, such as creating, updating, or deleting records, or executing custom scripts. UI Actions enhance the user experience by providing functional buttons, links, or context menus.\n\nIn this case, the UI Action contains a server-side script that creates a record in the sys_email table, including the email body, subject, and recipient details.\nWhen the button on the incident form is clicked, an email notification will be sent to the user to whom the incident is assigned.\n\n-> If the incident is not assigned to any user, a message will be shown, and no email will be sent.\n-> If the assigned user's email address is missing, the email will not be sent, and an appropriate message will be displayed.\n-> However, if the incident is assigned to a user with a valid email address, the UI Action will successfully send the email to the assigned user.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Send notification to the assigned user/script.js",
    "content": "//Servver Side Script to send notification to the assigned to user\nvar assignedToEmail = current.getValue('assigned_to'); // Fetches the sys_id of the assigned_to field\nif (assignedToEmail) {\n    var userGR = new GlideRecord('sys_user'); // Access the sys_user table\n    var txt_email = \"\";\n    if (userGR.get(assignedToEmail)) {\n        txt_email = userGR.getValue('email'); // Retrieves the email field from the sys_user record\n    }\n    if (txt_email) {\n        var gr_sys_email = new GlideRecord('sys_email');\n        gr_sys_email.initialize();\n        gr_sys_email.setValue('type', 'send-ready');\n        gr_sys_email.setValue('subject', 'UI Action Notification from Incident ' + current.number);\n        gr_sys_email.setValue('recipients', txt_email);\n        gr_sys_email.setValue('body', 'As the incident ' + current.number + ' is assigned to you, this UI Action Notification has been sent. Please review the incident.');\n        gr_sys_email.insert();\n    }\n\telse\n\t{\n\t\t\tgs.addInfoMessage(\"The email address for the user \" + userGR.getValue('name') + \" is missing. As a result, the email could not be sent.\");\n\t}\n}\nelse\n{\n\tgs.addInfoMessage(\"The incident \" + current.number + \" has not been assigned to any user. Therefore, the email could not be sent.\");\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Set Incident Priority Critical/README.md",
    "content": "Set Incident Priority Critical\n\nThis UI Action sets current incident record as priority as Critical.\n\nWhen clicked\nBelow Conformation message will be displayed to the user as below\n\"Are you sure you want to set priority as Critical?\"\n\nIf user selects cancel nothing will happen.\n\nIf user selects confirm then\na) It sets the Priority of the current Record as 1-Critical by setting Urgency and Impact as 1-High.\nb) It also sets 'assigned_to' field as 'logedin user'.\nc) It also upends the description as \"Priority is set to Critical by 'logedin User'\".\n\n** Please note that it changes field values only on the form that is client side. \nUnless you submit or update the record, field values will not be updated in the database table records.\n\nYou can use this as reference to set other field values as well.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Set Incident Priority Critical/script.js",
    "content": "// Call this function from OnClick field of UI Action form\nfunction ClientSideScript() {\n    var answer = confirm(\"Are you sure you want to set priority as Critical?\");\n    if (answer == true) {\n        g_form.setValue('assigned_to', g_user.userID);\n        g_form.setValue('impact', 1);\n        g_form.setValue('urgency', 1);\n        g_form.setValue('description', g_form.getValue('description') + \"\\nPriority is set to Critical by \" + g_user.getFullName());\n    }\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Show Today Emails Logs/README.md",
    "content": "# Show Today Emails Logs\n\nThe UI Action simplifies the debugging of ServiceNow notifications by redirecting you from the notification definition to the email log list view with the predefined filter.\n\nSetting:\n- Name: Show Today Emails Logs\n- Table: sysevent_email_action\n- Show insert: false\n- Show update: true\n- Client: true\n- Form link: true\n- Onclick: openEmailLogList()\n- Condition: new GlideRecord('sys_email_log').canRead()"
  },
  {
    "path": "Client-Side Components/UI Actions/Show Today Emails Logs/script.js",
    "content": "function openEmailLogList() {\n\tvar query = '';\n\tquery += 'sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()';\n\tquery += '^notification=' + g_form.getUniqueValue();\n\tquery += '^ORDERBYDESCsys_created_on';\n\t\n\tvar url = new GlideURL('sys_email_log_list.do');\n\turl.addParam('sysparm_query' , query);\n\tg_navigation.open(url.getURL(), '_blank');\n}"
  },
  {
    "path": "Client-Side Components/UI Actions/Smart Assign to available member/README.md",
    "content": "**Use-case:**\nThe primary goal of this UI Action is load-balancing.\nIt assigns tasks based on the fewest currently Active tasks assigned to a member in a group.\n\n**Example Scenario**:\nAn assignment group has 10 members. Instead of assigning a new task to the whole group or any random member, the user/agent clicks on\n\"Smart Assign\" to find the member with the fewest currently Active tasks in the same group and assign the task to them.\n\n**UI Action Name:**\nSmart Assign\n\n**Condition**: !current.assignment_group.nil() && current.assigned_to.nil()\n\n**How it works:**\n1. The code queries the Group members table to find every single user associated with the currently selected assignment group.\n   If someone removes a previous assignement group and then clicks on Smart Assign button, they are shown an error message to choose an Assignment group.\n2. There is a loop on the task table. This loop uses GlideAggregate to count how many active records are assigned to a specific user.\n3. It tracks the user that has the lowest count of tasks assigned to them and assigns the current task to them.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Smart Assign to available member/smartAssigntoAvailablemember.js",
    "content": "var assignedToId = '';\nvar minOpenTasks = 77777;\nvar targetGroup = current.assignment_group;\n\nif (!targetGroup) {\n    gs.addErrorMessage('Please select an Assignment Group first.');\n    action.setRedirectURL(current);\n}\n\n//Finding all active members in the target group\nvar member = new GlideRecord('sys_user_grmember');\nmember.addQuery('group', targetGroup);\nmember.query();\n\nwhile (member.next()) {\n    var userId = member.user.toString();\n\n    //CountIng the number of active tasks currently assigned to the member\n    var taskCountGR = new GlideAggregate('task');\n    taskCountGR.addQuery('assigned_to', userId);\n    taskCountGR.addQuery('active', true);\n    taskCountGR.addAggregate('COUNT');\n    taskCountGR.query();\n\n    var openTasks = 0;\n    if (taskCountGR.next()) {\n        openTasks = taskCountGR.getAggregate('COUNT');\n    }\n\n    //Checking if this member has fewer tasks than the current minimum\n    if (openTasks < minOpenTasks) {\n        minOpenTasks = openTasks;\n        assignedToId = userId;\n    }\n}\n\n//Assigning the current record to the chosen user\nif (assignedToId) {\n    current.assigned_to = assignedToId;\n    current.work_notes = 'Assigned via Smart Assign to the user with the fewest active tasks (' + minOpenTasks + ' open tasks).';\n    current.update();\n    gs.addInfoMessage('Incident assigned to ' + current.assigned_to.getDisplayValue() + '.');\n} else {\n    gs.addErrorMessage('Could not find an active member in the group to assign the task.');\n}\n\naction.setRedirectURL(current);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Test and Debug Scheduled Scripts/README.md",
    "content": "<h4>Test and Debug Scheduled Scripts using the Script Debugger</h4>\n\nThis UI Action will run the script in the current session, so that it can be run and debugged in the script debugger.\n\n<h4>Steps to Add:</h4>\n1. Create a new UI Action in Global scope.<br>\n2. Name = Test and Debug (Modify this as per your preference).<br>\n3. Table = \"Scheduled Script Execution [sysauto_script]\"<br>\n4. Form Button = selected<br>\n5. Add the given script.<br>\n6. New UI Action will be available on Scheduled Scripts in Studio.<br>\n7. Use break points in script to debug.\n\n<h3><span style='color: red;'>WARNING</span></h3>\nThis will run the script. So use it wisely.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Test and Debug Scheduled Scripts/script.js",
    "content": "try{\n\t\t\n\tvar evaluator = new GlideScopedEvaluator();  \n\tevaluator.evaluateScript(current, 'script');\n\taction.setRedirectURL(current);\n\t\n}\t\ncatch(e){\n\tgs.addInfoMessage(e);\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Try Catalog item in Portal view/README.md",
    "content": " UI action code  which will help developers to test the same catalog item in serviceportal view  by Clicking on the action .\n OOB we have try it which will preview the catalog item in ITIl view but some time we may miss few configurations which needs to bes tested in service portal. \n While doping testng its take to test the each item in serviceportal uisng this UI action we can test in portal itself.\n Its always recommended way to test and experience end user view way while performing testing.\n \n Note : Please update the \"portal \" value as per your request.\n\n Open any catalog item -->  \n                          Click on Try It Sp Ui action  -->\n                                                        Catalog item redirect to sp view in seperate tab.\n\nCreate UI action :\n==================\nOpen System defination --> UI action --> Create new\n\nName : Try it in SP\n\nTable : Catalog Item [sc_cat_item]\n\nOrder : 100\n\nAction name : try_it_in_sp\n\nClient : true\n\nForm button : true\n\nOnclick : tryitinsp()\n\nCondition : current.active == true && current.canWrite()&&new CatalogItemTypeProcessor().canTryIt(current.getRecordClassName()) && !(current.getRecordClassName() == \"sc_cat_item_content\" && current.content_type == \"external\") \n\nScript : copy and paste the code from TryItInSp.js file\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Try Catalog item in Portal view/TryItInSP.js",
    "content": "\ntryitinsp();\nfunction tryitinsp() {\n    var portal = \"sp\";  //the portal you want to use for testing\t\n\tvar page = g_form.getTableName();\n    var gUrl = new GlideURL(portal); \n    gUrl.addParam(\"id\", page); \n\tgUrl.addParam(\"sys_id\", g_form.getUniqueValue()); //add the sys_id of this Catalog Item to render\n\n    //and then display the Catalog Item in a new tab/window\n    g_navigation.open (gUrl.getURL(), \"_blank\");\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/UI Action to Mark Incident as Escalated/README.md",
    "content": "Description: This UI Action allows users to mark an incident as escalated, indicating that it requires immediate attention. \nBy changing the incident's state to \"Escalated,\" the action not only helps prioritize the incident but also notifies the relevant support group to take prompt action. \nThis functionality is essential in incident management, ensuring that critical issues are addressed without delay.\n\nKey Features:\nImmediate Priority Update: Changes the incident's state to \"Escalated\" to reflect its urgent status.\nNotification: Sends an alert to the relevant support team, ensuring they are aware of the escalation.\nUser-Friendly: Provides an easy way for users to escalate incidents directly from the incident form.\n"
  },
  {
    "path": "Client-Side Components/UI Actions/UI Action to Mark Incident as Escalated/Script.js",
    "content": "// UI Action: Mark as Escalated\n// Table: incident\n// Condition: current.state != 7 // Not Closed\n// Client: false\n\n(function executeAction(current) {\n    current.state = 3; // Set to Escalated (assuming 3 represents Escalated state)\n    current.update(); // Update the record\n\n    // Optionally notify the support group\n    var notification = new GlideRecord('sysevent_email');\n    notification.initialize();\n    notification.recipients = 'support_group_email@example.com';\n    notification.subject = 'Incident ' + current.number + ' has been escalated';\n    notification.body = 'The incident has been marked as escalated. Please address it promptly.';\n    notification.insert();\n\n    gs.addInfoMessage('Incident ' + current.number + ' has been marked as escalated.');\n})(current);\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Variable Ownership/Readme.md",
    "content": "The Variable Ownerships UI Action is a lightweight admin utility designed to help manage request item (RITM) variables more effectively.\n\nWith just one click, it provides direct access to the variable values table, allowing administrators to quickly review, update, or remove sensitive data entered by mistake. This eliminates the need to navigate multiple related tables manually, saving time and reducing risk.\n\nKey Benefits:\n\n• Direct access to RITM variable values\n\n• Faster cleanup of sensitive information\n\n• Improves data hygiene and admin efficiency\n\n• Simple to implement and lightweight\n"
  },
  {
    "path": "Client-Side Components/UI Actions/Variable Ownership/script.js",
    "content": "/*\nThis script should be placed in the UI action on the table sc_req_item form view.\nThis UI action should be marked as client.\nUse viewMtom() function in the Onclick field.\n*/\n\nfunction viewMtom() {\n\n    var url = 'sc_item_option_mtom_list.do?sysparm_query=request_item=' + g_form.getUniqueValue();\n    g_navigation.openPopup(url);\n\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/View in Portal Page/README.md",
    "content": "code-snippet used in UI Action SCript to view the current record in Service Portal Page using a redirect\n\nExample:\n//to view a KB article in the Service Portal:\n\nfunction goToPortal(){\n\tvar url = 'sp?id=kb_article_view&sys_kb_id=' + g_form.getUniqueValue();\n\tg_navigation.openPopup(url);\n\t//g_navigation.open(url);\n\treturn false;\n}\n"
  },
  {
    "path": "Client-Side Components/UI Actions/View in Portal Page/View in Portal Page.js",
    "content": "function goToPortal(){\n\tvar url = '<pageToRedirect>?<parameter>=<value>';\n\tg_navigation.openPopup(url);\n\treturn false;\n}\n"
  },
  {
    "path": "Client-Side Components/UI Macros/Copy To Clipboard/README.md",
    "content": "# ServiceNow UI Macro: Copy Field to Clipboard\n\nThis ServiceNow UI Macro allows you to easily copy field values to your clipboard by clicking on the button next to the field. It supports both standard fields and reference fields.\n\n## Usage\n\n1. Install the UI Macro:\n   - Navigate to **System UI** > **UI Macros**.\n   - Create a new UI Macro.\n   - Paste the XML code of the UI Macro into the script field.\n   - Save the UI Macro.\n\n2. Add the UI Macro to a Form:\n   - Navigate to the form where you want to add the copy functionality.\n   - Right-Click on the field you want this UI macro to be attached to.\n   - Add/Modify the following attribute 'ref_contributions=<NAME_OF_UI_MACRO>'\n   - (OPTIONAL) Use semicolon to separate UI macros in field attributes like this 'ref_contributions=<UI_MACRO_1>;<UI_MACRO_2>'.\n\n3. Copy Field Value:\n   - When viewing a record with the UI Macro added, you'll see a \"Copy to Clipboard\" icon next to the field.\n   - Click the \"Copy to Clipboard\" icon to copy the field value to Clipboard.\n\n## Supported Fields\n\n- **Standard Fields**: You can use this UI Macro to copy values from standard fields on the form.\n- **Reference Fields**: This UI Macro also supports copying sys_ids of reference fields.\n"
  },
  {
    "path": "Client-Side Components/UI Macros/Copy To Clipboard/copy_field_to_clipboard.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\t<!-- The block below is dedicated for html element id generation with always unique GUID to refer later if needed -->\n\t<g:evaluate var=\"jvar_guid\" expression=\"gs.generateGUID(this);\" />\n\t<j:set var=\"jvar_n\" value=\"copy_to_clpb_${jvar_guid}:${ref}\" />\n\t<!-- The block below looks for the field by id we assigned on the previous steps and attaches onClick function as well as hint and icon decoration -->\n\t<g:reference_decoration id=\"${jvar_n}\" field=\"${ref}\"\n\t\tonclick=\"copyFieldToClipboard('${ref}');\"\n\t\ttitle=\"${gs.getMessage('Copy to Clipboard')}\" image=\"images/icons/tasks.gifx\" icon=\"icon-copy\" />\n\t<script>\n\t\tfunction copyFieldToClipboard(reference) {\n\t\t\ttry {\n\t\t\t\tvar fieldName = reference.split('.');\n\t\t\t\tvar fieldValue;\n\t\t\t\t\n\t\t\t\tif (fieldName[0] === 'reference') {\n\t\t\t\t\tfieldValue = g_form.getReference(fieldName[1]).name;\n\t\t\t\t} else {\n\t\t\t\t\tfieldValue = g_form.getValue(fieldName[1]);\n\t\t\t\t}\n\n\t\t\t\tcopyToClipboard(fieldValue);\n\t\t\t} catch (e) {\n\t\t\t\tjslog('Error copying to clipboard. Please contact ServiceNow Administration group.');\n\t\t\t\tjslog(e);\n\t\t\t}\n\t\t}\n\t</script>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Macros/FormBackground/form_background.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\"\n\txmlns:j=\"jelly:core\"\n\txmlns:g=\"glide\"\n\txmlns:j2=\"null\"\n\txmlns:g2=\"null\">\n\t<style>\n .touch_scroll{\n background:url(\"/formbg.png\");\n background-size:cover;\n background-position:center;\n }\n label{\n font-weight:600;\n font-size:16px;\n \n }\n .btn-default{\n background:#fff;\n } \n table,form,.section,.tabs2_section {\n background:transparent !important;\n }\n </style>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Macros/FormBackground/readme.md",
    "content": "# ServiceNow Form Background Macro\n\n> A lightweight UI Macro to style ServiceNow forms with a custom background and simple element theming (labels, buttons, sections). \n---\n\n## Features\n\n* Adds a full-cover background image to a form (supports cover, center positioning).\n* Makes table/form/section backgrounds transparent so the background shows through.\n* Easy to customize (image path, label styles, button styles, additional CSS selectors).\n\n## Requirements\n\n* ServiceNow instance with admin access.\n* An image to set as background\n  \n> ⚠️ Note: This macro uses Jelly/CSS that may not work as expected in some Next Experience workspaces or future UI updates. Test in a non-production instance first.\n\n## Installation\n\n1. **Upload the background image**\n\n   * Navigate to **System UI > Images** and upload your background image (e.g., `formbg.png`).\n\n2. **Create the UI Macro**\n\n   * Go to **System UI > UI Macros** and create a new macro (e.g., `ui_form_background`).\n   * Copy the example macro content below into the UI Macro.\n\n3. **Create a UI Formatter**\n\n   * Go to **System UI > Formatters**. Create a new formatter for the target table (for example, `incident` table).\n   * In the *Formatter* field, reference the macro name you created (e.g., `ui_form_background.xml`).\n\n4. **Add the Formatter to the Form Layout**\n\n   * Open the form layout for the target table (Form Layout / Form Designer) and place the formatter region on the form.\n   * Save and open a record to see the background applied.\n\n## Compatibility\n\n* Tested on ServiceNow classic forms (UI16). May require tweaks for Next Experience, Service Portal, or Workspace.\n* If your instance uses strict Content Security Policy (CSP) or image hosting constraints, host the image in a supported location or adapt the implementation.\n\n## Troubleshooting\n\n* If no background appears:\n\n  * Confirm the image is uploaded and the filename matches.\n  * Ensure the formatter is placed on the form layout and published.\n  * Inspect (browser devtools) to confirm CSS selectors are applied.\n \n## Result\n<img width=\"1838\" height=\"922\" alt=\"image\" src=\"https://github.com/user-attachments/assets/14c29e0a-ad88-411e-b7ca-1c82eaeaf324\" />\n\n"
  },
  {
    "path": "Client-Side Components/UI Macros/JSON Formatter and Viewer/README.md",
    "content": "This solution provides a significant User Experience (UX) enhancement for fields that store complex data in JSON format (e.g., integration payloads, configuration properties, or setup data). \n\nInstead of forcing users (developers, administrators) to read or edit raw, unformatted JSON in a plain text area, this macro adds a \"JSON Viewer\" button next to the field\n\nName\tjson_formatter_macro\t\nActive\ttrue\t\nType\tXML\n\nNavigate to **System UI > UI** Macros and create a new record named json_formatter_macro , Use XML Attached File as Script\n"
  },
  {
    "path": "Client-Side Components/UI Macros/JSON Formatter and Viewer/json_formatter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"jelly:html\" xmlns:g2=\"glide.ui\">\n    \n    <g:evaluate var=\"jvar_field_name\" expression=\"RP.getWindowProperties().get('fieldName')\" />\n\n    <button type=\"button\" \n            onclick=\"openJsonViewer('${jvar_field_name}');\" \n            title=\"View and Format JSON\" \n            class=\"btn btn-default btn-sm\">\n        <span class=\"icon-code\"></span> JSON Viewer\n    </button>\n    \n    <script>\n        function openJsonViewer(fieldName) {\n            var jsonValue = g_form.getValue(fieldName);\n            var gm = new GlideModal('json_viewer_ui_page', false); // Replace 'json_viewer_ui_page' with your actual UI Page or Widget ID\n            gm.setTitle('JSON Data Viewer & Formatter for Field: ' + fieldName);\n            gm.setPreference('json_data', jsonValue);\n            gm.setSize(900);\n            gm.render();\n        }\n    </script>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Macros/Purchase Order Approval Summarizer/README.md",
    "content": "With the [Procurement plugin](https://docs.servicenow.com/bundle/rome-it-asset-management/page/product/procurement/concept/c_Procurement.html) activated, Asset managers can track vendor purchase orders for hardware and software assets. This application does not have approvals built-in by default, but that is a common addition.\n\nWhen these approvers are also fulfillers, they will likely be approving from the platform rather than via the portal. The included XML is to be used in a UI Macro to show critical Purchase Order data elements so that the approver can approve directly from the approval record without clicking into the PO.\n\nIn order to use this script, create a new UI Macro named \"approval_summarizer_proc_po\" add the full script from the XML file into the XML field. The system will automatically know to now use this macro for Purchase Orders because of the naming convention.\n\nYou can also create summarizers for other tables in a similar fashion by creating UI Macros start with \"approval_summarizer_\" and ending with the database table name.\n\nBelow is an example of the Purchase Order details display on an approval recor. It is not the most beautiful, but follows the formatting from similar approval summarizers.\n\n![Purchase Order Approval](approval_summarizer_proc_po.png)\n"
  },
  {
    "path": "Client-Side Components/UI Macros/Purchase Order Approval Summarizer/approval_summarizer_proc_po.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"true\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n    <tr>\n        <td class=\"label_left\" width=\"350px\" style=\"font-weight:bold;\">\n            ${gs.getMessage('Summary of Purchase Order')}:\n            <g:label_spacing />\n        </td>\n    </tr>\n    <g:evaluate var=\"jvar_total\"\n        expression=\"        \n        var proc_po = ${ref}.document_id;\n\n        var proc_po_labels = new GlideRecord('proc_po_item');\n        proc_po_labels.initialize();\n    \n        var proc_po_item = new GlideRecord('proc_po_item');\n        proc_po_item.addQuery('purchase_order', proc_po.sys_id);\n        proc_po_item.query();\n\n        var jvar_total = proc_po.total_cost;\n        jvar_total;\n        \" />\n    <tr>\n        <td width=\"350px\">\n            <table width=\"350px\">\n            \t<tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.number.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.number.getDisplayValue()}\n                    </td>\n                </tr>\n                <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.vendor.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.vendor.getDisplayValue()}\n                    </td>\n                </tr>\n\t\t\t\t <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.ship_to.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.ship_to.getDisplayValue()}\n                    </td>\n                </tr>\n                <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.due_by.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.due_by.getDisplayValue()}\n                    </td>\n                </tr>\n                <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.requested_for.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.requested_for.getDisplayValue()}\n                    </td>\n                </tr>\n                <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.department.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.department.getDisplayValue()}\n                    </td>\n                </tr>\n                <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.short_description.sys_meta.label}:\n                    </td>\n                    <td>\n                        ${proc_po.short_description.getDisplayValue()}\n                    </td>\n                </tr>\n                <tr>\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.ship_rate.sys_meta.label}:\n                    </td>\n                    <td style=\"text-align: right; padding-right: 5px;\">\n                        <g:currency_format value=\"${proc_po.ship_rate}\" />\n                    </td>\n                </tr>\n                <tr style=\"background-color:lightgreen;\">\n                    <td class=\"label_left\" width=\"150px\" style=\"font-weight: bold;\">\n                        <g:label_spacing />\n                        ${proc_po.total_cost.sys_meta.label}:\n                    </td>\n                    <td style=\"font-weight: bold; text-align: right; padding-right: 5px;\">\n                        <g:currency_format value=\"${proc_po.total_cost}\" />\n                    </td>\n                </tr>\n            </table>\n        </td>\n    </tr>\n    <j:set var=\"jvar_line_num\" value=\"0\" />\n    <tr>\n        <td width=\"100%\">\n            <table width=\"100%\">\n                <tr class=\"header\">\n                    <td width=\"150px\">\n                        ${proc_po_labels.number.sys_meta.label}\n                    </td>\n                    <!--<td>\n                        ${proc_po_labels.vendor.sys_meta.label}\n  </td> -->\n                    <td width=\"150px\">\n                        ${proc_po_labels.model.cmdb_model_category.sys_meta.label}\n                    </td>\n                    <td>\n                        ${proc_po_labels.model.sys_meta.label}\n                    </td>\n                    <td align=\"right\" width=\"150px\">\n                        ${proc_po_labels.cost.sys_meta.label}\n                    </td>\n                    <td align=\"right\" width=\"150px\">\n                        ${proc_po_labels.ordered_quantity.sys_meta.label}\n                    </td>\n                    <td align=\"right\" width=\"150px\">\n                        ${proc_po_labels.total_cost.sys_meta.label}\n                    </td>\n                </tr>\n                <j:while test=\"${proc_po_item.next()}\">\n                    <j:set var=\"jvar_line_color\" value=\"odd\" />\n                    <j:set var=\"jvar_line_num\" value=\"${jvar_line_num + 1}\" />\n                    <j:if test=\"${jvar_line_num % 2 == 0}\">\n                        <j:set var=\"jvar_line_color\" value=\"even\" />\n                    </j:if>\n\t\t\t\t\t\n                    <tr class=\"${jvar_line_color}\">\n                        <td valign=\"top\" width=\"150px\">\n                            <g2:evaluate var=\"jvar_pop_target\" expression=\"$[ref].getRecordClassName()\" />\n                            <a class=\"linked\" target=\"gsft_main\" href=\"proc_po_item.do?sys_id=${proc_po_item.sys_id}\"\n                                onmouseover=\"popListDiv(event, 'proc_po_item', '${proc_po_item.sys_id}','${JS:sysparm_view}')\" onmouseout=\"lockPopup(event)\">\n                                <img src=\"images/icons/hover_icon.gifx\" class=\"clsshort\" alt=\"${gs.getMessage('uppercase_view')}\" />\n                            </a>${SP}${SP}${SP}\n                            <a class=\"linked\" target=\"gsft_main\" href=\"proc_po_item.do?sys_id=${proc_po_item.sys_id}\">\n                                ${proc_po_item.number}\n                            </a>\n                        </td>\n\t\t\t\t\t\t<!--<td valign=\"top\"> ${proc_po_item.vendor.getDisplayValue()}</td>-->\n                        <td valign=\"top\" width=\"150px\"> ${proc_po_item.model.cmdb_model_category.getDisplayValue()}</td>\n                        <td valign=\"top\"> ${proc_po_item.model.getDisplayValue()}</td>\n                        <td valign=\"top\" align=\"right\" width=\"150px\">\n                            <g:currency_format value=\"${proc_po_item.cost.getDisplayValue()}\" />\n                        </td>\n                        <td valign=\"top\" align=\"right\" width=\"150px\"> ${proc_po_item.ordered_quantity}</td>\n                        <td valign=\"top\" align=\"right\" width=\"150px\">\n                            <g:currency_format double=\"${proc_po_item.total_cost}\" />\n                        </td>\n                    </tr>\n                </j:while>\n            </table>\n        </td>\n    </tr>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Macros/Show Open Incident of Caller/Readme.md",
    "content": "Show Open Incident of caller\n\nScript Type: UI Macro\n\nGoal: In Form view caller can see what are the open incident of that particular caller.\n\nWalk through of code: So for this use case a new macro will be added to the caller field,  when it is triggered it will open a new popup window where it will show the list of particular caller which are all open incident.So for this a new UImacro have been used in that a new list icon have been rendered from the db_image table and inside that a showopentckts() function this will get the current caller and then add the query to filter out the list of open incident and then open a popup to show the list of that particular caller which are all open(other than Closed and Cancelled).\n\nNote: To inherite the UI Macro in that particular field (Caller) we need to add the attribute in the Dictionary Entry = ref_contributions=caller_inc_lists [ref_contributions=\"name of the macro\"]\n\nUI Macro\n<img width=\"853\" height=\"292\" alt=\"UIMacro\" src=\"https://github.com/user-attachments/assets/f5b353a2-ffb6-4e44-a740-9905b33cb484\" />\n\nDictonary Entry in Attribute section\n<img width=\"848\" height=\"386\" alt=\"UIMacroDictionary\" src=\"https://github.com/user-attachments/assets/b21d2077-3df5-4408-9bb3-c0672fd1398b\" />\n\nUI Macro in Incident Form near the Caller Field\n<img width=\"860\" height=\"310\" alt=\"From UIMacro\" src=\"https://github.com/user-attachments/assets/c3867abf-f9cd-4947-a7e9-41c83189681d\" />\n\nResult:\n<img width=\"865\" height=\"409\" alt=\"UIMacro Result\" src=\"https://github.com/user-attachments/assets/5412b29b-e96b-4455-a3e0-5552c9680600\" />\n"
  },
  {
    "path": "Client-Side Components/UI Macros/Show Open Incident of Caller/macro.js",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\r\n\r\n<img src='sn_tile_icon/now-checklist.svg' stype='width=5%,height:5%' onclick=\"showopentckts()\" title=\"Show Open Incident of me\"></img>\r\n\r\n<script language=\"javascript\">\r\nfunction showopentckts(){\r\nvar name=g_form.getValue(\"caller_id\");\r\nvar tableName='incident';\r\nvar url=tableName+'_list.do?sysparm_query=caller_id='+name+'^stateNOT IN7,8';\r\nwindow.open(url,'OpenIncident',\"popup\");\r\n}\r\n\r\n</script>\r\n\r\n</j:jelly>"
  },
  {
    "path": "Client-Side Components/UI Macros/Variable Copy Context Options/README.md",
    "content": "# Add \"Copy Variable Name\" to Context Menu\n\nAdds code to the element_context UI Macro, allowing for admins to be able to right click a variable name and choose \"Copy Variable Name\" to quickly get the column name to their clipboard\n\n## where to add\n\n1. In the sys_ui_macro (Macros) table, open the record `element_context`\n2. Replace the existing `<j:if test=\"${jvar_personalize_dictionary == true }\" >` block with the block in element context.xml\n\n## full XML for element_context example\n\nThis code is from an out-of-box instance with the necessary code, you can replace the existing element_context xml with this:\n\n```xml\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n  <script>\n    var table = \"${sysparm_table}\";\n    var id = \"${sysparm_id}\";\n    var type = \"${sysparm_type}\";\n    var choice = \"${sysparm_choice}\";\n    var count = 0;\n\n    var gcm = new GwtContextMenu('context_personalize_menu');\n    gcm.setTableName('${ref}');\n    gcm.clear();\n\n    <g:evaluate var=\"jvar_personalize_dictionary\" expression=\"gs.hasRole('personalize_dictionary')\" />\n    <g:evaluate var=\"jvar_personalize_styles\" expression=\"gs.hasRole('personalize_styles')\" />\n    <g:evaluate var=\"jvar_personalize_security\" expression=\"gs.hasRole('security_admin')\" />\n    <g:evaluate var=\"jvar_personalize_responses\" expression=\"gs.hasRole('personalize_responses')\" />\n    <g:evaluate var=\"jvar_personalize_choices\" expression=\"gs.hasRole('personalize_choices')\" />\n    <g:evaluate var=\"jvar_field_parts\" object=\"true\" jelly=\"true\">\n        var fieldParts = jelly.sysparm_id.substring(jelly.sysparm_table.length + 1).split('.');\n    </g:evaluate>\n    <g:evaluate var=\"jvar_table_dot_do\" jelly=\"true\">\n        var tableDo = 'sys_dictionary.do';\n        // see if our parent is named 'var__m_...', \n        // in which case we are a variable and need to use the correct .do\n        if (fieldParts.length > 1) {\n            var parentName = fieldParts[fieldParts.length - 2];\n            if (parentName.indexOf('var__m_') == 0) {\n                var gr = new GlideRecord('sys_dictionary');\n                gr.setDisplayFields(['sys_class_name']);\n                gr.addQuery('name', parentName);\n                gr.addQuery('element', fieldParts[fieldParts.length - 1]);\n                gr.query();\n                if (gr.next())\n                    tableDo = gr.sys_class_name + \".do\";\n            }\n        }\n        tableDo;\n    </g:evaluate>\n    <g:evaluate var=\"jvar_field_name\">\n        fieldParts[fieldParts.length -1];\n    </g:evaluate>\n    <j:if test=\"${jvar_personalize_dictionary == true }\">\n        gcm.addHref(\"${JS:gs.getMessage('Configure Label')}\", \n            \"personalizeField('\" + id + \"', 'sys_documentation.do');\");\n        gcm.addHref(\"${JS:gs.getMessage('Configure Dictionary')}\", \n            \"personalizeField('\" + id + \"', '\" + '${jvar_table_dot_do}' + \"');\");\n        count = 1;\n    </j:if>\n    <j:if test=\"${jvar_personalize_styles == true }\">\n        gcm.addHref(\"${JS:gs.getMessage('Configure Styles')}\",\n            \"showList('sys_ui_style', 'name.element', '\" + id + \"');\");\n        count = 1;\n    </j:if>\n    <j:if test=\"${jvar_personalize_security == true}\">\n       <j:if test=\"${sysparm_contextual_security == true}\">\n          gcm.addHref(\"${JS:gs.getMessage('Configure Security')}\", \n              \"personalizeSecurity('\" + table + \"', '\" + id + \"');\");\n          gcm.addHref(\"${JS:gs.getMessage('Show Security Rules')}\",\n              \"listSecurity('\" + table + \"', '\" + id + \"');\");\n          count = 1;\n       </j:if>\n    </j:if>\n    if ((choice != 0 ${AND} type != 'sys_class_name') || type == 'choice') {\n        var disabled = needSubDisabled(table, id);\n        if (count != 0)\n            gcm.addLine();\n\n        if (type.substring(0, 7) == 'journal') {\n            <j:if test=\"${jvar_personalize_responses == true }\">\n                var c = gcm.addHref(\"${JS:gs.getMessage('Configure Responses')}\",\n                    \"personalizeResponses('\" + id + \"');\");\n\n                if (disabled == true)\n                    gcm._dullItem(c);\n\n                count = 1;\n            </j:if>\n        } else {\n            <g:evaluate var=\"jvar_personalize_choices_rights\">\n                var url = 'record/' + '${sysparm_table}' + '.' + '${jvar_field_name}' + '/personalize_choices';\n                GlideSecurityManager.get().hasRightsTo(url, gs.getUser());\n            </g:evaluate>\n            <j:if test=\"${jvar_personalize_choices == true || jvar_personalize_choices_rights == true }\">\n                var c = gcm.addHref(\"${JS:gs.getMessage('Configure Choices')}\",\n                    \"personalizeChoices('\" + id + \"');\");\n                if (disabled == true)\n                    gcm._dullItem(c);\n\n                count = 1;\n            </j:if>\n            <j:if test=\"${jvar_personalize_choices == true }\">\n                gcm.addHref(\"${JS:gs.getMessage('Show Choice List')}\",\n                    \"showList('sys_choice', 'name.element', '\" + id + \"');\");\n                count = 1;\n            </j:if>\n        }\n    }\n    <j:if test=\"${jvar_personalize_dictionary == true }\" >\n        // Custom \"Copy Field Name\"\n        gcm.addLine();\n        gcm.addHref(\"${JS:gs.getMessage('Copy Field Name')}\", \"copyToClipboard('${jvar_field_name}');\");\n        gcm.addHref(\"${JS:gs.getMessage('Copy Field Value')}\", \"copyToClipboard(g_form.getValue('${jvar_field_name}'));\");\n\t\tgcm.addHref(\"${JS:gs.getMessage('Copy Field Display Value')}\", \"copyToClipboard(g_form.getDisplayBox('${jvar_field_name}').value);\");\n        gcm.addLine();\n        gcm.addHref(\"${JS:gs.getMessage('Show')}\" + \" - '\" + '${jvar_field_name}' + \"'\",\n            \"showDictionary('\" + table + \"', '\" + id + \"');\");\n        count = 1;\n    </j:if>\n\n    if(showWatchMenu(true) == true) {\n        gcm.addHref(\"${JS:gs.getMessage('Watch')}\" + \" - '\" + '${jvar_field_name}' + \"'\", \"showWatchField('\" + id + \"');\");\n        count = 1;\n    }\n\n    if(showWatchMenu(false) == true) {\n        gcm.addHref(\"${JS:gs.getMessage('Unwatch')}\" + \" - '\" + '${jvar_field_name}' + \"'\", \"clearWatchField('\" + id + \"');\");\n        count = 1;\n    }\n\n    if (count == 0)\n        gcm = null;\n\n    function needSubDisabled(table, id) {\n        var dep = $('ni.dependent_reverse.' + id);\n        if (dep ${AND} dep.value) {\n            var p = dep.value;\n            var d = table + \".\" + p;\n            p = $(d);\n            if (p ${AND} p.value == '')\n                return true;\n        }\n    \n        return false;\n    }\n\n    function isWatch() {\n        if (type == 'password' || type == 'password2' || type == 'glide_encrypted')\n            return false;\n        var isOpticsPluginActive = ${pm.isActive('com.snc.optics_inspector')}; \n        //Debug icon will be displayed for admin or admin impersonated users\n        var isAdmin = ${new GlideImpersonate().canImpersonate(gs.getUserID())};\n        if (isOpticsPluginActive ${AND} isAdmin) {\n            var el = $('label.' + id);\n            if (el ${AND} !hasClassName(el, 'foreign'))\n                return true;\n        }\n        return false;\n    }\n\n    function showWatchMenu(showWatch) {\n        var flag = false;\n        // We're checking document location against top to see if the full real estate is available\n        // or if we're isolated in a single page (Field Watcher panel not available).\n        if (isWatch() ${AND} top.document.location.href != document.location.href) {\n            var isWatchActive = ('${sysparm_id}' === '${gs.getSession().getWatchField()}') ? true : false;\n            if (showWatch ${AND} !isWatchActive)\n                flag = true;\n            else if (!showWatch ${AND} isWatchActive)\n                flag = true;\n        }\n        return flag;\n    }\n\n  </script>\n</j:jelly>\n```"
  },
  {
    "path": "Client-Side Components/UI Macros/Variable Copy Context Options/element context.xml",
    "content": "<j:if test=\"${jvar_personalize_dictionary == true }\" >\n    // Custom \"Copy Field Name\"\n    gcm.addLine();\n    gcm.addHref(\"${JS:gs.getMessage('Copy Field Name')}\", \"copyToClipboard('${jvar_field_name}');\");\n    gcm.addHref(\"${JS:gs.getMessage('Copy Field Value')}\", \"copyToClipboard(g_form.getValue('${jvar_field_name}'));\");\n    gcm.addHref(\"${JS:gs.getMessage('Copy Field Display Value')}\", \"copyToClipboard(g_form.getDisplayBox('${jvar_field_name}').value);\");\n    gcm.addLine();\n    gcm.addHref(\"${JS:gs.getMessage('Show')}\" + \" - '\" + '${jvar_field_name}' + \"'\",\n        \"showDictionary('\" + table + \"', '\" + id + \"');\");\n    count = 1;\n</j:if>"
  },
  {
    "path": "Client-Side Components/UI Pages/Add Multiple Items to Order Guide/README.md",
    "content": "**Details**\n1. This code will add multiple items in an order guide in single click\n2. Order guide rule base creation will be automatic\n3. This code will also add variable set to selected catalog items automatically.\n\n**How to use**\n1. Go to \"sc_cat_item\" table and select the items to be added in list view.\n2. Look for \"Add to order guide\" in list actions.\n3. The list action will give you an option to select order guide and variable set to be added to catalog items\n\n**Components**\n1. UI Action\n2. UI Page\n3. Script Include\n\n<img width=\"3442\" height=\"1502\" alt=\"image\" src=\"https://github.com/user-attachments/assets/823efffa-1285-488e-a4a6-ae0e6377001f\" />\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Add Multiple Items to Order Guide/Script Include.js",
    "content": "\nvar AddtoOG = Class.create();\nAddtoOG.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    addToOrderGuide: function() {\n        var msgArrNotAdded = []; // array to store not added catalog items.\n        var msgArrAdded = []; // array to store added catalog items.\n        var msg = '';\n        var item = this.getParameter('sysparm_itemList').toString().split(',');\n        var order_guide = this.getParameter('sysparm_og');\n        var var_set = this.getParameter('sysparm_set');\n        for (var i = 0; i < item.length; i++) {\n            var itemName = new GlideRecord('sc_cat_item');\n            itemName.get(item[i]); // get item name\n            var itemBckName = itemName.name.toString().replace(/[^a-zA-Z0-9]/g, \"_\");\n            // check if item is present in order guide\n            var checkStatus = new GlideRecord('sc_cat_item_guide_items');\n            checkStatus.addQuery('guide', order_guide);\n            checkStatus.addQuery('item', item[i]);\n            checkStatus.query();\n            if (checkStatus.next()) {\n                msgArrNotAdded.push(itemName.name);\n            } else {\n                // Add variable set to all catalog items selected\n                var set = new GlideRecord('io_set_item');\n                var orderVar = new GlideRecord('item_option_new');\n                set.initialize();\n                set.variable_set = var_set;\n                set.sc_cat_item = item[i];\n                set.order = '200'; // set order as per your requirement\n                set.insert();\n\n                // Add checkbox variable in order guide for each catalog item\n                orderVar.initialize();\n                orderVar.setValue('type', 7);\n                orderVar.setValue('cat_item', order_guide);\n                orderVar.setValue('question_text', itemName.name);\n                orderVar.setValue('name', itemBckName);\n                orderVar.setValue('order', 1200); // set order as per your requirement\n                orderVar.insert();\n            }\n\n            // Add rule base to order guide\n            var ruleBase = new GlideRecord('sc_cat_item_guide_items');\n            ruleBase.initialize();\n            ruleBase.setValue('item', item[i]);\n            ruleBase.setValue('guide', order_guide);\n            ruleBase.setValue('condition', 'IO:' + orderVar.sys_id + '=true^EQ');\n            ruleBase.insert();\n            msgArrAdded.push(itemName.name);\n        }\n        if (msgArrNotAdded.length > 0) {\n            msg = \"Not added item are \" + msgArrNotAdded + ' Added Items are ' + msgArrAdded; // array of items which are not added\n        } else\n            msg = 'Added Items are ' + msgArrAdded; // array of added items\n        return msg;\n    },\n    type: 'AddtoOG'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Add Multiple Items to Order Guide/UI Action.js",
    "content": "\n/*\nonClick function name : addToOrderGuide\nThe UI action will prompt UI page to select order guide and variable set. \n*/\nfunction addToOrderGuide() {\n    var items = g_list.getChecked();\n    var dialog = new GlideDialogWindow(\"add_to_og\"); // UI page name\n    dialog.setTitle(\"Select Order Guide and Variable Set\"); // Prompt title.\n    dialog.setPreference(\"items\", items);\n    dialog.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Add Multiple Items to Order Guide/UI Page.js",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n    <g:evaluate var=\"jvar_items\" expression=\"RP.getWindowProperties().get('items')\" />\n    <hr>\n    </hr>\n    <!--Select Order Guide Name-->\n    <label>Select Order Guide</label>\n    <g:ui_reference name=\"order_guide\" id=\"order_guide\" table=\"sc_cat_item_guide\" completer=\"AJAXTableCompleter\" style=\"width:180px\" />\n    <hr>\n    </hr>\n    <hr>\n    </hr>\n    <!--Select varaible set name -->\n    <label>Select Variable Set</label>\n    <g:ui_reference name=\"var_set\" id=\"var_set\" table=\"item_option_new_set\" completer=\"AJAXTableCompleter\" style=\"width:180px\" />\n    <hr>\n    </hr>\n    <button style=\"margin-top:4px;background-color:crimson;color:white\" onclick=\"addItems('${jvar_items}')\"> Add to Order Guide</button>\n</j:jelly>\n\n//Client Script of UI page\n\nfunction addItems(catItems) {\n    var og = document.getElementById(\"order_guide\").value;\n    var varSet = document.getElementById(\"var_set\").value;\n\n    var orderG = new GlideAjax('AddtoOG');\n    orderG.addParam('sysparm_name', 'addToOrderGuide');\n    orderG.addParam('sysparm_itemList', catItems);\n    orderG.addParam('sysparm_og', og);\n    orderG.addParam('sysparm_set', varSet);\n    orderG.getXML(addOrderGuide);\n}\n\nfunction addOrderGuide(response) {\n    var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n    alert(answer);\n    GlideDialogWindow.get().destroy();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/BulkUpdate Worknotes/Readme.md",
    "content": "\r\nBulk Update Worknotes\r\n\r\nScript Type: UI Action, Table: incident, List banner button: True, Client: True, Show update: True, OnClick: functionName()\r\n\r\nScript Type: UI Page Category: General\r\n\r\nGoal: To update the worknotes for multiple tickets in a single view \r\n\r\nWalk through of code: So in the incident List view we do have a list banner button called \"Bulk Updates\" so that was the UI Action configured with the script which has used the GlideModel API to call the UI page which is responsible to get the multiple tickets and worknotes value and then update to the respective ticket and store in the worknotes in journal entry. For this the HTML part in the UI page is configured with two fields, one for the multiple list of tickets, and then the worknotes field, and one submit button to save that into the table.\r\nAnd the Processing Script is used to get each ticket number and then check the valid tickets and query the respective table, and then update the worknotes of each respective ticket if it is valid. Otherwise, it won't update.\r\n"
  },
  {
    "path": "Client-Side Components/UI Pages/BulkUpdate Worknotes/UI Action.js",
    "content": "// Table: incident, List banner button: True, Client: True, Show update: True, OnClick: bulkupdate()\r\n\r\nfunction bulkupdate() {\r\n    var modalT = new GlideModal(\"BulkUpdate\", false, 1200);\r\n    modalT.setTitle(\"Bulk Update Worknotes\");\r\n    modalT.render();\r\n}"
  },
  {
    "path": "Client-Side Components/UI Pages/BulkUpdate Worknotes/UI Page_ClientScript.js",
    "content": "function bulkupdate() {\r\n    document.getElementById(\"spinner-btn\").style.display = \"block\";\r\n    document.getElementById(\"submit-btn\").style.display = \"none\";\r\n\r\n}"
  },
  {
    "path": "Client-Side Components/UI Pages/BulkUpdate Worknotes/UI Page_HTML.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\r\n    <j2:set var=\"jvar_hide_response_time\" value=\"true\" />\r\n    <html lang=\"en\">\r\n\r\n    <head>\r\n        <meta charset=\"utf-8\" />\r\n        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\" />\r\n\r\n        <link rel=\"stylesheet\" href=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css\" />\r\n        <link rel=\"stylesheet\" href=\"https://cdn.datatables.net/1.13.7/css/dataTables.bootstrap5.min.css\" />\r\n        <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/5.3.0/css/bootstrap.min.css\" />\r\n\r\n        <script src=\"https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js\"></script>\r\n\r\n        <script src=\"https://code.jquery.com/jquery-3.7.0.js\"></script>\r\n        <script src=\"https://cdn.datatables.net/1.13.7/js/jquery.dataTables.min.js\"></script>\r\n        <script src=\"https://cdn.datatables.net/1.13.7/js/dataTables.bootstrap5.min.js\"></script>\r\n    </head>\r\n\r\n    <body>\r\n        <!-- Alert Message -->\r\n        <div id=\"primary-message\" style=\"display:none;\" class=\"alert alert-primary\" role=\"alert\">\r\n\r\n        </div>\r\n        <div id=\"error-message\" style=\"display:none;\" class=\"alert alert-danger\" role=\"alert\">\r\n            Error - An error has occurred when trying to update Work Notes.\r\n        </div>\r\n        <div id=\"warning-message-invalid-entry\" style=\"display:none;\" class=\"alert alert-warning\" role=\"alert\">\r\n            Warning - Invalid list of Incident Tickets or Work Note field. Please try again.\r\n        </div>\r\n        <div id=\"warning-message-invalid-tickets-valid-entry\" style=\"display:none;\" class=\"alert alert-warning\" role=\"alert\">\r\n\r\n        </div>\r\n\r\n        <!-- Bulk Update Work Notes Form -->\r\n        <div id=\"form-div\" class=\"container mt-3\">\r\n\r\n            <g:ui_form>\r\n                <div class=\"mb-3 mt-3\">\r\n                    <label class=\"text-lg\" for=\"ticket_list\">Incident Ticket List</label>\r\n                    <input type=\"text\" class=\"form-control text-sm\" id=\"ticket_list\" placeholder=\"Enter list of Incident Tickets, separated by commas. (Example: INC001001, INC001002, ...)\" name=\"ticket_list\" required=\"true\" />\r\n                </div>\r\n                <div class=\"mb-3\">\r\n                    <label class=\"text-lg\" for=\"work_note_to_apply\">Work Note to Apply</label>\r\n                    <textarea type=\"text\" class=\"form-control text-sm\" id=\"work_note_to_apply\" placeholder=\"Enter work note to apply here.\" name=\"work_note_to_apply\" required=\"true\" rows=\"6\" />\r\n                </div>\r\n                <div class=\"d-grid gap-2 col-6 mx-auto\">\r\n                    <button id=\"submit-btn\" class=\"btn btn-primary btn-lg text-lg\"  onclick=\"bulkupdate()\">Submit</button>\r\n                    <button id=\"spinner-btn\" style=\"display:none\" class=\"btn btn-primary btn-lg text-lg\" type=\"button\" disabled=\"true\">\r\n                        <span class=\"spinner-border spinner-border-lg me-2\" role=\"status\" aria-hidden=\"true\"></span>\r\n                        Loading...\r\n                    </button>\r\n                </div>\r\n            </g:ui_form>\r\n\r\n\r\n        </div>\r\n    </body>\r\n    <style>\r\n        textarea {\r\n            resize: none;\r\n        }\r\n\r\n        .text-lg {\r\n            //font-size: 1.125rem;\r\n            font-size: large;\r\n            line-height: 1.75rem;\r\n        }\r\n\r\n        .text-md {\r\n            //font-size: 1rem;\r\n            font-size: medium;\r\n            line-height: 1.5rem;\r\n        }\r\n\r\n        .text-sm {\r\n            //font-size: .75rem;\r\n            font-size: small;\r\n            line-height: 1rem;\r\n        }\r\n\r\n        .text-xs {\r\n            //font-size: .75rem;\r\n            font-size: smaller;\r\n            line-height: 1rem;\r\n        }\r\n    </style>\r\n\r\n    </html>\r\n</j:jelly>"
  },
  {
    "path": "Client-Side Components/UI Pages/BulkUpdate Worknotes/UI Page_ProcessingScript.js",
    "content": "// gs.info(\"Ticket List :\"+ticket_list);\r\n\r\n// Sepetation of ticket based on the comma.\r\nvar ticketlist = ticket_list;\r\nvar formatted_ticket_list = ticketlist.split(/[ ,]+/).filter(Boolean);\r\n            var valid_ticket_list = [];\r\n            var invalid_ticket_list = [];\r\n\r\n\r\n\t\t// Checks the first ten Incident ticket into an array which start with INC \r\n            for (var i = 0; i < formatted_ticket_list.length; i++) {\r\n                if (formatted_ticket_list[i].startsWith('INC')) {\r\n                    if (formatted_ticket_list[i].length == 10)\r\n                        valid_ticket_list.push(formatted_ticket_list[i]);\r\n                    else {\r\n                        invalid_ticket_list.push(formatted_ticket_list[i]);\r\n                    }\r\n                } else {\r\n                    invalid_ticket_list.push(formatted_ticket_list[i]);\r\n                }\r\n            }\r\n\r\n\t// Looping into each ticket to update the worknotes based on the table\r\n\t\t\tfor (var k = 0; k < valid_ticket_list.length; k++) {\r\n                var gr_inc = new GlideRecordSecure('incident');\r\n                gr_inc.addQuery('number', valid_ticket_list[k]);\r\n                gr_inc.query();\r\n\r\n                if (gr_inc._next()) {\r\n                    gr_inc['work_notes'] = work_note_to_apply;\r\n                    gr_inc.update();\r\n                } else {\r\n                    //If the ticket number was not found, add to Invalid Ticket List \r\n                    invalid_ticket_list.push(valid_ticket_list[k]);\r\n                }\r\n            }\r\n\t\t\t\r\n"
  },
  {
    "path": "Client-Side Components/UI Pages/CMDB CI Management UI page/ci_client_script.js",
    "content": "/*  function to fetch the configuration item details like serial No, name , class , install status */\nfunction searchCI() {  \n  var ciNameorId = document.getElementById('ci_input').value;\n  if (!ciNameorId) {\n      alert('Please Enter the Configuration item Name or ID');\n  } else {\n      var ga = new GlideAjax('ci_lifecycle_management_script_include');\n      ga.addParam('sysparm_name', 'get_ci_info');\n      ga.addParam('sysparm_ci_name_or_id', ciNameorId);\n      ga.getXMLAnswer(function(response) {\n          var record = JSON.parse(response);\n          document.getElementById(\"ci-info\").style.display = \"block\";\n          document.getElementById('ci_name').innerText = record.name;\n          document.getElementById('serial_number').innerText = record.serial_number;\n          document.getElementById('ci_class').innerText = record.ci_class;\n          document.getElementById('ci_install_status').innerText = record.ci_install_status;\n          var operations = ['Stolen', 'Retired','In Stock','In Maintenance'];\n          operations.forEach((operation) => {\n      var hidden_ele_id = operation.replace(/\\s+/g, \"\");\n      var elementId = \"ci_\"+hidden_ele_id;\n      var ele = document.getElementById(elementId);\n      if(operation == record.ci_install_status){\n        ele.style.display = \"none\";\n      }\n          });\n      });\n  }\n}\n/* end of searchCI() */\n\n/* function to update the status of Configuration item */\nfunction UpdateCI(status) {\n  var ciNameorId = document.getElementById('ci_input').value;\n  if (ciNameorId) {\n      var updateci = new GlideAjax('ci_lifecycle_management_script_include');\n      updateci.addParam('sysparm_name', 'update_ci_info');\n      updateci.addParam('sysparm_ci_id_or_name', ciNameorId);\n      updateci.addParam('sysparm_ci_status', status);\n      updateci.getXMLAnswer(function(response) {\n          var result = JSON.parse(response);\n    if(result.updated == true){\n      alert(\"Record Updated Successfully\");\n    }\n         \n      });\n\n\n  } else {\n     alert('Facing issues to Update the CI');\n  }\n\n}\n/* function to update the status of Configuration item */\n"
  },
  {
    "path": "Client-Side Components/UI Pages/CMDB CI Management UI page/ci_lifecycle_ui_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n <!--basic style for html element-->\n    <style>\n        .ci-lifecycle-container {\n            display: flex;\n            justify-content: center;\n            text-align: center;\n        }\n        .card {\n            width: 400px;\n            margin: 20px auto;\n            border: 1px solid #ddd;\n            border-radius: 8px;\n            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);\n            overflow: hidden;\n            font-family: Arial, sans-serif;\n        }\n        .card-header {\n            background-color: #007bff;\n            color: #fff;\n            padding: 16px;\n            font-size: 18px;\n        }\n        .card-footer, .card-body {\n            padding: 16px;\n        }\n        button {\n            padding: 10px;\n            font-size: 14px;\n            color: #FFFFFF;\n            background-color: #3cb371;\n            border: none;\n            cursor: pointer;\n            margin: 5px;\n        }\n        #ci_input {\n            padding: 5px;\n            width: 100%;\n        }\n        #ci_search {\n            margin-left: 1rem;\n            padding: 15px;\n            width: 30%;\n            cursor: pointer;\n            border-radius: 10px;\n            background-color: yellow;\n        }\n        table {\n            width: 100%;\n            border-collapse: collapse;\n            margin: 20px auto;\n            font-size: 16px;\n            border: 1px solid #ddd;\n        }\n        th, td {\n            padding: 12px;\n            text-align: left;\n        }\n        th {\n            background-color: #f2f2f2;\n            font-weight: bold;\n            width: 30%;\n        }\n        .card-footer {\n            background-color: #f9f9f9;\n            border-top: 1px solid #ddd;\n            display: flex;\n            justify-content: center;\n        }\n\t</style> \n  <!-- end of styling -->\n    <div class=\"ci-lifecycle-container\"> <!-- container -->\n        <div class=\"ci-lifecycle-header\"> <!-- header -->\n            <h1>CI Lifecycle Management</h1> <!-- header title -->\n            <!-- search input box and search button -->\n            <div style=\"display:flex;\">  \n                <input type=\"text\" name=\"ci_input\" id=\"ci_input\"  placeholder=\"Enter CI Id ...\" />\n                <button id=\"ci_search\" name=\"ci_search\"  onClick=\"searchCI()\">Search CI</button>\n            </div>\n            <!-- end of search -->\n            <!-- Display Configuration item details -->\n            <div id=\"ci-info\" style=\"display:none;\">\n                <div class=\"card\" >\n                    <div class=\"card-header\">\n                        Configuration Item\n                    </div>\n                    <div class=\"card-body\" >\n                        <table>\n                            <tr>\n                                <th style=\"\">Serial No</th>\n                                <td id=\"serial_number\"></td>\n                            </tr>\n                            <tr>\n                                <th style=\"\">Name</th>\n                                <td id=\"ci_name\" ></td>\n                            </tr>\n                            <tr>\n                                <th>CI class</th>\n                                <td id='ci_class'></td>\n                            </tr>\n                            <tr>\n                                <th>Status</th>\n                                <td id=\"ci_install_status\"></td>\n                            </tr>\n\n                        </table>\n\n\n                    </div>\n                    <!-- end -->\n\n            <!-- button to change configuration item install status -->\n            <div class=\"card-footer\" style=\"\">\n              <!--stolen-->\n              <button id=\"ci_Stolen\" name=\"ci_Stolen\"  onClick=\"UpdateCI(8)\">Stolen</button>\n              <!--retired-->\n              <button id=\"ci_Retired\" name=\"ci_Retired\"  onClick=\"UpdateCI(7)\">Retire CI\n              </button>\n\n              <!--In stock-->\n              <button id=\"ci_InStock\" name=\"ci_InStock\"  onClick=\"UpdateCI(6)\">In Stock\n              </button>\n              <!--In Maintainance-->\n              <button class=\"ci_InMaintenance\" id=\"ci_InMaintenance\" onclick=\"UpdateCI(3)\" >In maintainace</button>\n            </div>\n            <!-- end -->\n\n            </div>\n\n            </div>\n        </div>\n    </div>\n\n\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/CMDB CI Management UI page/ci_script_include.js",
    "content": "var ci_lifecycle_management_script_include = Class.create(); // script include class name \nci_lifecycle_management_script_include.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    /* function to query the cmdb_ci table to update the status of configuration item */\n    update_ci_info: function() {\n      var ci_id = this.getParameter('sysparm_ci_id_or_name');\n      var cistatus = this.getParameter('sysparm_ci_status');\n      var gr = new GlideRecord('cmdb_ci');\n      gr.addQuery('sys_id',ci_id);\n      gr.query();\n      if(gr.next()){\n        gr.setValue('install_status', cistatus);\n              gr.update();\n        return JSON.stringify({\n          \"updated\":true\n        });\n      }\n\t    },\n    /* end of update_ci_info function */\n\n    \n    /* function to query the cmdb_ci table to fetch configuration item details according to ci sys_id*/\n    get_ci_info:function(){\n      var ciNameOrId = this.getParameter('sysparm_ci_name_or_id');\n      var gr = new GlideRecord('cmdb_ci');\n      if(gr.get(ciNameOrId)){\n        var record = {\n          success:true,\n          name: gr.getValue('name'),\n          serial_number: gr.getValue('serial_number'),\n          ci_class : gr.getValue('sys_class_name'),\n          ci_install_status : gr.getDisplayValue('install_status')\n        };\n        return JSON.stringify(record);\n\n\t    }\n   },\n   /* end of get_ci_info() */\n    type: 'ci_lifecycle_management_script_include'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Custom Alert using UI Page/README.md",
    "content": "This script helps you to create custom popup easily and attached the screenshot for the reference, have a look.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Custom Alert using UI Page/client script.js",
    "content": " function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n\n    var gm = new GlideModal(\"glide_confirm2\", false, 600);\n    gm.setWidth(600);\n    gm.setTitle('Confirmation');\n    gm.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Custom Alert using UI Page/custom alert.js",
    "content": "******HTML*****\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\t<g:ui_form>\n<div style=\"font-size:18px;\">\n\t<p style=\"margin-bottom:15px; margin-bottom:10px;\"> Please go through this knowledge&#160;<a href =\"www.google.com\">article</a>&#160;before raising a priority incident</p>\n<button class=\"btn btn-success\" style=\"margin-right:10px;\" onclick=\"proceedFurther()\" type=\"button\">Proceed</button>\n<button class=\"btn btn-danger\" onclick=\"cancelAlert()\" type=\"button\">Cancel</button> \n\t</div>\n </g:ui_form>\n</j:jelly>\n****************\n\n**Client Script**\nfunction cancelAlert(){\nGlideDialogWindow.get().destroy();\n}\nfunction proceedFurther(){\ng_form.save();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Dynamic program status overview/Program details dynamic content block.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\t\n\t<div class=\"col-md-12\">\n\t\t\t<div class=\"panel-body\">\n\t\t\t\t<div id=\"pgmDetails\">\n\t\t\t\t\t<div class=\"list-group\"></div>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\n<script>\n\t\n\tvar container = document.getElementById(\"pgmDetails\");\n\t\n\t//Get the saved default filter\n\tvar defaultFilter = window.SNC.canvas.interactiveFilters.defaultValues;\n\t//Check if the saved default filter is an empty object, this means no filter (all) is selected\n\tif (Object.keys(defaultFilter).length === 0) {\n\t\tcontainer.textContent = \"No Program selected\";\n\t}\n\t//If the object is not empty, that means a default filter is saved and applied onload of the dashboard\n\telse {\n\t\tvar returnValue = defaultFilter[Object.keys(defaultFilter)[0]];\n\t\tfilterDefault = returnValue[0].filter;\n\t\tvar priorityID = filterDefault.split(\"=\").pop()\n\t\tcontainer.textContent = getText(priorityID);\n\t}\n\t\n\t//If we select \"All\", the filter is destroyed\n\tCustomEvent.observe('dashboard_filter.removed', function() {\n\t\tcontainer.textContent = \"No Program selected\";\n\t});\n\t\n\t//If we select something other than all, a filter is added\n\tCustomEvent.observe('dashboard_filter.added', function(filterMessage) {\n\t\tvar idObj = filterMessage.id;\n\t\tvar filterFound = filterMessage[idObj][0].filter;\n\t\tpriorityID = filterFound.split(\"=\").pop();\n\t\n\t\n\t\tvar ga = new GlideAjax('Program_Info');\n\t\tga.addParam('sysparm_name', 'getProgInfo');\n\t\tga.addParam('sysparm_task_type', priorityID);\n\t\tga.addParam('sysparm_fields', \"executive_summary,comments,achievements_last_week,key_activities_next_week\");\n\t\tga.getXML(callback);\n\t\tfunction callback(response) {\n\t\t\tvar result = response.responseXML.documentElement.getAttribute(\"answer\");\n\t\t\tvar details = JSON.parse(result);\n\t\t\tvar keys = Object.keys(details[0]);\n\t\t\tvar html = [];\n\t\t\tkeys.forEach(function(key) {\n\t\t\t\tvar label = key.toUpperCase().replace(\"_\", \" \");\n\t\t\t\tvar text = details[0][key];\n\t\t\t\thtml.push('<div class=\"list-group-item\"><h4 class=\"list-group-item-heading\">'+ label + '</h4>');\n\t\t\t\thtml.push('<p class=\"list-group-item-text\">' + text + '</p></div>');\n\t\t\t});\n\t\t\tpgmDetails.innerHTML = html.join(\"\");\n\t\t}\n\t});\n\t\n</script>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Dynamic program status overview/README.md",
    "content": "This is a super basic set of ui page and dynamic content block that can be added to a dashboard to allow users to select program records that will then load additional information, such as overall health and executive summary, into a content block.\n\nAddition to-dos:\n 1. The formatting is quite basic - you'll definitely need to apply some styles as per the instance design or customer preferences.\n 2. Link to the program status report to allow for another layer of detail to be accessible\n 3. Improve how users can interact with the program list\n\nSee attached image for example of what this looks like.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Dynamic program status overview/program_list.html",
    "content": "<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\t<html>\t\n\t\t<style>\n\t\t\ttable {\n\t\t\tborder-collapse: collapse;\n\t\t\twidth: 100%;\n\t\t\t}\n\n\t\t\tth, td {\n\t\t\ttext-align: left;\n\t\t\tpadding: 8px;\n\t\t\tcolor: white;\n\t\t\t}\n\n\t\t\ttr:nth-child(even){background-color: #f2f2f2}\n\n\t\t\tth {\n\t\t\t\tbackground-color: #0A2356;\n\t\t\t\tcolor: white !important;\n\t\t\t}\n\t\t\t\n\t\t\ttable.row-clickable tbody tr td {\n\t\t\t\tpadding: 0;\n\t\t\t}\n\n\t\t\ttable.row-clickable tbody tr td a {\n\t\t\t\tdisplay: block;\n\t\t\t\tpadding: 8px;\n\t\t\t}\n\t\t</style>\n\n\t\t<body>\t\n\n\t\t\t<div id=\"dvTable\">\n\t\t\t</div>\n\n\t\t\t<p id=\"no_users\" style=\"color:blue;display:none\">No program status to show</p>\n\n\t\t</body>\n\t</html>\n\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Dynamic program status overview/program_list.js",
    "content": "showUsers();\n\nfunction showUsers() {\n\n    var tableHeaders = ['Status Date', 'Number', 'Name', 'Overall Health'];\n\n    var gr = new GlideRecord(\"program_status\");\n    gr.orderByDesc('as_on');\n    gr.query();\n\n    if (gr.rows.length > 0) {\n\n        var table = document.createElement(\"TABLE\");\n        table.setAttribute('class', \"table table-hover row-clickable\");\n        table.border = \"2\";\n        table.padding = \"10px\";\n        table.id = \"myTable\";\n        var columnCount = tableHeaders.length;\n        var row = table.insertRow(-1);\n        for (var i = 0; i < columnCount; i++) {\n            var headerCell = document.createElement(\"TH\");\n            headerCell.innerHTML = tableHeaders[i];\n            row.appendChild(headerCell);\n        }\n\n        while (gr.next()) {\n\t\t\t\n            if (gr.executive_summary.length > 20) {\n\n                var pgmGR = new GlideRecord('pm_program');\n                pgmGR.get(gr.program);\n\n                row = table.insertRow(-1);\n\n                var user = row.insertCell(0);\n                var number = row.insertCell(1);\n                var name = row.insertCell(2);\n                var overall_health = row.insertCell(3);\n\n                if (gr.as_on != '') {\n                    user.innerHTML = gr.as_on;\n                    number.innerHTML = gr.number;\n                    name.innerHTML = pgmGR.short_description;\n                    overall_health.innerHTML = gr.overall_health;\n                } else {\n                    user.innerHTML = '';\n                }\n                var dvTable = document.getElementById(\"dvTable\");\n                dvTable.innerHTML = \"\";\n                dvTable.appendChild(table);\n            }\n            document.getElementById('no_users').style.display = 'none';\n        }\n    } else {\n        document.getElementById('no_users').style.display = '';\n        var Table = document.getElementById(\"dvTable\");\n        Table.innerHTML = \"\";\n    }\n}\n\ndocument.addEventListener('click', function(e) {\n    if (e.target.tagName.toLowerCase() === \"td\") {\n        var tr = e.target.closest('tr');\n        var handler = new DashboardMessageHandler(\"program-status-filter\");\n        var filter_message = {};\n        filter_message.id = \"taskFilter\";\n        filter_message.table = \"program_status\";\n        filter_message.filter = \"number=\" + e.target.innerHTML;\n\n        SNC.canvas.interactiveFilters.setDefaultValue({\n            id: filter_message.id,\n            filters: [filter_message]\n        }, false);\n\n        handler.publishFilter(filter_message.table, filter_message.filter);\n\n    } else {\n        console.log('not a table cell', e.target.tagName);\n    }\n});\n"
  },
  {
    "path": "Client-Side Components/UI Pages/EDM DocUnifiedSearch/EDMSearch.js",
    "content": "if (quicksearch_assigned != '')\n    response.sendRedirect(\"sn_hr_ef_employee_document_list.do?sysparm_force_row_count=100&sysparm_view=hr_employee_files&sysparm_query=employee=\" + quicksearch_assigned);\n\nelse if (quicksearch_doc != '')\n    response.sendRedirect(\"sn_hr_ef_employee_document_list.do?sysparm_force_row_count=100&sysparm_view=hr_employee_files&sysparm_query=document_type=\" + quicksearch_doc);\nelse if (quicksearch_case != '')\n    response.sendRedirect(\"sn_hr_ef_employee_document_list.do?sysparm_force_row_count=100&sysparm_view=hr_employee_files&sysparm_query=hr_case=\" + quicksearch_case);\nelse if (quicksearch_emp != '')\n\tresponse.sendRedirect(\"sn_hr_ef_employee_document_list.do?sysparm_query=employee.u_worker_id=\" + quicksearch_emp + \"^ORhr_profile.employee_number=\" + quicksearch_emp);\n"
  },
  {
    "path": "Client-Side Components/UI Pages/EDM DocUnifiedSearch/README.md",
    "content": "EDM ( Employee document management) which is part of HRSD product doesn’t allow query of employee documents based on different parameters e.g employee id, document type, employee and HR Case.\nCurrent OOB implementation just allows users to query employee documents with list view filters.This results is slow performance and users get security constraints  error based on OOB ACLs which is not intuitive for most users.\n\nMost of the organizations will have thousands of employee documents imported from different sources e.g Workday, Sharepoint and any type of sources.\nDuring EDM implementation, most of the time we will have to import employee documents from these sources and OOB employee document table will contain thousands of documents.\nAlso, after cut off from these sources is done, ServiceNow EDM table becomes every growing table since all future employee documents will be stored in this table.\n\nI created UI page which allows users to search employee documents based on employee, employee ID, document type and HR case.\nThis results is performance improvement as well as better UX for end users to search employee documents.\n\nNote : Please make sure that you have com.sn_employee_document_management plugin active on your instance before using code for this UI page.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/EDM DocUnifiedSearch/client script.js",
    "content": "function cancel() {\n    GlideDialogWindow.get().destroy();\n\treturn false;\n\n}\n\nfunction actionOK() {\n    var emp = gel('quicksearch_assign').value;\n    var doc = gel('quicksearch_doc').value;\n    var cas = gel('quicksearch_case').value;\n    var eid = gel('quicksearch_emp').value;\n    //alert(emp);    \n\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/EDM DocUnifiedSearch/code.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\t<input type=\"hidden\" id=\"cancelled\" name=\"cancelled\" value=\"false\"/>\n\t\n\t<g:requires name=\"styles/heisenberg/heisenberg_all.css\" includes=\"true\" />\n\t<g:requires name=\"scripts/lib/jquery_includes.js\" />\n\t<g:requires name=\"scripts/heisenberg/heisenberg_all.js\" />\n    <g:include_script src=\"bootstrap.min.jsdbx\"/>\n    <style>\n\t\tform {\n\t\twidth: 600px;\n\t\tmargin: 20px auto 30px auto;\n\t\tborder: 1px solid rgba(0,0,0,0.2);\n\t\tpadding: 35px;\n\t\tbox-shadow: 0 1px 5px rgba(0,0,0,0.3);\n\t\tborder-radius: 3px;\n\t\t}\n\t\t\n\t\tlabel {\n\t\tpadding-top: 7px;\n\t\t}\n\t\t\n\t\t.col-sm-9 {\n\t\tpadding-left: inherit;\n\t\tpadding-right: inherit;\n\t\t}\n\t\t\n\t\t.has-error {\n\t\tcolor: #f95050;\n\t\t}\n\t\t\n\t\t.form-control.has-error {\n\t\tcolor: #f95050;\n\t\tborder: 1px solid #f95050;\n\t\t}\n\t\t\n\t\t.footer-buttons {\n\t\tdisplay: block;\n\t\ttext-align: right;\n\t\t}\n\t\t.table-hover tbody tr:hover > th {\n\t\t\tbackground-color: #D1D119!important;\n\t\t}\n\t\t#quicksearch_results {\n\t\tpadding: 20px 40px;\n\t\tborder-top: 1px solid #e8e8e8;\n\t\t}\n        .form-group {\n\t\t\tmargin-left: 15px;\n\t\t}\n\t\t\n\t\t.iframe-placeholder {\n\t\t    background: url('data:image/svg+xml;charset=utf-8,<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"100%\" height=\"100%\" viewBox=\"0 0 100% 100%\"><text fill=\"%23000000\" x=\"50%\" y=\"50%\" font-family=\"\\'Lucida Grande\\', sans-serif\" font-size=\"24\" text-anchor=\"middle\">LOADING...</text></svg>') 0px 0px no-repeat;\n\t\t}\n\t\t.vertical-alignment-helper {\n\t\tdisplay:table;\n\t\theight: 100%;\n\t\twidth: 100%;\n\t\tpointer-events:none;\n\t\t}\n\t\t.vertical-align-center {\n\t\tdisplay: table-cell;\n\t\tvertical-align: middle;\n\t\tpointer-events:none;\n\t\t}\n\t\t.modal-content {\n\t\t/* Bootstrap sets the size of the modal in the modal-dialog class, we need to inherit it */\n\t\twidth:inherit;\n\t\tmax-width:inherit; /* For Bootstrap 4 - to avoid the modal window stretching \n\t\tfull width */\n\t\theight:inherit;\n\t\t/* To center horizontally */\n\t\tmargin: 0 auto;\n\t\tpointer-events:all;\n\t\t}\n\t</style>\n\t\n\t<g:ui_form>\n\t\t\n\t\t<fieldset>\n\t\t\t<h2 style=\"margin-top: 0;\">Employee Documents Search</h2>\n\t\t\t<div style=\"font-size: small;padding-left: 0px;color: gray;\">Search files by Employee, Document Type , HR Case</div>\n\t\t\t<h3 style=\"margin-bottom: 15px;\">Specify one of</h3>\n\t\n\t\t\t<div class=\"row form-group\">\n\t\t\t\t<label for=\"quicksearch_term\" class=\"control-label col-sm-3\">Employee</label>\n\t\t\t\t<span class=\"col-sm-9 form-field\">\t\t\t\n\t\t\t\t\t<g:ui_reference name=\"quicksearch_assigned\" query=\"emailISNOTEMPTY\" id=\"quicksearch_assign\" table=\"sys_user\" />\n\t\t\t\t</span>\n\t\t\t</div>\t\n\t\t\t\n\t\t\t<div class=\"row form-group\">\n\t\t\t\t<label class=\"col-sm-3 control-label\" for=\"quicksearch_table\">Employee ID</label>\n\n\t\t\t\t<span class=\"col-sm-9 form-field\">\n\t\t\t\t\t<input id=\"quicksearch_emp\" class=\"form-control\" type=\"text\" name=\"quicksearch_emp\" onkeyup=\"triggerTheOne('quicksearch_emp');\" />\n\t\t\t\t</span>\n\t\t\t</div>\n\t\t\t<!--div class=\"row form-group\">\n\t\t\t\t<label for=\"quicksearch_term\" class=\"control-label col-sm-3\">Most Recent Login User</label>\n\t\t\t\t<span class=\"col-sm-9 form-field\">\t\t\t\n\t\t\t\t\t<g:ui_reference name=\"quicksearch_most\" query=\"active=true\" id=\"quicksearch_most_recent\" table=\"sys_user\" />\n\t\t\t\t</span>\n   </div-->\t\n\t\t\t<!--div class=\"row form-group\">\n\t\t\t\t<label class=\"col-sm-3 control-label\" for=\"quicksearch_ip_address\" id=\"lbl_ip_address\">Serial Number</label>\n\n\t\t\t\t<span class=\"col-sm-9 form-field\">\n\t\t\t\t\t<input id=\"quicksearch_ip_address\" class=\"form-control\" type=\"text\" placeholder=\"Enter an exact IPv4 address\" name=\"quicksearch_term\" onChange=\"validateIP();\" onkeyup=\"triggerTheOne('quicksearch_ip_address');\" />\n\t\t\t\t</span>\n   </div-->\n\t\t\t<!--div class=\"row form-group\">\n\t\t\t\t<label class=\"col-sm-3 control-label\" for=\"quicksearch_table\">Document Type</label>\n\n\t\t\t\t<span class=\"col-sm-9 form-field\">\n\t\t\t\t\t<input id=\"quicksearch_serial\" class=\"form-control\" type=\"text\" placeholder=\"Enter Document Type\" name=\"quicksearch_serial\" onkeyup=\"triggerTheOne('quicksearch_serial');\" />\n\t\t\t\t</span>\n   </div-->\n\t\t\t\t<div class=\"row form-group\">\n\t\t\t\t<label for=\"quicksearch_term\" class=\"control-label col-sm-3\">Document Type</label>\n\t\t\t\t<span class=\"col-sm-9 form-field\">\t\t\t\n\t\t\t\t\t<g:ui_reference name=\"quicksearch_doc\" query=\"active=true\" id=\"quicksearch_doc\" table=\"sn_hr_ef_document_type\" />\n\t\t\t\t</span>\n\t\t\t</div>\t\n\t\t\t<!--div class=\"row form-group\">\n\t\t\t\t<label for=\"quicksearch_term\" class=\"control-label col-sm-3\">Asset Tag</label>\n\t\t\t\t<span class=\"col-sm-9 form-field\">\n\t\t\t\t\t<input id=\"quicksearch_asset_tag\" class=\"form-control\" type=\"text\" placeholder=\"Enter asset tag\" name=\"quicksearch_asset_tag\" onkeyup=\"triggerTheOne('quicksearch_asset_tag');\" />\n\t\t\t\t</span>\n   </div-->\n\t\t\t\t<div class=\"row form-group\">\n\t\t\t\t<label for=\"quicksearch_term\" class=\"control-label col-sm-3\">HR Case</label>\n\t\t\t\t<span class=\"col-sm-9 form-field\">\t\t\t\n\t\t\t\t\t<g:ui_reference name=\"quicksearch_case\" query=\"active=true\" id=\"quicksearch_case\" table=\"sn_hr_core_case\" />\n\t\t\t\t</span>\n\t\t\t</div>\t\n\t\t\t\n\t\t\n            <div class=\"row form-group\">\n\t\t\t\t<!--<div class=\"col-sm-3\"></div>-->\n                <div class=\"col-sm-12\">\n\t\t\t\t\t\t<span class=\"input-group-checkbox\">\n\t\t\t\t\t\t\t<input id=\"quicksearch_active_only\" class=\"checkbox\" type=\"checkbox\" checked=\"true\" name=\"quicksearch_active_only\"  />\n\t\t\t\t\t\t\t<!--label for=\"quicksearch_active_only\" class=\"checkbox-label\">Exclude Retired</label-->\n\t\t\t\t\t\t</span>\n                </div>\n            </div>\n\t\t\t\n\t\t</fieldset>\n\t\t    <tr id=\"dialogbuttons\">\n        <td colspan=\"3\" align=\"right\" style=\"padding-right:10px;\">\n            <g:dialog_buttons_ok_cancel ok=\"return actionOK();\" cancel=\"cancel();\"/>\n        </td>\n    </tr>\n\n\n\t\t\n\t</g:ui_form>\n\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Edit Last WorkNotes/README.md",
    "content": "Edit Last Entered Work Notes/Additional comments\n\nThis UI action is built specifically to edit the last entered work notes/ additional comments by the user in incident form or it can modified for any table which support this journal fields.\n\nThere is some restriction around journal fields/ work notes as user cannot edit or adjust the work notes/additional comments that they entered. If they wish to edit it, I have introduced a new\nUI action which calls the UI pages which will automatically populates the last entered work notes/comments and user can adjust and submit it.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Edit Last WorkNotes/UIaction.js",
    "content": "//Enable client set to true\n//Enter Onclick value as function name openEditLastCommentModal()\n//Enter form button as true\nfunction openEditLastCommentModal() {\n    var dialog = new GlideModal(\"edit_worknotes_comments_inc\");\n    dialog.setTitle('Edit Last WorkNotes/Additional Comments');\n    dialog.setPreference('incid', g_form.getUniqueValue());\t      \t\n    dialog.setWidth(550);\n    dialog.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Edit Last WorkNotes/scriptinclude.js",
    "content": "var UpdateCommentsworkNotes = Class.create();\nUpdateCommentsworkNotes.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getIncLastWorknotes: function() {\n        var recordId = this.getParameter('sysparm_id');\n        // Check if the record ID is provided and is not null or undefined\n        if (!recordId) {\n            gs.error(\"UpdateINCworkNotes.getIncLastWorknotes: No record ID (sysparm_id) provided.\");\n            return '';\n        }\n\n        var grJournal = new GlideRecord(\"sys_journal_field\");\n        grJournal.addEncodedQuery(\"element_id=\" + recordId + \"^element=comments^ORelement=work_notes\");\n        grJournal.orderByDesc('sys_created_on');\n        grJournal.setLimit(1);\n        grJournal.query();\n\n        if (grJournal.next()) {\n            return grJournal.getValue('value');\n        }\n\n        return '';\n    },\n\n    updateCommentsLatest: function() {\n        var recordId = this.getParameter('sysparm_id');\n        var newComment = this.getParameter('sysparm_newcomment');\n        // Validate input parameters\n        if (!recordId || !newComment) {\n            gs.error(\"UpdateINCworkNotes.updateCommentsLatest: Missing required parameters (sysparm_id or sysparm_newcomment).\");\n            return \"failure: Missing parameters.\";\n        }\n\n        // Update the latest journal entry for the incident\n        var grJournal = new GlideRecord(\"sys_journal_field\");\n        grJournal.addEncodedQuery(\"element_id=\" + recordId + \"^element=comments^ORelement=work_notes\");\n        grJournal.orderByDesc('sys_created_on');\n        grJournal.setLimit(1);\n        grJournal.query();\n\n        if (grJournal.next()) {\n            grJournal.setValue('value', newComment);\n            grJournal.update();\n        } else {\n            // Log if no journal field was found to update\n            gs.warn(\"UpdateINCworkNotes.updateCommentsLatest: No latest journal entry found for record ID: \" + recordId);\n        }\n\n        var grAudit = new GlideRecord(\"sys_audit\");\n        grAudit.addEncodedQuery(\"documentkey=\" + recordId + \"^fieldname=comments^ORfieldname=work_notes\");\n        grAudit.orderByDesc('sys_created_on');\n        grAudit.setLimit(1);\n        grAudit.query();\n\n        if (grAudit.next()) {\n            grAudit.setValue('newvalue', newComment);\n            grAudit.setValue('oldvalue', '');\n            grAudit.update();\n        } else {\n            gs.warn(\"UpdateINCworkNotes.updateCommentsLatest: No latest audit entry found for record ID: \" + recordId);\n        }\n\n        var grHistorySet = new GlideRecord(\"sys_history_set\");\n        grHistorySet.addQuery(\"id\", recordId);\n        grHistorySet.setLimit(1);\n        grHistorySet.query();\n\n        if (grHistorySet.next()) {\n            grHistorySet.deleteRecord();\n        }\n    },\n\n    type: 'UpdateCommentsworkNotes'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Edit Last WorkNotes/uipage_client.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n    <div style=\"display:flex; flex-direction:column;\">\n        <textarea id='commenttext' style=\"width:100%; margin-bottom:10px\" name=\"w3review\" rows=\"4\" cols=\"50\"></textarea>\n        <g:dialog_buttons_ok_cancel ok_text=\"${gs.getMessage('Submit')}\" ok_title=\"${gs.getMessage('Submit')}\" ok=\"return submitComment();\" ok_style_class=\"btn btn-primary\" cancel_text=\"${gs.getMessage('Cancel')}\" cancel_title=\"${gs.getMessage('Cancel')}\" cancel=\"return closeDialog(); disable\" />\n    </div>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Edit Last WorkNotes/uipage_clientcode.js",
    "content": "fetchLastComment();\n\nfunction closeDialog() {\n    GlideDialogWindow.get().destroy();\n    return false;\n}\n\nfunction fetchLastComment() {\n    var dialogWindow = GlideDialogWindow.get();\n    var incidentSysId = dialogWindow.getPreference('incid');\n    var glideAjax = new GlideAjax('UpdateINCworkNotes');\n    glideAjax.addParam('sysparm_name', 'getIncLastWorknotes');\n    glideAjax.addParam('sysparm_id', incidentSysId);\n    glideAjax.getXMLAnswer(setCommentFieldValue);\n}\n\nfunction setCommentFieldValue(answer) {\n    var commentField = document.getElementById('commenttext');\n    if (commentField) {\n        commentField.value = answer || '';\n    }\n}\n\nfunction submitComment() {\n    var dialogWindow = GlideDialogWindow.get();\n    var incidentSysId = dialogWindow.getPreference('incid');\n    var newCommentText = document.getElementById('commenttext').value;\n\n    var glideAjax = new GlideAjax('UpdateINCworkNotes');\n    glideAjax.addParam('sysparm_name', 'updateCommentsLatest');\n    glideAjax.addParam('sysparm_id', incidentSysId);\n    glideAjax.addParam('sysparm_newcomment', newCommentText);\n\n    glideAjax.getXMLAnswer(handleSuccessfulSubmit);\n    closeDialog();\n}\n\nfunction handleSuccessfulSubmit(answer) {\n    window.location.reload();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Export UI pages to word docx/Export to word.js",
    "content": "********** HTML CODE ************\n<? xml version = \"1.0\" encoding = \"utf-8\" ?>\n    <j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n        <body>\n            <div id=\"source-html\">\n                <h1>Heading</h1>\n                <h2>Heading 1</h2>\n                <h3>Heading 2</h3>\n                <button id=\"btn-export\" onclick=\"exportHTML();\">Export to\n                    word doc</button>\n            </div>\n        </body>\n    </j:jelly>\n********* End of HTML *************\n\n********* Client Script ************\n    function exportHTML() {\n        var header = \"<html xmlns:o='urn:schemas-microsoft-com:office:office' \" +\n            \"xmlns:w='urn:schemas-microsoft-com:office:word' \" +\n            \"xmlns='http://www.w3.org/TR/REC-html40'>\" +\n            \"<head><meta charset='utf-8'><title>Export HTML to Word Document with JavaScript</title></head><body>\";\n        var footer = \"</body></html>\";\n        var sourceHTML = header + document.getElementById(\"source-html\").innerHTML + footer;\n\n        var source = 'data:application/vnd.ms-word;charset=utf-8,' + encodeURIComponent(sourceHTML);\n        var fileDownload = document.createElement(\"a\");\n        document.body.appendChild(fileDownload);\n        fileDownload.href = source;\n        fileDownload.download = 'document.doc';\n        fileDownload.click();\n        document.body.removeChild(fileDownload);\n    }\n    ********* End of Client Script ************\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Export UI pages to word docx/README.md",
    "content": "This code allows you to export the contents of a UI page into a word document\n\nSteps\n1. Create a body and contents in the html part of the UI Page\n2. Create a button and name it as 'Export to word' and call the function.\n3. Now, paste the js code in the client script section of the ui page.\n4. Click on try it and the contents of the html body will be exported as a word upon clicking on the 'export to word' button.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Fetch Table(Incident) fields in UI Page/Fetch incident fields.js",
    "content": "********** UI Action Code **********\n  //Client callable : true\n  //OnClick : fetchHTML();\n  //table : incident\n  \n  function fetchHTML() {\n\tvar dialog = new GlideDialogWindow(\"my_page\"); \n    dialog.setTitle('Fetch table fields');\n\tdialog.setSize('600', '600');\n    dialog.setPreference(\"sysparm_sys_id\", g_form.getUniqueValue());\n    dialog.render();\n}\n\n********** End of UI Action code **********\n  \n********** UI Page code **********\n// Name of ui page : my_page\n  \n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n<g:evaluate var=\"jvar_sysId\" expression=\"RP.getWindowProperties().get('sysparm_sys_id')\" />\n<g:evaluate jelly = \"true\"  object=\"true\">\n\nvar gr_inc = new GlideRecord('incident');\ngr_inc.addQuery('sys_id', jelly.jvar_sysId);\ngr_inc.query();\ngr_inc;\n</g:evaluate>\n<div>\n<h2>Fetch Incident fields in UI Page</h2>\t\n  <body>\n        <div>\n            <p>\t\n\t\t\t\t    <j:while test=\"${gr_inc.next()}\">  \n\t\t\t\t    <div>\n\t                    <h2>Caller </h2>\n <p>The Caller is : <span value=\"${gr_inc.sys_id}\">${gr_inc.caller_id.getDisplayValue()}</span></p>\n\t                </div>\n\t\t\t\t\t<div>\n\t\t\t\t\t\t<h2>Incident Short Description</h2>\n\t\t\t\t        <p value=\"${gr_inc.sys_id}\">${gr_inc.short_description()}</p>\n\t\t\t\t\t</div>\n\t\t\t\t\t<div>\t\n\t\t\t\t\t\t<h2>Incident Description</h2>\n\t\t\t\t        <p value=\"${gr_inc.sys_id}\">${gr_inc.description()}</p>\n\t\t\t\t    </div>\n\t\t\t\t   \n\t\t\t\t</j:while> <!-- The entire content needs to be written inside this while loop.-->\n                <!-- End -->\n\t\t\t</p> <!-- Any content outside this p tag will not fetch the Incident fields -->\n        </div>\n    </body>\t\n</div>\n</j:jelly>\n\n********** End of UI Page code **********\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Fetch Table(Incident) fields in UI Page/README.md",
    "content": "Fetch Incident fields(or any table fields) in a UI page via UI Action trigger\n\nSteps\n1) Create a UI action and create a function.\n2) Add the UI action script provided in the Script section.\n3) This code helps to render a pop up window of 600x600 dimentions for the UI page and passes the current sys id to UI page.\n4) Make sure to add the code in the UI Page : <g:evaluate var=\"jvar_sysId\" expression=\"RP.getWindowProperties().get('sysparm_sys_id')\" />\n5) Create a UI Page and add the HTML Code provided.\n6) Trigger the UI Action from the Incident form and it should render the ui page with the incident fields data.\n7) Add additional static or dynamic data as per the requirement.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Populate Glide List field/README.md",
    "content": "//The UI page includes a label prompting users to select an \"SN Company,\" and utilizes the lightweight_glide_list2 macro to generate a dropdown list populated with active companies from the core_company table. The form also features \"OK\" and \"Cancel\" buttons in the modal footer, which trigger the onSubmit() and onCancel() functions, respectively, when clicked. This setup facilitates user interaction by enabling the selection of a company, which can be crucial for various processes within the ServiceNow platform.\n\n\n<p><label for=\"company\"><b>SN Company</b></label></p>\n//This code creates a label for the company selection field, indicating that users should select a company from the list.\nCompany Selection Control:\n\n\n<g:macro_invoke macro=\"lightweight_glide_list2\" id=\"comId\" name=\"comId\" control_name=\"comCollector\" reference=\"core_company\" query=\"u_active=true\" can_write=\"true\" />\n//This line invokes a macro called lightweight_glide_list2, which creates a dropdown or list control for selecting a company. The parameters specify that it should reference the core_company table, only show active companies (where u_active is true), and allow the user to write (make changes).\n\n<div class=\"modal-footer\">\n    <span class=\"pull-right\">\n        <g:dialog_buttons_ok_cancel ok=\"return onSubmit();\" cancel=\"return onCancel();\" />\n    </span>\n</div>\n//This section creates a footer for the modal dialog containing \"OK\" and \"Cancel\" buttons. The g:dialog_buttons_ok_cancel macro generates these buttons, calling the onSubmit() function to handle submission when the OK button is clicked and the onCancel() function to handle cancellation when the Cancel button is clicked.\n\n\n\n\nvar splitted = ['0c441abbc6112275000025157c651c89', '820351a1c0a8018b67c73d51c074097c'];\nvar displayText = ['3Com', 'Acer'];\n//Two arrays are defined: splitted contains unique identifiers (likely Sys IDs), and displayText contains the corresponding display names for each identifier. For example, the first identifier corresponds to \"3Com\" and the second to \"Acer.\"\nLoop Through the Arrays:\n\n\nfor (var z = 0; z < splitted.length; z++) {\n//A for loop is initiated to iterate through the elements of the splitted array. The loop runs as long as z is less than the length of the splitted array, ensuring that each item in both arrays is processed.\nGet the Dropdown Element:\n\nvar selEl = document.getElementById('select_0comCollector');\n//This line retrieves the HTML select element with the ID select_0comCollector. This element is where the new options will be added.\nCreate a New Option Element:\n\n\nvar optEl = document.createElement('option');\n//A new <option> element is created, which will represent a selectable item in the dropdown menu.\nCreate and Append Display Text:\n\n\nvar txtNd1 = document.createTextNode([displayText[z]]);\n//A text node is created using the corresponding display name from the displayText array, indexed by z. This node will be the visible text for the option.\nSet Option Value:\n\n\noptEl.setAttribute('value', [splitted[z]]);\n//The value of the newly created option is set to the corresponding identifier from the splitted array, indexed by z.\nAppend Text Node to Option Element:\n\n\noptEl.appendChild(txtNd1);\n//The text node containing the display name is appended to the option element, so it will be displayed in the dropdown.\nAppend Option to Select Element:\n\nselEl.appendChild(optEl);\n//Finally, the fully constructed option element is appended to the dropdown select element, making it visible in the user interface.\n\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Populate Glide List field/populateGlideListClientScript.js",
    "content": "    var splitted = ['0c441abbc6112275000025157c651c89', '820351a1c0a8018b67c73d51c074097c']; //Two arrays are defined: splitted contains unique identifiers (likely Sys IDs), and displayText contains the corresponding display names for each identifier. For example, the first identifier corresponds to \"3Com\" and the second to \"Acer.\"\n    var displayText = ['3Com', 'Acer'];\n    for (var z = 0; z < splitted.length; z++) {\n        var selEl = document.getElementById('select_0comCollector'); //This line retrieves the HTML select element with the ID select_0comCollector. This element is where the new options will be added.\n        var optEl = document.createElement('option');\n        var txtNd1 = document.createTextNode([displayText[z]]); //A text node is created using the corresponding display name from the displayText array, indexed by z. This node will be the visible text for the option.\n        optEl.setAttribute('value', [splitted[z]]); //The value of the newly created option is set to the corresponding identifier from the splitted array, indexed by z.\n        optEl.appendChild(txtNd1);\n        selEl.appendChild(optEl); //The fully constructed option element is appended to the dropdown select element, making it visible in the user interface.\n    }\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Populate Glide List field/populateGlideListHTML.js",
    "content": "//Creating a user interface form that allows users to select a company from a list. The form includes a label prompting \nusers to select an \"SN Company,\" and utilizes the lightweight_glide_list2 macro to generate a dropdown list populated with active companies from the core_company table. \nThe form also features \"OK\" and \"Cancel\" buttons in the modal footer, which trigger the onSubmit() and onCancel() \nfunctions, respectively, when clicked. This setup facilitates user interaction by enabling the selection of a company, \nwhich can be crucial for various processes within the ServiceNow platform.\n\n\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\n    <g:ui_form>\n        <g:evaluate>\n            var currRecSysId = RP.getWindowProperties().get('curr_rec_sysid') || '';\n        </g:evaluate>\n\n        <input type=\"hidden\" name=\"curr_rec_sysid\" value=\"$[currRecSysId]\" />\n        <input type=\"hidden\" id=\"company_list\" name=\"company_list\" value=\"\" />\n\n        <br />\n        <br />\n\n        <p><label for=\"company\"><b>SN Company</b></label></p>\n        <g:macro_invoke macro=\"lightweight_glide_list2\" id=\"comId\" name=\"comId\" control_name=\"comCollector\" reference=\"core_company\" query=\"u_active=true\" can_write=\"true\" />\n        <br />\n\n        <div class=\"modal-footer\">\n            <span class=\"pull-right\">\n                <g:dialog_buttons_ok_cancel ok=\"return onSubmit();\" cancel=\"return onCancel();\" />\n            </span>\n        </div>\n    </g:ui_form>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Progress Loader/REAMDE.md",
    "content": "# Loaders UI Page  \n\nThis UI Page showcases a collection of loaders and spinners designed to visually represent ongoing processes in web applications. These components enhance user experience by providing clear feedback during data loading or processing.  \n\n## Features  \n- A diverse set of loader and spinner designs.  \n- Fully responsive and easily customizable.  \n- Lightweight and seamlessly integrable into glideModal or other UI components.  \n\n\n## Usage  \n1. Select the desired loader's HTML and CSS code from the examples.  \n2. Go to the Application Navigator and search for UI Pages under **System UI > UI Pages**.\n3. Click on New and create new record by adding given HTML in the HTML section.\n4. Adjust the styles as needed to align with your design requirements.  \n\n\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Progress Loader/example_use_GlideModal.js",
    "content": "\n\n//Example code to show the UI Page in GlideModal\n\nvar dialog = new GlideModal(\"YOUR_UI_PAGE_NAME\");\n\n//Set the dialog title\ndialog.setTitle('Demo title');\n\n//Set the dialog width\t\t      \t\ndialog.setWidth(550);\n\n//Display the modal\ndialog.render();\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Progress Loader/loaderHTML.html",
    "content": "<style>\n  /* Loader 1 CSS */\n  .lds-ring,\n  .lds-ring div {\n    box-sizing: border-box;\n  }\n\n  .lds-ring {\n    display: inline-block;\n    position: relative;\n    width: 80px;\n    height: 80px;\n  }\n\n  .lds-ring div {\n    box-sizing: border-box;\n    display: block;\n    position: absolute;\n    width: 64px;\n    height: 64px;\n    margin: 8px;\n    border: 8px solid blue;\n    border-radius: 50%;\n    animation: lds-ring 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n    border-color: blue transparent transparent transparent;\n  }\n\n  .lds-ring div:nth-child(1) {\n    animation-delay: -0.45s;\n  }\n\n  .lds-ring div:nth-child(2) {\n    animation-delay: -0.3s;\n  }\n\n  .lds-ring div:nth-child(3) {\n    animation-delay: -0.15s;\n  }\n\n  @keyframes lds-ring {\n    0% {\n      transform: rotate(0deg);\n    }\n\n    100% {\n      transform: rotate(360deg);\n    }\n  }\n\n  /* Loader 2 CSS */\n\n  .lds-roller,\n  .lds-roller div,\n  .lds-roller div:after {\n    box-sizing: border-box;\n  }\n\n  .lds-roller {\n    display: inline-block;\n    position: relative;\n    width: 80px;\n    height: 80px;\n  }\n\n  .lds-roller div {\n    animation: lds-roller 1.2s cubic-bezier(0.5, 0, 0.5, 1) infinite;\n    transform-origin: 40px 40px;\n  }\n\n  .lds-roller div:after {\n    content: \" \";\n    display: block;\n    position: absolute;\n    width: 7.2px;\n    height: 7.2px;\n    border-radius: 50%;\n    background: currentColor;\n    margin: -3.6px 0 0 -3.6px;\n  }\n\n  .lds-roller div:nth-child(1) {\n    animation-delay: -0.036s;\n  }\n\n  .lds-roller div:nth-child(1):after {\n    top: 62.62742px;\n    left: 62.62742px;\n  }\n\n  .lds-roller div:nth-child(2) {\n    animation-delay: -0.072s;\n  }\n\n  .lds-roller div:nth-child(2):after {\n    top: 67.71281px;\n    left: 56px;\n  }\n\n  .lds-roller div:nth-child(3) {\n    animation-delay: -0.108s;\n  }\n\n  .lds-roller div:nth-child(3):after {\n    top: 70.90963px;\n    left: 48.28221px;\n  }\n\n  .lds-roller div:nth-child(4) {\n    animation-delay: -0.144s;\n  }\n\n  .lds-roller div:nth-child(4):after {\n    top: 72px;\n    left: 40px;\n  }\n\n  .lds-roller div:nth-child(5) {\n    animation-delay: -0.18s;\n  }\n\n  .lds-roller div:nth-child(5):after {\n    top: 70.90963px;\n    left: 31.71779px;\n  }\n\n  .lds-roller div:nth-child(6) {\n    animation-delay: -0.216s;\n  }\n\n  .lds-roller div:nth-child(6):after {\n    top: 67.71281px;\n    left: 24px;\n  }\n\n  .lds-roller div:nth-child(7) {\n    animation-delay: -0.252s;\n  }\n\n  .lds-roller div:nth-child(7):after {\n    top: 62.62742px;\n    left: 17.37258px;\n  }\n\n  .lds-roller div:nth-child(8) {\n    animation-delay: -0.288s;\n  }\n\n  .lds-roller div:nth-child(8):after {\n    top: 56px;\n    left: 12.28719px;\n  }\n\n  @keyframes lds-roller {\n    0% {\n      transform: rotate(0deg);\n    }\n\n    100% {\n      transform: rotate(360deg);\n    }\n  }\n\n  /* Loader 3 CSS */\n\n  body {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 100vh;\n    margin: 0;\n  }\n\n  /**************/\n\n  body {\n    /* background-color: #202628; */\n  }\n\n  .loader-circle-11 {\n    position: relative;\n    width: 70px;\n    height: 70px;\n    transform-style: preserve-3d;\n    perspective: 400px;\n  }\n\n  .loader-circle-11 .arc {\n    position: absolute;\n    content: \"\";\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border-radius: 50%;\n    border-bottom: 5px solid #f00;\n  }\n\n  .loader-circle-11 .arc:nth-child(1) {\n    animation: rotate1 1.15s linear infinite;\n  }\n\n  .loader-circle-11 .arc:nth-child(2) {\n    animation: rotate2 1.15s linear infinite;\n  }\n\n  .loader-circle-11 .arc:nth-child(3) {\n    animation: rotate3 1.15s linear infinite;\n  }\n\n  .loading .arc:nth-child(1) {\n    animation-delay: -0.8s;\n  }\n\n  .loader-circle-11 .arc:nth-child(2) {\n    animation-delay: -0.4s;\n  }\n\n  .loader-circle-11 .arc:nth-child(3) {\n    animation-delay: 0s;\n  }\n\n  @keyframes rotate1 {\n    from {\n      transform: rotateX(35deg) rotateY(-45deg) rotateZ(0);\n    }\n\n    to {\n      transform: rotateX(35deg) rotateY(-45deg) rotateZ(1turn);\n    }\n  }\n\n  @keyframes rotate2 {\n    from {\n      transform: rotateX(50deg) rotateY(10deg) rotateZ(0);\n    }\n\n    to {\n      transform: rotateX(50deg) rotateY(10deg) rotateZ(1turn);\n    }\n  }\n\n  @keyframes rotate3 {\n    from {\n      transform: rotateX(35deg) rotateY(55deg) rotateZ(0);\n    }\n\n    to {\n      transform: rotateX(35deg) rotateY(55deg) rotateZ(1turn);\n    }\n  }\n</style>\n\n<!-- Loader 1 HTML -->\n<div class=\"lds-ring\">\n  <div></div>\n  <div></div>\n  <div></div>\n  <div></div>\n</div>\n\n<!-- Loader 2 HTML -->\n<div class=\"lds-roller\">\n  <div></div>\n  <div></div>\n  <div></div>\n  <div></div>\n  <div></div>\n  <div></div>\n  <div></div>\n  <div></div>\n</div>\n\n<!-- Loader 3 HTML -->\n<div class=\"loader-circle-11\">\n  <div class=\"arc\"></div>\n  <div class=\"arc\"></div>\n  <div class=\"arc\"></div>\n</div>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Real time log watcher/README.md",
    "content": "This code-snippet will give a new experience how you see logs while debugging. This UI Page can be navigated by https://<instance_name>.service-now.com/log_watcher.do and will give an real time log capture which will help to make debugging easy for developers. You can also search with the keyword to find your desired log.\n\nThe below GIF is a short demo for reference.\n\n![Log Watcher](https://github.com/abhrajyotikanrar/code-snippets/assets/25823899/ec51bfd2-ec48-40a4-9ae7-8b9fc37fbd56)\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Real time log watcher/TestScriptInclude.js",
    "content": "var TestScriptInclude = Class.create();\nTestScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getLogs: function() {\n        var query = this.getParameter(\"sysparm_query\");\n        var initialFetch = this.getParameter(\"sysparm_initialFetch\");\n        var startTime = this.getParameter(\"sysparm_startTime\");\n        var response = [];\n        if (initialFetch == \"true\") {\n            startTime = new GlideDateTime().getDisplayValue().toString();\n        }\n\t\tvar date = startTime.split(\" \")[0];\n\t\tvar time = startTime.split(\" \")[1];\n        var grLog = new GlideRecord(\"syslog\");\n        grLog.addEncodedQuery(query);\n        grLog.addEncodedQuery(\"sys_created_on>=javascript:gs.dateGenerate('\" + date.split(\"-\").reverse().join(\"-\") + \"','\" + time + \"')\");\n        grLog.orderByDesc(\"sys_created_on\");\n        grLog.query();\n        while (grLog.next()) {\n            response.push({\n                \"message\": grLog.message.toString(),\n                \"sys_created_on\": grLog.sys_created_on.getDisplayValue()\n            });\n        }\n\t\t\n        return JSON.stringify({\n            \"startTime\": startTime,\n            \"response\": response\n        });\n    },\n\n    type: 'TestScriptInclude'\n});"
  },
  {
    "path": "Client-Side Components/UI Pages/Real time log watcher/log_watcher.js",
    "content": "******HTML*****\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\n\t<input type=\"text\" hidden=\"true\" id=\"initialFetch\" name=\"initialFetch\" value=\"true\" />\n\t<input type=\"text\" hidden=\"true\" id=\"startTime\" name=\"startTime\" />\n\n\t<div style=\"padding: 2rem 20rem;\">\n\t\t<div class=\"input-group mb-3\">\n\t\t\t<input type=\"text\" style=\"padding:2rem;\" class=\"form-control\" id=\"filterText\" placeholder=\"Enter your filter\" aria-label=\"Recipient's username\" aria-describedby=\"basic-addon2\" />\n\t\t\t<div class=\"input-group-append\">\n\t\t\t\t<button style=\"padding:2rem;\" class=\"btn btn-primary\" type=\"button\" onClick=\"startLogs()\">Start Logs</button>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\n\t<div id=\"log_container\" style=\"padding: 5rem;\"></div>\n\n</j:jelly>\n****************\n\n**Client Script**\nfunction startLogs() {\n    setInterval(function() {\n\t\tvar queryText = $j(\"#filterText\").val();\n        var query = \"messageSTARTSWITH\" + queryText;\n        getLogs(query);\n    }, 1000);\n}\n\nfunction getLogs(query) {\n    var initialFetch = $j(\"#initialFetch\").val();\n    var startTime = $j(\"#startTime\").val();\n\t\n    var ga = new GlideAjax(\"TestScriptInclude\");\n    ga.addParam(\"sysparm_name\", \"getLogs\");\n    ga.addParam(\"sysparm_initialFetch\", initialFetch);\n    ga.addParam(\"sysparm_startTime\", startTime);\n    ga.addParam(\"sysparm_query\", query);\n    ga.getXMLAnswer(function(response) {\n        var answer = JSON.parse(response);\n\n        gel('initialFetch').value = \"false\";\n        gel('startTime').value = answer.startTime;\n\n        var data = answer.response;\n\t\t$j(\"#log_container\").empty();\n        data.forEach(function(item) {\n            var elm = \"<div><b>\" + item.sys_created_on + \"</b>: \" + item.message + \"</div>\";\n\t\t\t$j(\"#log_container\").append(elm);\n        });\n\n    });\n}"
  },
  {
    "path": "Client-Side Components/UI Pages/Resolve Incident UI Page/README.md",
    "content": "Use Case: \n\nEverytime user try to resolve the incident, the resolution codes and resolution notes are mandatory to be entered as it hidden in tabs,Since it is mandatory fields. So to ease the process we introduced a custom UI action will prompt the user\nto enter resolution notes and resolution codes and automatically set the state to Resolved.\n\nHow it Works:\n\nNavigate the Incident form and make sure the incident is not closed or active is false.\nClick Resolve Incident UI action, it will open an modal with asking resolution notes and resolution code.\nProvide the details and submit. incident is updated with above Resolution notes and codes and set state to be Resolved.\n\n\nBelow Action Need to Performed: \n\n1.Create UI action: \n\nNavigate to System UI > UI Actions.\nCreate a new UI Action with the following details:\nName: Resolve Incident (or a descriptive name of your choice).\nTable: Incident [incident].\nAction name: resolve_incident_action (must be a unique, server-safe name).\nOrder: A number that determines the position of the button on the form.\nClient: Check this box. This is crucial for running client-side JavaScript.\nForm button: Check this box to display it on the form.\nOnclick: ResolveIncident() (This must match the function name).\nCondition: Set a condition to control when the button is visible (e.g., current.active == true).\n\n2.Create Script Include: \n\nNavigate to System Definition > Script Includes.\nClick New.\nFill in the form:\nName: ResolutionProcessor\nClient callable: Check the box.\nCopy the provided script into the Script field.\nClick Submit.\n\n3.Create UI page:\n\nNavigate to System Definition > UI pages\nFill the HTML and client script.\nClick Submit.\n\n\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Resolve Incident UI Page/UI_action.js",
    "content": "function ResolveIncident() {\n    var dialog = new GlideModal(\"resolve_incident\");\n    dialog.setTitle(\"Resolve Incident\");\n    dialog.setPreference('sysparm_record_id', g_form.getUniqueValue());\n    dialog.render(); //Open the dialog\n}\n\n\n// Navigate to System UI > UI Actions.\n// Create a new UI Action with the following details:\n// Name: Resolve Incident (or a descriptive name of your choice).\n// Table: Incident [incident].\n// Action name: resolve_incident_action (must be a unique, server-safe name).\n// Order: A number that determines the position of the button on the form.\n// Client: Check this box. This is crucial for running client-side JavaScript.\n// Form button: Check this box to display it on the form.\n// Onclick: ResolveIncident() (This must match the function name).\n// Condition: Set a condition to control when the button is visible (e.g., current.active == true).\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Resolve Incident UI Page/scriptinclude.js",
    "content": "var ResolutionProcessor = Class.create();\nResolutionProcessor.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    updateRecord: function() {\n        var recordId = this.getParameter('sysparm_record_id');\n        var reason = this.getParameter('sysparm_reason');\n        var resolution = this.getParameter('sysparm_resolution');\n        gs.info(\"Updating record \" + recordId + \" with reason: \" + reason + \" and resolution: \" + resolution);\n        var grinc = new GlideRecord('incident');\n        if (grinc.get(recordId)) {\n            grinc.close_code = resolution;\n            grinc.close_notes = reason;\n            grinc.state = '6'; //set to resolved\n            grinc.update();\n        } else {\n            gs.error('No Record found for ' + recordId);\n        }\n    },\n    type: 'ResolutionProcessor'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Resolve Incident UI Page/ui_page_client.js",
    "content": "// Below code will be used in client script of UI page as mentioned in README.md file\n\nfunction ResolveIncidentOnsubmit(sysId) {     //This function is called in UI page HTML section When user clicks the Submit button\n      var rejectionReason = document.getElementById('resolution_reason').value.trim();\n      var resolutionCode = document.getElementById('resolution_code').value.trim();\n      if (!rejectionReason || rejectionReason === ' ') {\n          alert('Resolution Notes is a mandatory field.');\n          return false;\n      }\n      if (resolutionCode == 'None') {\n          alert('Resolution Code is a mandatory field.');\n          return false;\n      }\n      var ga = new GlideAjax('ResolutionProcessor');\n      ga.addParam('sysparm_name', 'updateRecord');\n      ga.addParam('sysparm_record_id', sysId);\n      ga.addParam('sysparm_reason', rejectionReason);\n      ga.addParam('sysparm_resolution', resolutionCode);\n      ga.getXML(handleSuccessfulSubmit);\n      GlideDialogWindow.get().destroy();\n      return false;\n      function handleSuccessfulSubmit(answer) {\n          window.location.reload();\n      }\n  }\n  function closeDialog() {\n      GlideDialogWindow.get().destroy();\n      return false;\n  }\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Resolve Incident UI Page/ui_page_html.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n    <g:evaluate var=\"jvar_sys_id\" expression=\"RP.getWindowProperties().get('sysparm_record_id')\" />\n    <div>\n        <p>\n            <label for=\"resolution_reason\">Enter the Resolution Notes</label>\n            <textarea id=\"resolution_reason\" name=\"resolution_reason\" class=\"form-control\" rows=\"4\" cols=\"80\"></textarea>\n        </p>\n        <p>\n            <label for=\"resolution_code\">Select the Resolution Code</label>\n            <select id=\"resolution_code\" name=\"resolution_code\" class=\"form-control\">\n                <option value=\"None\">None</option>\n                <option value=\"No resolution provided\">No resolution provided</option>\n                <option value=\"Resolved by request\">Resolved by request</option>\n                <option value=\"Resolved by caller\">Resolved by caller</option>\n                <option value=\"Solution provided\">Solution provided</option>\n                <option value=\"Duplicate\">Duplicate</option>\n                <option value=\"Resolved by change\">Resolved by change</option>\n                <option value=\"Workaround provided\">Workaround provided</option>\n                <option value=\"Known error\">Known error</option>\n                <option value=\"Resolved by problem\">Resolved by problem</option>\n                <option value=\"User error\">User error</option>\n            </select>\n        </p>\n        <g:dialog_buttons_ok_cancel ok_text=\"${gs.getMessage('Submit')}\" ok_title=\"${gs.getMessage('Submit')}\" ok=\"return ResolveIncidentOnsubmit('${jvar_sys_id}');\" ok_style_class=\"btn btn-primary\" cancel_text=\"${gs.getMessage('Cancel')}\" cancel_title=\"${gs.getMessage('Cancel')}\" cancel=\"return closeDialog(); disable\" />\n    </div>\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Send Email On Form Incident/EmailScript.js",
    "content": "// Email is been used to set the cc and subjed where we got from the UI Page and this will be attached to the Notification to run dymanically\r\n\r\n\r\n(function runMailScript( /* GlideRecord */ current, /* TemplatePrinter */ template,\r\n    /* Optional EmailOutbound */\r\n    email, /* Optional GlideRecord */ email_action,\r\n    /* Optional GlideRecord */\r\n    event) {\r\n\r\n\r\n    // In this we get the data in the 4th parameter so that we use the parm2 to get the object of data and the used in the certain purpose\r\n    \r\n    var obj = JSON.parse(event.parm2);\r\n    email.addAddress(\"cc\", obj.cc);\r\n    email.setSubject(obj.subject);\r\n\r\n    template.print(\" <h5 style='margin: 0px;'>Hi Team,</h5><br><p>Details are : </p>\" + obj.body);\r\n\r\n})(current, template, email, email_action, event);"
  },
  {
    "path": "Client-Side Components/UI Pages/Send Email On Form Incident/Notification.js",
    "content": "/*  \r\nNotificaiton for this proble\r\n\r\nTable : Incident (incident)\r\n\r\nWhen to Send\r\nSend when - Event is fried\r\nEvent name - SendEamilInForm\r\n\r\nWho will receive\r\nEvent parm 1 contains recipient - True\r\n\r\nWhat it will contain\r\n// This will hold the email script which we use to populate the subjec and the body.\r\nMessage HTML - ${mail_script:IncidentFormScript}\r\nEmail template - Unsubscribe and Preferences\r\nContent type - HTML only\r\n\r\n*/\r\n\r\n// Name of the Email Script will be populated when the notification has been triggered.\r\n${mail_script:IncidentFormScript}"
  },
  {
    "path": "Client-Side Components/UI Pages/Send Email On Form Incident/Readme.md",
    "content": "Send Email On Form for every Record\n\nScript Type: UI Action, Table: incident, Form button: True, Client: True, Show update: True, OnClick: functionName()\n\nScript Type: UI Page Category: General\n\nScript Type: Email Script \n\nEvent Registry : Table: incident, Fired by: UI Page, Event Name: SendEmailInForm\n \nNotification : Table: incident, Type: EMAIL, Category: Uncategorized, \n\nGoal: To Send Email on Form Directly by population some field and then customize the body and trigger it.\n\nWalk through of code: So to send the Email directly on each and every record there will be a UI Action which will help to populate the UI Page which we use some field to be populate in the UI Page directly to the particulat HTML content and these are the fields will be populate (Caller Email as the To and then Short Description as the Subjet of the Email) and othe field will be CC and Body which the user want to decide what data can be filled out and then send.\n\n<img width=\"1908\" height=\"417\" alt=\"UIAction_INC_fomr\" src=\"https://github.com/user-attachments/assets/0ec5afb2-8375-41b8-a346-4fe5ee55913f\" />\n\n\nUI Page - This will have 5 components\n1. To Caller \n2. CC\n3. Subject\n4. Body\n5. Send button\n   \n<img width=\"804\" height=\"434\" alt=\"UI Page Email template\" src=\"https://github.com/user-attachments/assets/1d1ff563-7430-4ec3-b391-9f55912ad4e1\" />\n\n\n\nOnce the Send button has been triggered this will call the Processing Script where the event will trigger once this will call the Event Registry event(\"SendEmailInForm\") which we use for this problem statment.Where the Notification will trigger when the Event is fried and then for the email content we uset the Email Script which dynamic content will be populated which we got from the UI page as the event parm2 and then will send the email to the respective caller.\n\n<img width=\"1477\" height=\"759\" alt=\"Notification_Contains\" src=\"https://github.com/user-attachments/assets/ddc91bbf-aeba-4de2-8db4-2fba9d823fba\" />\n<img width=\"1522\" height=\"377\" alt=\"Notification_Receive\" src=\"https://github.com/user-attachments/assets/b2934470-6c90-4b52-be6d-7f2397b8609a\" />\n<img width=\"1705\" height=\"952\" alt=\"Notification1\" src=\"https://github.com/user-attachments/assets/d74cfe59-dd3e-42b7-b95a-b0134169b679\" />\n\n\n\n<img width=\"808\" height=\"278\" alt=\"Email Preview\" src=\"https://github.com/user-attachments/assets/75d4c1ca-36e8-4e01-8d37-6f1fad0ecfc1\" />\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Send Email On Form Incident/UIAction.js",
    "content": "//  This function will creat the model object and then route to the UI page which we configured to show the field details to fill by the user\r\n\r\n/* \r\nTable- Incident (incident)\r\nForm button - True\r\nClient -True\r\nShow Update - True\r\nOnclick - Function name (sendEmail)\r\n\r\n\r\n\r\n*/\r\n function sendEmail(){\r\n var modalT = new GlideModal(\"SendEmailEvent\", false, 1200);\r\n modalT.setTitle(\"Send Email\");\r\n modalT.setPreference(\"sysparm_sys_id\", g_form.getUniqueValue());\r\n modalT.render();\r\n }"
  },
  {
    "path": "Client-Side Components/UI Pages/Send Email On Form Incident/UIPage_Html.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\r\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\r\n  <style>\r\n    body {\r\n      font-family: Arial, sans-serif;\r\n      background-color: #f8f9fa;\r\n      display: flex;\r\n      justify-content: center;\r\n      align-items: center;\r\n      height: 100vh;\r\n      margin: 0;\r\n    }\r\n\r\n    .email-form {\r\n      background: #fff;\r\n      padding: 20px 25px;\r\n      border-radius: 8px;\r\n      box-shadow: 0 2px 8px rgba(0,0,0,0.1);\r\n      width: 850px;\r\n    }\r\n\r\n    .email-form h2 {\r\n      text-align: center;\r\n      margin-bottom: 15px;\r\n      color: #333;\r\n    }\r\n\r\n    label {\r\n      display: block;\r\n      margin-top: 10px;\r\n      font-weight: bold;\r\n      color: #555;\r\n    }\r\n\r\n    input[type=\"text\"],\r\n    input[type=\"email\"],\r\n    textarea {\r\n      width: 100%;\r\n      padding: 8px;\r\n      margin-top: 5px;\r\n      border: 1px solid #ccc;\r\n      border-radius: 5px;\r\n      box-sizing: border-box;\r\n      font-size: 14px;\r\n    }\r\n\r\n    textarea {\r\n      resize: none;\r\n      height: 100px;\r\n    }\r\n\r\n    button {\r\n      width: 100%;\r\n      background-color: #007bff;\r\n      color: white;\r\n      padding: 10px;\r\n      border: none;\r\n      border-radius: 5px;\r\n      margin-top: 15px;\r\n      cursor: pointer;\r\n      font-size: 16px;\r\n    }\r\n\r\n    button:hover {\r\n      background-color: #0056b3;\r\n    }\r\n  </style>\r\n<body>\r\n\r\n <!-- Getting the sys_id From the Form UI View -->\r\n    <g:evaluate var=\"jvar_sysId\" expression=\"RP.getWindowProperties().get('sysparm_sys_id')\" />\r\n\r\n\r\n\t   <!-- GlideRecord Query to current INC Ticket -->\r\n\r\n    <g:evaluate jelly=\"true\" object=\"true\" var=\"jvar_gr\">\r\n        var gr_inc = new GlideRecord('incident');\r\n        gr_inc.addQuery('sys_id', '${jvar_sysId}');\r\n        gr_inc.query();\r\n        gr_inc;\r\n    </g:evaluate>\r\n\r\n    <j:while test=\"${gr_inc.next()}\">\r\n        <j:set var=\"jvar_short_desc\" value=\"${gr_inc.getValue('short_description')}\"></j:set>\r\n        <j:set var=\"jvar_num\" value=\"${gr_inc.getValue('number')}\"></j:set>\r\n        <j:set var=\"jvar_email\" value=\"${gr_inc.caller_id.email}\"></j:set>\r\n    </j:while>\r\n\r\n\r\n  <g:ui_form>\r\n    <!-- <h2>Send Email</h2> -->\r\n    \r\n    <label for=\"to\">To Caller:</label>\r\n    <input type=\"email\" id=\"to\" name=\"to\" value=\"${jvar_email}\" placeholder=\"Caller recipient email\" />\r\n\r\n    <label for=\"bcc\">CC:</label>\r\n    <input type=\"email\" id=\"cc\" name=\"cc\" placeholder=\"Enter CC email\" />\r\n\r\n    <label for=\"subject\">Subject:</label>\r\n    <input type=\"text\" id=\"subject\" name=\"subject\" value=\"${jvar_short_desc}\" placeholder=\"Short Description will be poulated\" />\r\n\r\n    <label for=\"body\">Body:</label>\r\n    <textarea id=\"body\" name=\"body\" placeholder=\"Body to send email\" >${jvar_num}</textarea>\r\n\r\n    <button type=\"submit\">Send</button>\r\n\t</g:ui_form>\r\n</body>\r\n\r\n</j:jelly>"
  },
  {
    "path": "Client-Side Components/UI Pages/Send Email On Form Incident/UIPage_ProcessingScript.js",
    "content": "// Getting the value from the HTML and the make the body and trigger the event for further process.\r\n\r\nvar obj={};\r\nobj.body = body.toString();\r\nobj.cc = cc.toString();\r\nobj.subject = subject.toString();\r\n\r\n// Event Trigger and passing the paramenters\r\n/*\r\nParm 1 : will have the to receipient\r\nParm 2 : will have the cc, subject and body\r\n*/\r\ngs.eventQueue(\"SendEmailInForm\",current,to,JSON.stringify(obj));"
  },
  {
    "path": "Client-Side Components/UI Pages/Share reports with users and groups/README.md",
    "content": "**Usecase:**\nCurrenlty there's no OOB feature to share the all the reports from the particular dashboard with the user or group at a time. Also, sharing the dashboard with the user/group doesnot share the corresponding reports with them automatically.\nIn order to do that, admin or report owner should open each report and share them individually.\nIf the dashboard has more reports i.e 20+, then it'll take a considerable amount of time to complete this task.\nTo reduce the manual effort, we can use this custom logic to share all the reports from the particular dashboard at a time.\n\n**Pre-requisite:**\nA database view which shows the reports shared with atleast one dashboard need to be created. \nServiceNow community article link which explains how to build one..(Thanks to Adam Stout for this)\nhttps://www.servicenow.com/community/performance-analytics-blog/view-reports-on-a-dashboard-and-dashboards-using-a-report/ba-p/2271548\n\n**Components:**\n1. UI Page: It contains Jelly script (HTML), Client script and Processing script. Used to capture the user/group info and share the rports with them.\n2. UI Action(Client): Created on the Dashboards (pa_dashboards) table. Used to open the UI page as apopup/modal window\n\nThis UI action is visible on the dashboard properties page (image attached)\n\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Share reports with users and groups/UI Page/ui_page.html",
    "content": "<!--\nUI Page details \nName: sj_share_reports\nCategory: General\n-->\n\n<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n<g:ui_form>\n    <input type=\"hidden\" id=\"key\" name=\"key\" value=\"${sysparm_key}\"/>\n    <input type=\"hidden\" id=\"cancelled\" name=\"cancelled\" value=\"false\"/>\n \n \n    <div class=\"modal-body\">\n        <div class=\"row form-section form-group\">\n            <div class=\"control-label\">\n                <div><p><b> Select the group: </b></p></div>\n                <div>              \n                    <g:ui_reference name=\"group\" id=\"group\" query=\"active=true\" table=\"sys_user_group\" field=\"name\"/>\n                </div>\n                <div><p><b> Select the user: </b></p></div>\n                <div>              \n                    <g:ui_reference name=\"user\" id=\"user\" query=\"active=true\" table=\"sys_user\" field=\"name;user_name;email\"/>\n                </div>\n            </div>\n        </div>\n    </div>\n    <footer class=\"modal-footer\">\n        <button class=\"btn-primary btn\" style=\"min-width:5em\" onclick=\"return onSubmit();\" name=\"submit\" id=\"submit\">Share</button>\n        <button class=\"btn-danger btn\" style=\"min-width:5em\" onclick=\"return onCancel();\" name=\"cancel\" id=\"cancel\">Cancel</button>\n    </footer>\n</g:ui_form>\n \n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Share reports with users and groups/UI Page/ui_page_client_script.js",
    "content": "function onCancel() {\n    var c = gel('cancelled');\n    c.value = \"true\";\n    GlideModal.get().destroy();\n    return false;\n}\n\nfunction onSubmit() {\n    return true;\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Share reports with users and groups/UI Page/ui_page_processing_script.js",
    "content": "if (cancelled == \"false\") {\n    var dashboard_id = key; // sys id of the dashboard from the Jelly script\n    var group_id = group; // sys id of group from the Jelly script\n    var user_id = user; //sys id of user from the Jelly script\n\n    if (!gs.nil(group_id) || !gs.nil(user_id)) {\n        groupShare(dashboard_id, group_id);\n        userShare(dashboard_id, user_id);\n        response.sendRedirect(\"pa_dashboards.do?sys_id=\" + key);\n    } else {\n        response.sendRedirect(\"pa_dashboards.do?sys_id=\" + key);\n        gs.addErrorMessage(\"Please select group/user\");\n    }\n} else {\n    response.sendRedirect(\"pa_dashboards.do?sys_id=\" + key);\n}\n\nfunction groupShare(dashboard_id, group_id) {\n    var db_view = new GlideRecord('u_reports_shared_with_dashboard'); // Database view name\n    db_view.addEncodedQuery('repstat_report_sys_id!=^dt_dashboard=' + dashboard_id);\n    db_view.query();\n    while (db_view.next()) {\n        var report_id = db_view.rep_sys_id;\n\n        var rec = new GlideRecord(\"sys_report\");\n        rec.get(report_id);\n        rec.user = \"group\";\n        rec.update();\n\n        var report_sharing = new GlideRecord('sys_report_users_groups');\n        report_sharing.addQuery('group_id', group_id);\n\n        report_sharing.addQuery('report_id', report_id);\n        report_sharing.query();\n        if (!report_sharing.next()) {\n            var new_record = new GlideRecord('sys_report_users_groups');\n            new_record.initialize();\n            new_record.report_id = report_id;\n            new_record.group_id = group_id;\n            new_record.insert();\n        }\n\n    }\n}\n\nfunction userShare(dashboard_id, user_id) {\n    var db_view = new GlideRecord('u_reports_shared_with_dashboard');\n    db_view.addEncodedQuery('repstat_report_sys_id!=^dt_dashboard=' + dashboard_id);\n    db_view.query();\n    while (db_view.next()) {\n        var report_id = db_view.rep_sys_id;\n\n        var rec = new GlideRecord(\"sys_report\");\n        rec.get(report_id);\n        rec.user = \"group\";\n        rec.update();\n\n        var report_sharing = new GlideRecord('sys_report_users_groups');\n        report_sharing.addQuery('user_id', user_id);\n        report_sharing.addQuery('report_id', report_id);\n        report_sharing.query();\n        if (!report_sharing.next()) {\n            var new_record = new GlideRecord('sys_report_users_groups');\n            new_record.initialize();\n            new_record.report_id = report_id;\n            new_record.user_id = user_id;\n            new_record.insert();\n        }\n\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/Share reports with users and groups/ui_action_script.js",
    "content": "/*\nUI Action details:\n\nActive: True\nName: Share reports\nTable: Dashboard [pa_dashboards]\nOrder: 1000\nAction name: share_report\nShow update: True\nClient: True\nList v2 Compatible: True\nForm button: True\nForm style: Primary\nOnclick: shareReport();\n\n*/\n\nfunction shareReport() {\n    var modal = new GlideModal(\"sj_share_reports\"); // UI Page id\n    modal.setTitle(\"Share Reports\");\n    modal.setPreference('sysparm_key', g_form.getUniqueValue());\n    modal.render();\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/UI Page Auto Populate Assigned to based on Assignment group/README.md",
    "content": "This project adds functionality to dynamically filter the \"Assigned To\" field based on the selected \"Assignment Group\" on a UI page. When a group is selected, only users from that group will be displayed in the \"Assigned To\" reference field.\n\nOverview\nThe UI page includes two reference fields:\n\nAssignment Group: A reference to the sys_user_group table where users can select an active group.\nAssigned To: A reference to the sys_user table that should display users belonging to the selected \"Assignment Group.\"\nSolution Approach\nUI Page Setup: The \"Assignment Group\" field is configured to trigger a function when a group is selected. The \"Assigned To\" field is updated based on the selected group.\n\nClient-Side Script:\n\nThe onAssignmentGroupChange() function is triggered when the \"Assignment Group\" field is changed. It retrieves the selected group's internal ID.\nA GlideAjax call is made to send the selected group ID to the server-side Script Include.\nUpon receiving a response, the script processes the data and updates the lookup filter of the \"Assigned To\" field to display only the users from the selected group.\nServer-Side Script (Script Include):\n\nA client-callable Script Include queries the sys_user_grmember table to retrieve user IDs of members in the selected group.\nThe user IDs are returned to the client as a comma-separated string, which is used to update the \"Assigned To\" field's query.\nKey Steps\nonChange Trigger:\n\nThe client script triggers whenever the \"Assignment Group\" field is updated by the user.\nGlideAjax Call:\n\nThe onAssignmentGroupChange() function uses GlideAjax to send the selected group ID to the server-side Script Include.\nRetrieve Group Members:\n\nThe Script Include queries the sys_user_grmember table to fetch user IDs for users in the selected group.\nUpdate the Lookup Field:\n\nThe client script dynamically modifies the \"Assigned To\" field's lookup to filter users based on the selected \"Assignment Group.\"\nAdditional Notes\nEnsure that the Script Include is client-callable for it to be accessible via GlideAjax.\nThis solution enhances user experience by restricting the \"Assigned To\" lookup field to relevant users from the selected \"Assignment Group,\" improving data accuracy.\n"
  },
  {
    "path": "Client-Side Components/UI Pages/UI Page Auto Populate Assigned to based on Assignment group/client_script.js",
    "content": "function onAssignmentGroupChange(){\n\tvar assignGroup = gel('assignment_group').value;\t\n\tvar ga = new GlideAjax('global.getGroupUserIDUtil');\n\tga.addParam('sysparm_name','getUserId');\n\tga.addParam('sysparm_group',assignGroup);\n\tga.getXML(handleXml);\n}\n\nfunction handleXml(response){\n\tvar userIDArr = response.responseXML.documentElement.getAttribute(\"answer\");\n\tvar userLookUp = gel('lookup.assigned_to');\n\tuserLookUp.setAttribute('onclick',\"mousePositionSave(event); reflistOpen( 'assigned_to', 'not', 'sys_user', '', 'false','QUERY:active=false','sys_idIN\" + userIDArr+ \"', '')\");\n}\n"
  },
  {
    "path": "Client-Side Components/UI Pages/UI Page Auto Populate Assigned to based on Assignment group/jelly_script.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n<label>Assignment Group</label>\n<g:ui_reference id=\"assignment_group\" name=\"assignment_group\" table=\"sys_user_group\" query=\"active=true\" coloumns=\"label\" onChange=\"onAssignmentGroupChange()\"/>\n<label>Assigned To</label>\n<g:ui_reference id=\"assigned_to\" name=\"assigned_to\" table=\"sys_user\" query=\"active=true\" />\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Pages/UI Page Auto Populate Assigned to based on Assignment group/script_include.js",
    "content": "var getGroupUserIDUtil = Class.create();\ngetGroupUserIDUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\tgetUserId: function(){\n\t\tvar userIDArr = [];\n\t\tvar getGroup = this.getParameter('sysparm_group');\n\t\tvar getUser = new GlideRecord('sys_user_grmember');\n\t\tgetUser.addEncodedQuery('group='+getGroup);\n\t\tgetUser.query();\n\t\twhile(getUser.next()){\n\t\t\tuserIDArr.push(getUser.getValue('user'));\n\t\t}\n\t\treturn userIDArr.join(',');\n\t},\n    type: 'getGroupUserIDUtil'\n});\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Custom Change Schedule/README.md",
    "content": "# 🧾 ServiceNow Change Schedule Enhancement  \n### _(UI Scripts: `sn_chg_soc.change_soc`, `sn.chg_soc.config`, `sn.chg_soc.data`)_  \n\n---\n\n## 📘 Overview  \n\nThis customization extends the **ServiceNow Change Schedule (Change Calendar)** functionality.  \nThe enhancement adds visibility and interactivity to the Change Calendar by including:  \n\n- A **Short Description** column in the Change Schedule view.  \n- A **configurable UI** allowing users to toggle visibility of columns such as _Configuration Item_, _Short Description_, and _Duration_.  \n- Integration of additional data services for fetching and rendering change records with enhanced details.  \n- A **Change Schedule button** that refreshes and displays these changes dynamically.  \n\nThe result is a more informative and user-friendly Change Schedule interface for Change Managers, CAB members, and ITSM users.  \n\n---\n\n## 🧩 Architecture  \n\n| Module | Description |\n|--------|-------------|\n| **`sn_chg_soc.change_soc`** | Main controller and directive for the Change Schedule Gantt Chart UI. Handles initialisation, rendering, zoom, and popovers. |\n| **`sn.chg_soc.config`** | Manages configuration settings for displayed columns and schedules (blackout, maintenance). Allows toggling visibility. |\n| **`sn.chg_soc.data`** | Provide the data on the gantt chat from the change records\n\n\nRequirement:\nAs an ITIL user, you can click the Change Schedule button to navigate directly to the Change Schedule view.\nThis allows you to see all planned changes and plan your own changes accordingly, especially useful for customers who do not have a well-established CMDB integrated with Discovery.\n\n<img width=\"1505\" height=\"809\" alt=\"image\" src=\"https://github.com/user-attachments/assets/4af1b6cb-87e6-4a53-a243-6592fbf548c1\" />\n\n<img width=\"1913\" height=\"877\" alt=\"image\" src=\"https://github.com/user-attachments/assets/fc0a6f46-febd-45bb-a741-78462fa1512a\" />\n\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Custom Change Schedule/change_soc.js",
    "content": "angular.module(\"sn.chg_soc.change_soc\", [\n        \"ngAria\",\n        \"sn.common\",\n        \"sn.common.glide\",\n        \"sn.angularstrap\",\n        \"sn.chg_soc.accessibility\",\n        \"sn.chg_soc.tooltip_overflow\",\n        \"sn.chg_soc.notification\",\n        \"sn.chg_soc.mousedown\",\n        \"sn.chg_soc.gantt\",\n        \"sn.chg_soc.data\",\n        \"sn.chg_soc.style\",\n        \"sn.chg_soc.config\",\n        \"sn.chg_soc.share\",\n        \"sn.chg_soc.landing_wizard\",\n        \"sn.chg_soc.context_menu\",\n        \"sn.chg_soc.snCreateNewInvite\",\n        \"sn.chg_soc.keyboard\",\n        \"sn.chg_soc.popover\",\n        \"sn.chg_soc.duration\",\n        \"sn.app_itsm.now.filter\",\n        \"sn.chg_soc.filter_control\",\n        \"sn.chg_soc.loading\",\n        \"sn.itsm.change.overflow\"\n    ])\n    .constant(\"SOC\", {\n        BLACKOUT: \"blackout\",\n        BLACKOUT_SPAN_COLOR: \"#BDC0C4\",\n        CHANGE_REQUEST: \"change_request\",\n        DATE_FORMAT: \"%Y-%m-%d %H:%i:%s\",\n        GET_CHANGE_SCHEDULE: \"/api/sn_chg_soc/soc/changeschedule/\",\n        GET_PARSE_QUERY: \"/api/now/ui/query_parse/change_request?sysparm_query=\",\n        ISO_WEEK: \"isoWeek\",\n        MAINT: \"maint\",\n        MAINT_SPAN_COLOR: \"#BDDCFC\",\n        STYLE_PREFIX: \"soc_\",\n        SYSPARM_ID: \"sysparm_id\",\n        ZOOM_LEVEL_PREF: \"sn_chg_soc.change_soc_zoom_level\",\n        COLUMN: {\n            SHORT_DESCRIPTION: \"short_description\",\n            CONFIG_ITEM: \"config_item\",\n            // DURATION: \"duration\",\n            NUMBER: \"number\"\n        },\n        STYLE_CLASS_MAP: {\n            soc_event_bar: \"soc-event-bar\",\n            soc_row_child: \"soc-row-child\",\n            soc_row_child_end: \"soc-row-child-end\",\n            soc_row_child_start: \"soc-row-child-start\",\n            soc_row_child_single: \"soc-row-child-single\"\n        },\n        KEYS: {\n            TABKEY: 9,\n            ENTER: 13,\n            ESCAPE: 27,\n            SPACE: 32,\n            LEFT_ARROW: 37,\n            UP_ARROW: 38,\n            RIGHT_ARROW: 39,\n            DOWN_ARROW: 40,\n            D: 68,\n            E: 69,\n            F: 70,\n            SLASH: 191\n        }\n    })\n    .config([\"$httpProvider\", \"$locationProvider\", function($httpProvider, $locationProvider) {\n        $locationProvider.html5Mode({\n            enabled: true,\n            requireBase: false\n        });\n        $httpProvider.interceptors.push(\"xhrInterceptor\");\n    }])\n    .service(\"urlService\", [\"$location\", \"SOC\", function($location, SOC) {\n        var urlService = this;\n\n        urlService.socId = $location.search()[SOC.SYSPARM_ID];\n\n        urlService.setChangeScheduleId = function() {\n            var params = $location.search();\n            urlService.socId = params[SOC.SYSPARM_ID];\n        };\n    }])\n    .service(\"clientService\", [\"dataService\", function(dataService) {\n        var clientService = this;\n\n        clientService.filter = dataService.definition;\n    }])\n    .directive(\"changeSoc\", [\"urlService\", \"ganttChart\", \"ganttScale\", \"dataService\", \"i18n\", \"getTemplateUrl\", \"$templateRequest\", \"$templateCache\", \"$filter\", \"$compile\", \"$window\", \"SOC\", \"TextSearchService\", \"socNotification\",\n        function(urlService, ganttChart, ganttScale, dataService, i18n, getTemplateUrl, $templateRequest, $templateCache, $filter, $compile, $window, SOC, TextSearchService, socNotification) {\n            return {\n                restrict: \"A\",\n                scope: false,\n                transclude: true,\n                template: \"<div ng-transclude></div>\",\n                link: function($scope, $element, $attrs, changeSoCCtrl) {\n                    var position = {\n                        delta: {\n                            top: 0,\n                            left: 0\n                        },\n                        original: {\n                            top: 0,\n                            left: 0\n                        }\n                    };\n                    $scope.ganttInstance = ganttChart.getInstance(urlService.socId);\n                    $scope.gantt = $scope.ganttInstance.gantt;\n\n                    // destroy all popovers when resizing the window\n                    angular.element(window).on(\"resize\", function() {\n                        angular.element(\".popover.soc-task-popover\").popover(\"destroy\");\n                        _handleDestroyPopover();\n                    });\n\n                    angular.element(window).on(\"keydown\", function($event) {\n                        if ($event.keyCode === SOC.KEYS.ESCAPE)\n                            _handleDestroyPopover();\n                    });\n\n                    angular.element(window).on(\"click\", function($event) {\n                        var target = getTargetElement($event);\n                        if (target === null)\n                            _handleDestroyPopover(); // Clicking outside a gantt task\n                    });\n\n                    //size of gantt\n                    $scope.$watch(function() {\n                        return $element[0].offsetWidth + \".\" + $element[0].offsetHeight;\n                    }, function() {\n                        $scope.gantt.setSizes();\n                    });\n\n                    $scope.$watch(\"dataService.definition.condition.dryRun\", function(newValue, oldValue) {\n                        if (newValue)\n                            angular.element(\".control-left .filter-btn\").addClass(\"dry-run\");\n                        else\n                            angular.element(\".control-left .filter-btn\").removeClass(\"dry-run\");\n                    });\n                    /**\n                     * Marker config\n                     */\n                    $scope.gantt.config.show_markers = true;\n\n                    /**\n                     * Column config\n                     */\n                    var msgSelectRecord = i18n.getMessage(\"Show span start\");\n                    $scope.gantt.config.columns = [{\n                            name: SOC.COLUMN.NUMBER,\n                            label: i18n.getMessage(\"Number\"),\n                            align: \"left\",\n                            tree: true,\n                            width: 160,\n                            min_width: 160,\n                            resize: true,\n                            template: function(content) {\n                                return \"<span>\" + content.number + \"</span>\" +\n                                    \"<span data-toggle='tooltip' data-trigger='hover focus click' data-container='body' data-placement='auto bottom' class='select-task icon-target'\" +\n                                    \"data-original-title='\" + msgSelectRecord + \"'\" +\n                                    \"></span>\";\n                            }\n                        },\n                        {\n                            name: SOC.COLUMN.CONFIG_ITEM,\n                            label: i18n.getMessage(\"Configuration Item\"),\n                            align: \"left\",\n                            width: 220,\n                            min_width: 220,\n                            resize: true,\n                            template: function (content) {\n                                return \"<span data-toggle='tooltip' data-trigger='hover focus click' data-container='body' data-placement='auto bottom' \" +\n                                    \"data-original-title='\" + content[SOC.COLUMN.CONFIG_ITEM] + \"'>\" +\n                                    content[SOC.COLUMN.CONFIG_ITEM] +\n                                    \"</span>\";\n                            }\n                        },\n                        {\n\n                            name: SOC.COLUMN.SHORT_DESCRIPTION,\n                            label: \"Short Description\",\n                            align: \"left\",\n                            tree: true,\n                            width: 160,\n                            min_width: 160,\n                            resize: true,\n                            template: function(content) {\n                                return \"<span>\" + content.short_description + \"</span>\" +\n                                    \"<span data-toggle='tooltip' data-trigger='hover focus click' data-container='body' data-placement='auto bottom' class='select-task icon-target'\" +\n                                    \"data-original-title='\" + msgSelectRecord + \"'\" +\n                                    \"></span>\";\n                            }\n\n                        },\n                        // {\n                        //     name: SOC.COLUMN.DURATION,\n                        //     label: i18n.getMessage(\"Duration\"),\n                        //     align: \"left\",\n                        //     width: 130,\n                        //     min_width: 130,\n                        //     template: function(content) {\n                        //         return content.dur_display;\n                        //     },\n                        //     resize: true\n                        // }\n                    ];\n\n                    /**\n                     * Core Config\n                     */\n                    // internal date time format\n                    $scope.gantt.config.xml_date = SOC.DATE_FORMAT;\n                    // ARIA attributes\n                    $scope.gantt.config.wai_aria_attributes = true;\n                    // Keyboard navigation\n                    $scope.gantt.config.keyboard_navigation = true;\n\n                    /**\n                     * Scrolling\n                     */\n                    // Prevents scrolling gantt on load of data\n                    $scope.gantt.config.initial_scroll = false;\n\n                    $scope.gantt.showTask = function(id) {\n                        var task = this.getTask(id);\n                        var taskSize = this.getTaskPosition(task, task.start, task.end);\n                        var left = Math.max(taskSize.left - this.config.task_scroll_offset, 0);\n                        var ganttVerScrollWidth = angular.element(\".gantt_ver_scroll\").width();\n                        var ganttTaskWidth = angular.element(\".gantt_task\").width() - ganttVerScrollWidth;\n\n                        if (Math.abs(this.getScrollState().x - taskSize.left) < ganttTaskWidth && (taskSize.left + taskSize.width) > this.getScrollState().x)\n                            left = null;\n\n                        var scrollStateTop = this.getScrollState().y;\n                        var scrollStateBottom = scrollStateTop + this._scroll_sizes().y;\n                        var visibleTaskTop = taskSize.top;\n                        var visibleTaskBottom = taskSize.top + this.config.row_height;\n                        var top = null;\n\n                        if (visibleTaskTop < scrollStateTop)\n                            top = visibleTaskTop;\n                        else if (visibleTaskTop > scrollStateTop && (visibleTaskTop < scrollStateBottom && visibleTaskBottom < scrollStateBottom))\n                            top = null;\n                        else if (visibleTaskTop > scrollStateTop && visibleTaskBottom > scrollStateBottom)\n                            top = visibleTaskBottom - scrollStateBottom + scrollStateTop;\n\n                        this.scrollTo(left, top);\n                    };\n\n                    function isPopoverInViewport(el) {\n                        var visibleArea = {\n                            minWidth: angular.element(\".gantt_grid_data\").width() - 15, // 15px considering the arrow can be shifted to the right (still visible)\n                            maxWidth: angular.element(\"body\").width(),\n                            minTop: angular.element(\".gantt_data_area\").offset().top,\n                            maxTop: angular.element(\"body\").height()\n                        };\n                        var currentArea = {\n                            minWidth: el.offset().left,\n                            maxWidth: el.offset().left + el.width(),\n                            minTop: el.offset().top - 15, // 15px considering the arrow\n                            maxTop: el.offset().top + el.height() + 15, // 15px considering the arrow\n                        };\n                        if (currentArea.minWidth > visibleArea.minWidth && currentArea.maxWidth < visibleArea.maxWidth &&\n                            currentArea.minTop > visibleArea.minTop && currentArea.maxTop < visibleArea.maxTop)\n                            return true;\n                        return false;\n                    }\n\n                    function adjustPopover() {\n                        popoverElement = angular.element(\".popover.soc-task-popover\");\n                        if (position.delta.top - angular.element(\".gantt_ver_scroll\").scrollTop() === 0 && position.delta.left - angular.element(\".gantt_hor_scroll\").scrollLeft() === 0)\n                            return;\n                        if (popoverElement.hasClass(\"in\")) {\n                            var newPopoverPosition = {\n                                top: position.original.top + position.delta.top - angular.element(\".gantt_ver_scroll\").scrollTop(),\n                                left: position.original.left + position.delta.left - angular.element(\".gantt_hor_scroll\").scrollLeft()\n                            };\n                            popoverElement.offset(newPopoverPosition);\n                            if (!isPopoverInViewport(popoverElement))\n                                _handleDestroyPopover();\n                        }\n                    }\n\n                    $scope.lastScrollTop = 0;\n                    $scope.loadScrollTop = 0;\n                    $scope.lazyLoading = false;\n                    $scope.gantt.attachEvent(\"onGanttScroll\", function(left, top) {\n                        if ($scope.lazyLoading)\n                            $scope.lastScrollTop = top;\n\n                        if (dataService.count >= $window.NOW.sn_chg_soc.limit)\n                            return;\n\n                        var gridHeight = angular.element(\"div.gantt_ver_scroll\").find(\"div\").height();\n                        var shouldLoad = top > ($scope.loadScrollTop + ((gridHeight - $scope.loadScrollTop) / 4));\n                        adjustPopover();\n                        if (!shouldLoad || $scope.isLoading() || !dataService.more || $scope.lazyLoading || top <= $scope.loadScrollTop || top <= $scope.lastScrollTop)\n                            return;\n\n                        $scope.loadScrollTop = top;\n                        $scope.lazyLoading = true;\n                        dataService.getChanges(urlService.socId).then(function(model) {\n                            if (dataService.count >= $window.NOW.sn_chg_soc.limit)\n                                socNotification.show(\"warning\", i18n.format(i18n.getMessage(\"This schedule has exceeded the event limit. The first {0} events based on your order criteria will be displayed.\"), $window.NOW.sn_chg_soc.limit), 0);\n\n                            // Need to provide the tasks so it can calc min/max\n                            ganttScale.setDateRange(dataService.tasks.data);\n                            ganttScale.configureScale();\n                            $scope.gantt.clearAll();\n                            ganttChart.addNowMarker(urlService.socId);\n                            // these are the created tasks that will be added to the gantt\n                            $scope.gantt.parse(dataService.tasks, \"json\");\n                            $scope.lazyLoading = false;\n                        });\n                    });\n\n                    /**\n                     * Scales\n                     */\n                    // Only visible scale is rendered\n                    $scope.gantt.config.smart_scales = true;\n                    // Removes vertical borders on cells\n                    $scope.gantt.config.show_task_cells = false;\n                    $scope.gantt.config.scale_height = 60;\n                    $scope.gantt.config.row_height = 40;\n                    $scope.gantt.config.duration_unit = \"hour\";\n                    $scope.gantt.config.duration_step = 1;\n                    $scope.gantt.config.scale_unit = \"day\";\n                    $scope.gantt.config.date_scale = \"%j %M %Y\";\n                    $scope.gantt.config.subscales = [{\n                        unit: \"hour\",\n                        step: 1,\n                        date: \"%H:%i\"\n                    }];\n\n                    /**\n                     * UI Components\n                     */\n                    $scope.gantt.config.show_progress = false;\n                    $scope.gantt.config.drag_links = false;\n                    $scope.gantt.config.drag_move = false;\n                    $scope.gantt.config.drag_resize = false;\n\n                    /**\n                     * Templates\n                     */\n                    // Configure use of icons in the gantt rows\n                    $scope.gantt.templates.grid_open = function(item) {\n                        return \"<div class='gantt_tree_icon gantt_\" + (item.$open ? \"close\" : \"open\") + \" icon-vcr-\" + (item.$open ? \"down\" : \"right\") + \"'></div>\";\n                    };\n                    $scope.gantt.templates.grid_folder = function(item) {\n                        return \"\";\n                    };\n                    $scope.gantt.templates.grid_file = function(item) {\n                        return \"\";\n                    };\n                    $scope.gantt.templates.grid_indent = function(item) {\n                        return \"\";\n                    };\n                    $scope.gantt.templates.grid_row_class = function(start, end, task) {\n                        return \"\";\n                    };\n                    $scope.gantt.templates.task_row_class = function(start, end, task) {\n                        return \"\";\n                    };\n                    $scope.gantt.templates.task_class = function(start, end, task) {\n                        return SOC.STYLE_CLASS_MAP.soc_event_bar;\n                    };\n                    $scope.gantt.templates.task_text = function(start, end, task) {\n                        return \"\";\n                    };\n\n                    function getNode(node) {\n                        if (node.hasClass(\"gantt_row\"))\n                            return angular.element(node.children(\".gantt_cell\")[0]);\n                        if (!node.hasClass(\"gantt_cell\") || node.hasClass(\"gantt_task_content\") || node.hasClass(\"gantt_task_drag\"))\n                            node = node.parent();\n                        return node;\n                    }\n\n                    function getTargetElement($event) {\n                        var node = angular.element($event.target || $event.srcElement);\n                        if ($event.type === \"keydown\")\n                            return angular.element($event.target);\n                        node = getNode(node);\n                        if (node.hasClass(\"gantt_task_line\"))\n                            return node;\n                        return null;\n                    }\n\n                    function handleOpenRecord() {\n                        var task = $filter(\"filter\")(dataService.tasks.data, {\n                            id: this.targetId\n                        })[0];\n                        $window.location.href = task.table + \".do?&sys_id=\" + task.sys_id +\n                            \"&sysparm_redirect=\" + encodeURIComponent(\"sn_chg_soc_change_soc.do?sysparm_id=\" + urlService.socId);\n                    }\n\n                    function _handleDestroyPopover() {\n                        if (angular.element(\"[soc-popover]\").length === 0)\n                            return \"\";\n                        angular.element(\"[soc-popover]\").focus();\n                        angular.element(\"[soc-popover]\").attr(\"aria-expanded\", \"false\");\n                        angular.element(\"[soc-popover]\").removeAttr(\"soc-popover\");\n                        angular.element(\".popover.soc-task-popover\").popover(\"destroy\");\n                    }\n\n                    function _handleDestroyFlyout() {\n                        $scope.$broadcast(\"sn.aside.change_soc_side.close\");\n                    }\n\n                    function getTargetSelector($event, taskObj) {\n                        if ($event.type !== \"keydown\") {\n                            var selector = \".gantt_grid_data .\" + $event.target.className;\n                            var result = angular.element(selector);\n                            var targetClass = (result.length > 0) ? \".gantt_grid\" : \".gantt_task\";\n                            return (result.length > 0) ? targetClass + \" [task_id='\" + taskObj.id + \"'] .gantt_cell:first\" : targetClass + \" .gantt_task_line[task_id='\" + taskObj.id + \"']\";\n                        } else\n                            return \".gantt_grid [task_id='\" + taskObj.id + \"'] .gantt_cell:first\";\n                    }\n\n                    function getX(target) {\n                        var result = {\n                            \"start\": 0,\n                            \"end\": 0\n                        };\n                        var targetElement = {\n                            \"start\": angular.element(target).offset().left,\n                            \"end\": angular.element(target).offset().left + angular.element(target).width()\n                        };\n                        var visibleArea = angular.element(\".gantt_task\");\n                        var visibleAreaLimits = {\n                            \"start\": visibleArea.offset().left,\n                            \"end\": visibleArea.offset().left + visibleArea.width()\n                        };\n                        result.start = (targetElement.start > visibleAreaLimits.start) ? targetElement.start : visibleAreaLimits.start;\n                        result.end = (targetElement.end < visibleAreaLimits.end) ? targetElement.end : visibleAreaLimits.end;\n                        return result.start + (result.end - result.start) / 2;\n                    }\n\n                    // Callback function used for building the popover template\n                    function buildPopoverTemplate(taskObj, $event, popoverContent, popoverTemplate) {\n                        var $popoverScope = $scope.$new(true);\n                        $popoverScope.openRecord = i18n.getMessage(\"Open Record\");\n                        $popoverScope.handleOpenRecord = handleOpenRecord;\n                        var targetSelector = getTargetSelector($event, taskObj);\n                        var target = angular.element(targetSelector);\n                        $popoverScope.targetId = taskObj.id;\n                        popoverTemplate = $compile(popoverTemplate)($popoverScope);\n                        target.attr(\"tabindex\", \"0\");\n                        target.attr(\"aria-expanded\", \"true\");\n                        target.attr(\"soc-popover\", \"opened\");\n                        var options = {\n                            \"container\": \"body\",\n                            \"viewport\": {\n                                \"selector\": \"body\",\n                                \"padding\": 20\n                            },\n                            \"html\": true,\n                            \"trigger\": \"manual\",\n                            \"placement\": \"auto\",\n                            \"title\": taskObj.number + \" - \" + (taskObj.record.short_description ? taskObj.record.short_description.display_value : \"\"),\n                            \"content\": popoverContent,\n                            \"template\": popoverTemplate\n                        };\n                        target.popover(options);\n                        if (targetSelector.indexOf(\"gantt_task\") !== -1) {\n                            target.data(\"bs.popover\").options.atMouse = $event.pageX !== 0;\n                            target.data(\"bs.popover\").options.mousePos = {\n                                \"x\": getX(target),\n                                \"y\": $event.pageY\n                            };\n                        }\n                        var action = angular.element(\".popover.soc-task-popover\").hasClass(\"in\") ? \"hidden\" : \"shown\";\n                        target.on(action + \".bs.popover\", function($ev) {\n                            if ($ev.type === \"shown\") {\n                                _handleDestroyFlyout();\n                                angular.element(\".soc-btn-open-record\").focus();\n                                position.delta = {\n                                    top: angular.element(\".gantt_ver_scroll\").scrollTop(),\n                                    left: angular.element(\".gantt_hor_scroll\").scrollLeft()\n                                };\n                                position.original = {\n                                    top: angular.element(\".popover.soc-task-popover\").offset().top,\n                                    left: angular.element(\".popover.soc-task-popover\").offset().left\n                                };\n                                // Amend popover height if it is taller than remaining part of the window\n                                var popoverElement = angular.element(\".soc-task-popover\");\n                                var windowHeight = angular.element(window).height();\n                                var maxHeight = windowHeight - popoverElement.offset().top;\n                                if (popoverElement.height() > maxHeight)\n                                    popoverElement.height(maxHeight + \"px\");\n                            } else\n                                _handleDestroyPopover();\n                        });\n                        target.popover(\"toggle\");\n                    }\n\n                    function getTooltipTextToDisplay() {\n\n                    }\n\n                    // Callback function used for building the popover content\n                    function buildPopoverContent(taskObj, $event, popoverContent) {\n                        $templateCache.remove(getTemplateUrl(\"sn_chg_soc_change_soc_popover_template.xml\"));\n                        var $popoverContentScope = $scope.$new(true);\n                        $popoverContentScope.leftFields = taskObj.left_fields;\n                        $popoverContentScope.rightFields = taskObj.right_fields;\n                        $popoverContentScope.emptyValue = \"[\" + i18n.getMessage(\"Empty\") + \"]\";\n                        popoverContent = $compile(popoverContent)($popoverContentScope);\n                        $templateRequest(getTemplateUrl(\"sn_chg_soc_change_soc_popover_template.xml\")).then(buildPopoverTemplate.bind(this, taskObj, $event, popoverContent));\n                    }\n\n                    function openPopover(id, $event) {\n                        var targetElement = getTargetElement($event);\n                        var openedPopover = angular.element(\"[soc-popover]\");\n                        if (targetElement === null || openedPopover.length > 0) {\n                            _handleDestroyPopover();\n                            if (targetElement === null || openedPopover.attr(\"task_id\") === id)\n                                return;\n                        }\n                        _handleDestroyFlyout();\n                        $event.stopPropagation();\n                        var taskObj = $filter(\"filter\")(dataService.tasks.data, {\n                            \"id\": id\n                        }, true)[0];\n                        $templateRequest(getTemplateUrl(\"sn_chg_soc_change_soc_task_popover.xml\")).then(buildPopoverContent.bind(this, taskObj, $event));\n                    }\n\n                    /**\n                     * Events\n                     **/\n                    $scope.gantt.attachEvent(\"onTaskClick\", function(id, $event) {\n                        openPopover(id, $event);\n                        return true;\n                    });\n\n                    $scope.gantt.attachEvent(\"onTaskDblClick\", function(id, e) {\n                        return false;\n                    });\n\n                    $scope.gantt.addShortcut(\"enter\", function($event) {\n                        openPopover(this.taskId, $event);\n                    }, \"taskRow\");\n\n                    $scope.gantt.addShortcut(\"tab\", function($event) {}, \"taskRow\");\n\n                    $scope.gantt.attachEvent(\"onTaskSelected\", function(id, item) {\n                        return true;\n                    });\n\n                    $scope.gantt.attachEvent(\"onBeforeTaskSelected\", function(id, item) {\n                        return true;\n                    });\n\n                    function getScheduleEvent(task, startDate, endDate, styleClass) {\n                        startDate = $scope.gantt.date.parseDate(startDate, \"xml_date\");\n                        endDate = $scope.gantt.date.parseDate(endDate, \"xml_date\");\n                        var sizes = $scope.gantt.getTaskPosition(task, startDate, endDate);\n                        var el = document.createElement(\"div\");\n                        el.className = \"schedule-bar \" + styleClass;\n                        el.style.left = sizes.left + \"px\";\n                        el.style.width = sizes.width + \"px\";\n                        el.style.top = sizes.top + \"px\";\n                        return el;\n                    }\n\n                    // Add task layer for blackout windows\n                    $scope.ganttInstance.addTaskLayer(function(task) {\n                        if (task.blackout_spans.length === 0 && task.maint_spans.length === 0)\n                            return;\n                        var wrapper = document.createElement(\"div\");\n                        if (dataService.definition.show_maintenance.value)\n                            task.maint_spans.forEach(function(maintSpan) {\n                                wrapper.appendChild(getScheduleEvent(task, maintSpan.start, maintSpan.end, \"maint\"));\n                            });\n                        if (dataService.definition.show_blackout.value)\n                            task.blackout_spans.forEach(function(blackoutSpan) {\n                                wrapper.appendChild(getScheduleEvent(task, blackoutSpan.start, blackoutSpan.end, \"blackout\"));\n                            });\n                        return wrapper;\n                    });\n\n                    $scope.gantt.attachEvent(\"onGanttRender\", function() {\n                        $element.find(\".gantt_container\").attr(\"role\", \"grid\");\n                        angular.element('[data-toggle=\"tooltip\"]').tooltip('destroy');\n                        angular.element(\".tooltip[id^='tooltip']\").remove();\n                        $element.find('[data-toggle=\"tooltip\"]').tooltip();\n                    });\n\n                    // Locale information must be associated with gantt object attached to window\n                    $window.gantt.locale = {\n                        date: {\n                            month_full: [i18n.getMessage(\"January\"),\n                                i18n.getMessage(\"February\"),\n                                i18n.getMessage(\"March\"),\n                                i18n.getMessage(\"April\"),\n                                i18n.getMessage(\"May\"),\n                                i18n.getMessage(\"June\"),\n                                i18n.getMessage(\"July\"),\n                                i18n.getMessage(\"August\"),\n                                i18n.getMessage(\"September\"),\n                                i18n.getMessage(\"October\"),\n                                i18n.getMessage(\"November\"),\n                                i18n.getMessage(\"December\")\n                            ],\n                            month_short: [i18n.getMessage(\"Jan\"),\n                                i18n.getMessage(\"Feb\"),\n                                i18n.getMessage(\"Mar\"),\n                                i18n.getMessage(\"Apr\"),\n                                i18n.getMessage(\"May\"),\n                                i18n.getMessage(\"Jun\"),\n                                i18n.getMessage(\"Jul\"),\n                                i18n.getMessage(\"Aug\"),\n                                i18n.getMessage(\"Sep\"),\n                                i18n.getMessage(\"Oct\"),\n                                i18n.getMessage(\"Nov\"),\n                                i18n.getMessage(\"Dec\")\n                            ],\n                            day_full: [i18n.getMessage(\"Sunday\"),\n                                i18n.getMessage(\"Monday\"),\n                                i18n.getMessage(\"Tuesday\"),\n                                i18n.getMessage(\"Wednesday\"),\n                                i18n.getMessage(\"Thursday\"),\n                                i18n.getMessage(\"Friday\"),\n                                i18n.getMessage(\"Saturday\")\n                            ],\n                            day_short: [i18n.getMessage(\"Sun\"),\n                                i18n.getMessage(\"Mon\"),\n                                i18n.getMessage(\"Tue\"),\n                                i18n.getMessage(\"Wed\"),\n                                i18n.getMessage(\"Thu\"),\n                                i18n.getMessage(\"Fri\"),\n                                i18n.getMessage(\"Sat\")\n                            ]\n                        },\n                        labels: {}\n                    };\n\n                    $scope.zoomIn = function() {\n                        _handleDestroyPopover();\n                        ganttScale.zoom(++$scope.ganttScale.level, urlService.socId);\n                    };\n\n                    $scope.zoomOut = function() {\n                        _handleDestroyPopover();\n                        ganttScale.zoom(--$scope.ganttScale.level, urlService.socId);\n                    };\n\n                    $scope.gantt.init($element[0]);\n                }\n            };\n        }\n    ])\n    .controller(\"ChangeSoCCtrl\", [\"$scope\", \"$document\", \"$timeout\", \"$window\", \"$location\", \"ganttChart\", \"styleService\", \"configService\", \"shareService\", \"dataService\", \"urlService\", \"ganttScale\", \"getTemplateUrl\", \"i18n\", \"SOC\", \"TextSearchService\", \"socNotification\",\n        function($scope, $document, $timeout, $window, $location, ganttChart, styleService, configService, shareService, dataService, urlService, ganttScale, getTemplateUrl, i18n, SOC, TextSearchService, socNotification) {\n            var changeSoCCtrl = this;\n\n            changeSoCCtrl.share = {\n                canWrite: false\n            };\n\n            changeSoCCtrl.closeFlyout = function() {\n                $scope.$apply(function() {\n                    $scope.$broadcast(\"sn.aside.change_soc_side.close\");\n                });\n            };\n\n            $scope.loadingElements = {};\n            $scope.dataService = dataService;\n            $scope.ganttScale = ganttScale;\n            $scope.urlService = urlService;\n\n            $scope.pageLeft = function($event) {\n                if ($event && $event.keyCode !== SOC.KEYS.ENTER && $event.keyCode !== SOC.KEYS.SPACE)\n                    return;\n                var gantt = ganttChart.getGantt(urlService.socId);\n                var left = gantt.getScrollState().x - angular.element(\"div.gantt_scale_cell\").width();\n                gantt.scrollTo(left < 0 ? 0 : left, null);\n            };\n\n            $scope.pageRight = function($event) {\n                if ($event && $event.keyCode !== SOC.KEYS.ENTER && $event.keyCode !== SOC.KEYS.SPACE)\n                    return;\n                var gantt = ganttChart.getGantt(urlService.socId);\n                var left = gantt.getScrollState().x + angular.element(\"div.gantt_scale_cell\").width();\n                var scrollLength = angular.element(\"div.gantt_hor_scroll > div\").width();\n                gantt.scrollTo(left > scrollLength ? scrollLength : left, null);\n            };\n\n            $scope.scrollToday = function() {\n                var gantt = ganttChart.getGantt(urlService.socId);\n                gantt.showDate(new Date());\n            };\n\n            $scope.openView = function(viewId, event) {\n                // We already have something open, need to deal with that first\n                if ($scope.activeAside === viewId) {\n                    $scope.$broadcast(\"sn.aside.change_soc_side.close\");\n                    if (event)\n                        event.target.blur();\n                } else {\n                    var view;\n                    switch (viewId) {\n                        case \"share\":\n                            view = getView(viewId, \"sn_chg_soc_aside_share.xml\");\n                            break;\n                        case \"style\":\n                            view = getView(viewId, \"sn_chg_soc_aside_style.xml\");\n                            break;\n                        case \"style_def\":\n                            view = getView(viewId, \"sn_chg_soc_aside_style_page.xml\", true);\n                            break;\n                        case \"config\":\n                            view = getView(viewId, \"sn_chg_soc_aside_config.xml\");\n                            break;\n                        case \"keyboard\":\n                            view = getView(viewId, \"sn_chg_soc_aside_keyboard.xml\");\n                            break;\n                    }\n                    if (view !== undefined) {\n                        angular.element(\".sn-aside_right\").show();\n                        $scope.activeAside = viewId;\n                        $scope.$broadcast(\"sn.aside.change_soc_side.open\", view, \"320px\");\n                    }\n                }\n            };\n\n            $scope.$on(\"sn.aside.change_soc_side.close\", function() {\n                switch ($scope.activeAside) {\n                    case \"share\":\n                        angular.element(\"#share_side\").focus();\n                        break;\n                    case \"style\":\n                        angular.element(\"#style_side\").focus();\n                        break;\n                    case \"style_def\":\n                        angular.element(\"#style_side\").focus();\n                        break;\n                    case \"config\":\n                        angular.element(\"#config_side\").focus();\n                        break;\n                    case \"keyboard\":\n                        angular.element(\"#keyboard_side\").focus();\n                        break;\n                }\n                $scope.activeAside = \"\";\n                angular.element(\".sn-aside_right\").hide();\n            });\n\n            $scope.$on(\"sn.aside.change_soc_side.open_style\", function(event, style) {\n                styleService.selectedStyle = style;\n                $scope.openView(\"style_def\");\n            });\n\n            $scope.$on(\"sn.aside.change_soc_side.style_updated\", function(event, result) {\n                if (result.style_sheet) {\n                    var socStyleSheet = $document[0].getElementById(\"soc_span_style\");\n                    socStyleSheet.innerHTML = result.style_sheet;\n                }\n\n                if (result.records) {\n                    var gantt = ganttChart.getGantt(urlService.socId);\n                    for (var i = 0; i < dataService.tasks.data.length; i++)\n                        if (result.records[dataService.tasks.data[i].id].style_rule)\n                            dataService.tasks.data[i].style_class = SOC.STYLE_PREFIX + result.records[dataService.tasks.data[i].id].style_rule.sys_id;\n                    gantt.parse(dataService.tasks, \"json\");\n                }\n            });\n\n            $scope.$on(\"sn.aside.change_soc_side.open_share\", function(event, model) {\n                shareService.model = model;\n                $scope.openView(\"share\");\n            });\n\n            $scope.$on(\"sn.aside.change_soc_side.open_share:closed\", function(event, model) {\n                $scope.openView(\"share\");\n            });\n\n            $scope.$on(\"sn.aside.change_soc_side.historyBack.completed\", function(e, view) {\n                $scope.activeAside = view.title;\n            });\n\n            function getView(name, template, isChild) {\n                return {\n                    scope: $scope,\n                    title: name,\n                    templateUrl: getTemplateUrl(template),\n                    isChild: isChild\n                };\n            }\n\n            // Global keyboard shortcuts\n            $document.on(\"keydown\", function(event) {\n                // Open keyboard help side\n                if (event.shiftKey && event.which == SOC.KEYS.SLASH && event.originalEvent.target.tagName !== \"INPUT\") {\n                    $scope.$apply(function() {\n                        if ($scope.activeAside === \"keyboard\") {\n                            $scope.$broadcast(\"sn.aside.change_soc_side.close\");\n                            if (event)\n                                event.target.blur();\n                        } else {\n                            $scope.activeAside = \"keyboard\";\n                            $scope.$broadcast(\"sn.aside.change_soc_side.open\", getView(\"keyboard\", \"sn_chg_soc_aside_keyboard.xml\"), \"320px\");\n                        }\n                    });\n                }\n            });\n\n            var getChildTaskDividerClass = function(start, end, task) {\n                if (!task.parent)\n                    return \"\";\n\n                var classStyle = \" \" + SOC.STYLE_CLASS_MAP.soc_row_child;\n\n                var nextTask = this.ganttChart.getNext(task.id);\n                nextTask = nextTask ? this.ganttChart.getTask(nextTask) : null;\n                var previousTask = this.ganttChart.getPrev(task.id);\n                previousTask = previousTask ? this.ganttChart.getTask(previousTask) : null;\n\n                // Only child task for a parent\n                if (previousTask && !previousTask.parent)\n                    if (!nextTask || (nextTask && !nextTask.parent))\n                        return classStyle += \" \" + SOC.STYLE_CLASS_MAP.soc_row_child_single;\n\n                // First child task for their parent\n                if (previousTask && !previousTask.parent && (nextTask && nextTask.parent))\n                    return classStyle += \" \" + SOC.STYLE_CLASS_MAP.soc_row_child_start;\n\n                // Last child task for their parent\n                if (previousTask && previousTask.parent && ((nextTask && !nextTask.parent) || !nextTask))\n                    return classStyle += \" \" + SOC.STYLE_CLASS_MAP.soc_row_child_end;\n\n                return classStyle;\n            };\n\n            var defineClassTemplate = function(start, end, task) {\n                var classStyle = \"\";\n\n                if (this.type)\n                    classStyle += SOC.STYLE_CLASS_MAP[this.type];\n\n                classStyle += getChildTaskDividerClass.call({\n                    ganttChart: this.ganttChart\n                }, null, null, task);\n\n                if (task.style_class)\n                    classStyle += \" \" + task.style_class;\n\n                return classStyle;\n            };\n\n            var dateToStr = gantt.date.date_to_str(gantt.config.task_date);\n\n            function updateMarkerInterval(gantt, markerId) {\n                var today = gantt.getMarker(markerId);\n                today.start_date = new Date();\n                today.title = dateToStr(today.start_date);\n                gantt.updateMarker(markerId);\n            }\n\n            function addNowMarker(gantt) {\n                var markerId = gantt.addMarker({\n                    start_date: new Date(),\n                    css: \"today-marker\",\n                    title: dateToStr(new Date()),\n                    text: \" \"\n                });\n                setInterval(updateMarkerInterval(gantt, markerId), 1000 * 60);\n            }\n\n            function addScheduleSpanStyle(definition) {\n                var socStyleSheet = $document[0].createElement(\"style\");\n                socStyleSheet.id = \"soc_schedule_style\";\n                $document[0].head.appendChild(socStyleSheet);\n\n                var maintColor = definition.maintenance_span_color.value ? definition.maintenance_span_color.value : SOC.MAINT_SPAN_COLOR;\n                var blackoutColor = definition.blackout_span_color.value ? definition.blackout_span_color.value : SOC.BLACKOUT_SPAN_COLOR;\n\n                var spanStyleSheet;\n                for (var i = 0; i < $document[0].styleSheets.length; i++)\n                    if ($document[0].styleSheets[i].ownerNode.id === socStyleSheet.id) {\n                        spanStyleSheet = $document[0].styleSheets[i];\n                        break;\n                    }\n\n                if (!spanStyleSheet)\n                    return;\n\n                spanStyleSheet.insertRule(\"div.schedule-bar.maint {background-color: \" + maintColor + \";}\", 0);\n                spanStyleSheet.insertRule(\"div.schedule-bar.blackout {background-color: \" + blackoutColor + \";}\", 0);\n            }\n\n            changeSoCCtrl.initPage = function() {\n                dataService.initPage(urlService.socId).then(function() {\n                    styleService.initStyle();\n\n                    // Setup for share panel\n                    changeSoCCtrl.share.canWrite = dataService.canWrite();\n\n                    // Setup configuration panel\n                    configService.showBlackoutOption = configService.showBlackoutSchedules = dataService.definition.show_blackout.value;\n                    configService.showMaintOption = configService.showMaintSchedules = dataService.definition.show_maintenance.value;\n                    configService.setChildRecords(dataService.child_table);\n\n                    // Need to apply changes due to style info\n                    var socStyleSheet = document.createElement(\"style\");\n                    socStyleSheet.id = \"soc_span_style\";\n                    document.head.appendChild(socStyleSheet);\n                    socStyleSheet.innerHTML = dataService.style.style_sheet;\n\n                    addScheduleSpanStyle(dataService.definition);\n\n                    var gantt = ganttChart.getGantt(urlService.socId);\n                    gantt.templates.grid_row_class = defineClassTemplate.bind({\n                        ganttChart: gantt,\n                        type: \"\",\n                    });\n                    gantt.templates.task_row_class = getChildTaskDividerClass.bind({\n                        ganttChart: gantt\n                    });\n                    gantt.templates.task_class = defineClassTemplate.bind({\n                        ganttChart: gantt,\n                        type: \"soc_event_bar\",\n                    });\n                    gantt.render();\n\n                    // Need to provide the tasks so it can calc min/max\n                    ganttScale.setDateRange(dataService.tasks.data);\n                    ganttScale.configureScale();\n                    gantt.clearAll();\n                    addNowMarker(gantt);\n                    // these are the created tasks that will be added to the gantt\n                    gantt.parse(dataService.tasks, \"json\");\n                    if (dataService.tasks.data.length > 0) {\n                        gantt.showTask(dataService.tasks.data[0].id);\n                        $scope.noResults = false;\n                    } else\n                        $scope.noResults = true;\n                }).catch(function(response) {\n                    socNotification.show(\"error\", response.data.error.message);\n                });\n            };\n\n            $scope.filter = {\n                filterConditions: [\"number\", \"config_item\", \"Short Description\", \"children.number\", \"children.config_item\"],\n                placeholderText: i18n.getMessage(\"Search Change Schedule\")\n            };\n\n            function buildFilterData() {\n                var augmentedData = dataService.tasks.data;\n                dataService.tasks.data.forEach(function(obj, index) {\n                    augmentedData[index].children = dataService.getChildren(obj.id);\n                });\n                return augmentedData;\n            }\n\n            $scope.$on(\"textSearch\", function(event, textSearch) {\n                var filteredRecords = TextSearchService.getFilteredArray(buildFilterData(), textSearch);\n                ganttChart.updateGanttData(urlService.socId, filteredRecords);\n                $scope.noResults = filteredRecords.length === 0;\n            });\n\n            $scope.isLoading = function() {\n                return $scope.$parent.loading;\n            };\n\n            changeSoCCtrl.messages = {\n                \"No records to display\": i18n.getMessage(\"No records to display\"),\n                \"No records match the filter\": i18n.getMessage(\"No records match the filter\"),\n                \"Change Schedule\": i18n.getMessage(\"Change Schedule\"),\n                \"Close panel\": i18n.getMessage(\"Close panel\"),\n                \"Configuration\": i18n.getMessage(\"Configuration\"),\n                \"Share\": i18n.getMessage(\"Share\"),\n                \"Open context menu\": i18n.getMessage(\"Open context menu\"),\n                \"Filter\": i18n.getMessage(\"Filter\"),\n                \"Keyboard Shortcuts\": i18n.getMessage(\"Keyboard Shortcuts\"),\n                \"Search Change Schedule\": i18n.getMessage(\"Search Change Schedule\"),\n                \"Span Styles\": i18n.getMessage(\"Span Styles\"),\n                \"Today\": i18n.getMessage(\"Today\"),\n                \"Zoom in\": i18n.getMessage(\"Zoom in\"),\n                \"Zoom out\": i18n.getMessage(\"Zoom out\"),\n                \"Page left\": i18n.getMessage(\"Page left\"),\n                \"Page right\": i18n.getMessage(\"Page right\"),\n                \"Show today\": i18n.getMessage(\"Show today\")\n            };\n\n            $scope.noResults = false;\n            var noResultsElem = \"<div class='no-results' aria-live='polite' aria-label='\" + changeSoCCtrl.messages[\"No records to display\"] + \"'>\" + changeSoCCtrl.messages[\"No records to display\"] + \"</div>\";\n\n            function noResults(newValue, oldValue) {\n                if (newValue === oldValue)\n                    return;\n                if (newValue) {\n                    angular.element(\"div.gantt_marker.today-marker\").hide();\n                    angular.element(\"div.gantt_task_scale\").after(noResultsElem);\n                } else {\n                    angular.element(\"div.gantt_marker.today-marker\").show();\n                    angular.element(\"div.no-results\").remove();\n                }\n            }\n            $scope.$watch(\"noResults\", noResults);\n        }\n    ])\n    .filter(\"objectKeys\", [function() {\n        return function(anObject) {\n            if (!anObject)\n                return null;\n            return Object.keys(anObject);\n        };\n    }])\n    .filter(\"objectKeysLength\", [function() {\n        return function(anObject) {\n            if (!anObject)\n                return null;\n            return Object.keys(anObject).length;\n        };\n    }]);\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Custom Change Schedule/config.js",
    "content": "angular.module(\"sn.chg_soc.config\", [\"sn.common\"])\n    .service(\"configService\", [\"dataService\", \"ganttChart\", \"urlService\", \"SOC\", function(dataService, ganttChart, urlService, SOC) {\n        var configService = this;\n\n        configService.showConfigItem = true;\n        configService.showDuration = true;\n\t\tconfigService.showShortDesc=true; \n        configService.showBlackoutOption = true;\n        configService.showBlackoutSchedules = true;\n        configService.showMaintOption = true;\n        configService.showMaintSchedules = true;\n\n        configService.childRecords = {};\n\n        configService.setChildRecords = function(childTables) {\n            for (var tableName in childTables)\n                configService.childRecords[tableName] = {\n                    inputId: tableName + \"Option\",\n                    label: childTables[tableName].__label,\n                    name: tableName + \"Show\",\n                    show: true,\n                    change: updateChildRecords\n                };\n        };\n\n        function updateChildRecords(tableName) {\n            var gantt = ganttChart.getGantt(urlService.socId);\n            var ganttTasks = gantt.getTaskByTime();\n            for (var i = 0; i < ganttTasks.length; i++)\n                if (ganttTasks[i].parent && ganttTasks[i].table === tableName)\n                    ganttTasks[i].__visible = configService.childRecords[tableName].show;\n            gantt.attachEvent(\"onBeforeTaskDisplay\", function(id, task) {\n                if (task.parent)\n                    return task.__visible;\n                return true;\n            });\n            gantt.templates.grid_open = gridOpen;\n            gantt.render();\n        }\n\n        function gridOpen(task) {\n            var gantt = ganttChart.getGantt(urlService.socId);\n            var children = gantt.getChildren(task.id);\n\n            for (var i = 0; i < children.length; i++) {\n                var childTask = gantt.getTask(children[i]);\n                if (childTask.__visible)\n                    return \"<div class='gantt_tree_icon gantt_\" + (task.$open ? \"close\" : \"open\") +\n                        \" icon-vcr-\" + (task.$open ? \"down\" : \"right\") + \"'></div>\";\n            }\n\n            return \"<div class='gantt_tree_icon gantt_blank'></div>\";\n        }\n    }])\n    .directive(\"socAsideConfig\", [\"getTemplateUrl\", \"configService\", \"ganttChart\", \"dataService\", \"objectKeysLengthFilter\", \"SOC\", \"i18n\", function(getTemplateUrl, configService, ganttChart, dataService, objectKeysLengthFilter, SOC, i18n) {\n        \"use strict\";\n        return {\n            restrict: \"A\",\n            templateUrl: getTemplateUrl(\"sn_chg_soc_aside_config_body.xml\"),\n            scope: {\n                socDefId: \"=\"\n            },\n            controller: function($scope, objectKeysLengthFilter) {\n               // $scope.showConfigItem = configService.showConfigItem;\n              //  $scope.showDuration = configService.showDuration;\n                $scope.showBlackoutOption = configService.showBlackoutOption;\n                $scope.showShortDesc = configService.showShortDesc;\n                $scope.showBlackoutSchedules = configService.showBlackoutSchedules;\n                $scope.showMaintOption = configService.showMaintOption;\n                $scope.showMaintSchedules = configService.showMaintSchedules;\n                $scope.childRecords = configService.childRecords;\n                $scope.objectKeysLengthFilter = objectKeysLengthFilter;\n                $scope.messages = {\n                    \"Child Records\": i18n.getMessage(\"Child Records\"),\n                    \"Columns\": i18n.getMessage(\"Columns\"),\n                    \"Configuration Item\": i18n.getMessage(\"Configuration Item\"),\n                    \"Short Description\": i18n.getMessage(\"Short Description\"),\n                    \"Duration\": i18n.getMessage(\"Duration\"),\n                    \"Related Records\": i18n.getMessage(\"Related Records\"),\n                    \"Schedules\": i18n.getMessage(\"Schedules\"),\n                    \"Blackout\": i18n.getMessage(\"Blackout\"),\n                    \"Maintenance\": i18n.getMessage(\"Maintenance\")\n                };\n\n                $scope.updateColumnLayout = function(columnId) {\n                    var gantt = ganttChart.getGantt($scope.socDefId);\n                    var column = gantt.getGridColumn(columnId);\n                    if (SOC.COLUMN.CONFIG_ITEM === columnId) {\n                        configService.showConfigItem = !configService.showConfigItem;\n                        column.hide = !configService.showConfigItem;\n                    } else if (SOC.COLUMN.DURATION === columnId) {\n                        configService.showDuration = !configService.showDuration;\n                        column.hide = !configService.showDuration;\n                    } else if (SOC.COLUMN.SHORT_DESCRIPTION === columnId) {\n                        configService.showShortDesc = !configService.showShortDesc;\n\t\t\t\t\t\tcolumn.hide = !configService.showShortDesc;\n                    } else\n                        return;\n                    gantt.render();\n                };\n\n                function getScheduleEvent(task, startDate, endDate, styleClass) {\n                    var gantt = ganttChart.getGantt($scope.socDefId);\n                    startDate = gantt.date.parseDate(startDate, \"xml_date\");\n                    endDate = gantt.date.parseDate(endDate, \"xml_date\");\n                    var sizes = gantt.getTaskPosition(task, startDate, endDate);\n                    var el = document.createElement(\"div\");\n                    el.className = \"schedule-bar \" + styleClass;\n                    el.style.left = sizes.left + \"px\";\n                    el.style.width = sizes.width + \"px\";\n                    el.style.top = sizes.top + \"px\";\n                    return el;\n                }\n\n                var scheduleTaskLayer = function(task) {\n                    if ((!this.show_blackout && !this.show_maint) || (task.blackout_spans.length === 0 && task.maint_spans.length === 0))\n                        return;\n                    var wrapper = document.createElement(\"div\");\n                    if (this.show_blackout && dataService.definition.show_blackout.value)\n                        task.blackout_spans.forEach(function(blackoutSpan) {\n                            wrapper.appendChild(getScheduleEvent(task, blackoutSpan.start, blackoutSpan.end, \"blackout\"));\n                        });\n                    if (this.show_maint && dataService.definition.show_maintenance.value)\n                        task.maint_spans.forEach(function(maintSpan) {\n                            wrapper.appendChild(getScheduleEvent(task, maintSpan.start, maintSpan.end, \"maint\"));\n                        });\n                    return wrapper;\n                };\n\n                $scope.updateScheduleLayer = function() {\n                    configService.showBlackoutSchedules = $scope.showBlackoutSchedules;\n                    configService.showMaintSchedules = $scope.showMaintSchedules;\n                    var ganttInstance = ganttChart.getInstance($scope.socDefId);\n                    ganttInstance.removeTaskLayer();\n\n                    if ($scope.showBlackoutSchedules || $scope.showMaintSchedules) {\n                        ganttInstance.addTaskLayer(scheduleTaskLayer.bind({\n                            show_blackout: $scope.showBlackoutSchedules,\n                            show_maint: $scope.showMaintSchedules\n                        }));\n                        ganttInstance.gantt.render();\n                    }\n                };\n            }\n        };\n    }]);\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Custom Change Schedule/data.js",
    "content": "angular.module(\"sn.chg_soc.data\", [])\n\t.service(\"dataService\", [\"$http\", \"$q\", \"$window\", \"i18n\", \"urlService\", \"ganttChart\", \"durationFormatter\", \"SOC\", \"$filter\", function($http, $q, $window, i18n, urlService, ganttChart, durationFormatter, SOC, $filter) {\n\t\tvar dataService = this;\n\n\t\tdataService.more = false;\n\t\tdataService.count = 0;\n\t\tdataService.child_table = {};\n\t\tdataService.definition = {};\n\t\tdataService.style = {\n\t\t\tchg_soc_style_rule: {},\n\t\t\tchg_soc_definition_style_rule: {},\n\t\t\tchg_soc_def_child_style_rule: {},\n\t\t\tstyle_sheet: \"\"\n\t\t};\n\t\tdataService.tasks = {\n\t\t\tdata: [],\n\t\t\tlinks: []\n\t\t};\n\t\tdataService.allRecords = {};\n\n\t\tfunction isValidDate(date) {\n\t\t\tif (Object.prototype.toString.call(date) === \"[object Date]\" && !isNaN(date.getTime()))\n\t\t\t\treturn true;\n\t\t\treturn false;\n\t\t}\n\n\t\tfunction buildFields(record, selectedFieldsList, tableMeta) {\n\t\t\tvar result = [];\n\t\t\tif (!selectedFieldsList)\n\t\t\t\treturn result;\n\t\t\tvar selectedFields = selectedFieldsList.split(\",\");\n\t\t\tselectedFields.forEach(function(fieldName) {\n\t\t\t\tif (fieldName && tableMeta[fieldName])\n\t\t\t\t\tresult.push({\n\t\t\t\t\t\tcolumn_name: fieldName,\n\t\t\t\t\t\tlabel: tableMeta[fieldName].label,\n\t\t\t\t\t\tdisplay_value: record[fieldName].display_value,\n\t\t\t\t\t\tvalue: record[fieldName].value,\n\t\t\t\t\t});\n\t\t\t});\n\t\t\treturn result;\n\t\t}\n\n\t\tfunction buildRecord(record, chgSocDef, tableMeta, styleRule, scheduleWindow) {\n\t\t\tvar ganttUtil = ganttChart.getGantt(urlService.socId);\n\t\t\tvar startDate = ganttUtil.date.parseDate(record[chgSocDef.start_date_field.value].display_value_internal, \"xml_date\");\n\t\t\tvar endDate = ganttUtil.date.parseDate(record[chgSocDef.end_date_field.value].display_value_internal, \"xml_date\");\n\t\t\t// Check start/end dates are valid before adding the task to gantt chart\n\t\t\tif (!isValidDate(startDate) || !isValidDate(endDate))\n\t\t\t\treturn;\n\n\t\t\tvar recordEvent = {\n\t\t\t\tid: record.sys_id ? record.sys_id.value : \"\",\n\t\t\t\ttext: record.number ? record.number.display_value : \"\",\n\t\t\t\tnumber: record.number ? record.number.display_value : \"\",\n\t\t\t\tchg_soc_def: chgSocDef.sys_id.value,\n\t\t\t\tconfig_item: record.cmdb_ci ? record.cmdb_ci.display_value : \"\",\n\t\t\t\tstart_date: startDate,\n\t\t\t\tend_date: endDate,\n\t\t\t\tdur_display: durationFormatter.buildDurationDisplay(startDate, endDate),\n\t\t\t\torder: 0,\n\t\t\t\tprogress: 0,\n\t\t\t\ttable: record.sys_class_name ? record.sys_class_name.value : chgSocDef.table_name.value,\n\t\t\t\tleft_fields: buildFields(record, chgSocDef.popover_left_col_fields.value, tableMeta),\n\t\t\t\tright_fields: buildFields(record, chgSocDef.popover_right_col_fields.value, tableMeta),\n\t\t\t\trecord: record,\n\t\t\t\tblackout_spans: [],\n\t\t\t\tmaint_spans: [],\n\t\t\t\tsys_id: record.sys_id ? record.sys_id.value : \"\",\n\t\t\t\tshort_description: record.short_description ? record.short_description.display_value : \"\",\n\t\t\t\t__visible: true\n\t\t\t};\n\n\t\t\tif (styleRule && styleRule.sys_id)\n\t\t\t\trecordEvent.style_class = SOC.STYLE_PREFIX + styleRule.sys_id;\n\n\t\t\tif (scheduleWindow) {\n\t\t\t\tif (chgSocDef.show_maintenance.value)\n\t\t\t\t\tangular.forEach(scheduleWindow.maintenance, function (schedule) {\n\t\t\t\t\t\tArray.prototype.push.apply(recordEvent.maint_spans, schedule.spans);\n\t\t\t\t\t});\n\t\t\t\tif (chgSocDef.show_blackout.value)\n\t\t\t\t\tangular.forEach(scheduleWindow.blackout, function (schedule) {\n\t\t\t\t\t\tArray.prototype.push.apply(recordEvent.blackout_spans, schedule.spans);\n\t\t\t\t\t});\n\t\t\t} else {\n\t\t\t\trecordEvent.id = chgSocDef.sys_id.value + \"_\" + recordEvent.id;\n\t\t\t\trecordEvent.parent = record[chgSocDef.reference_field.value].value;\n\t\t\t}\n\n\t\t\tdataService.allRecords[recordEvent.id] = {\n\t\t\t\tstyle_rule: styleRule,\n\t\t\t\tsys_id: record.sys_id ? record.sys_id.value : \"\",\n\t\t\t\ttable_name: record.sys_class_name ? record.sys_class_name.value : chgSocDef.table_name.value,\n\t\t\t\tchg_soc_def: chgSocDef.sys_id.value\n\t\t\t};\n\n\t\t\treturn recordEvent;\n\t\t}\n\n\t\tfunction buildItem(result, item) {\n\t\t\t// Build change_request record\n\t\t\tvar record = buildRecord(result[item.table_name][item.sys_id], result.chg_soc_definition, result[item.table_name].__table_meta, item.style, item.schedule_window);\n\t\t\tif (!record)\n\t\t\t\treturn;\n\n\t\t\tdataService.tasks.data.push(record);\n\n\t\t\t// Build related tasks\n\t\t\tif (item.related)\n\t\t\t\tfor (var childSocDefId in item.related) {\n\t\t\t\t\tvar childRecords = item.related[childSocDefId];\n\t\t\t\t\tfor (var i = 0; i < childRecords.length; i++) {\n\t\t\t\t\t\tvar childRecord = buildRecord(result[childRecords[i].table_name][childRecords[i].sys_id], result.chg_soc_definition.__child[childSocDefId], result[childRecords[i].table_name].__table_meta, childRecords[i].style);\n\t\t\t\t\t\tif (childRecord)\n\t\t\t\t\t\t\tdataService.tasks.data.push(childRecord);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t}\n\n\t\tdataService.buildData = function(result) {\n\t\t\tif (!result)\n\t\t\t\treturn;\n\n\t\t\tdataService.more = result.__more;\n\t\t\tdataService.count = result.__change_count;\n\n\t\t\t// Start with the definition object\n\t\t\tif (result.chg_soc_definition)\n\t\t\t\tdataService.definition = result.chg_soc_definition;\n\n\t\t\t// Ordered change requests with style and related records\n\t\t\tif (result.__struct)\n\t\t\t\tfor (var i = 0; i < result.__struct.length; i++)\n\t\t\t\t\tbuildItem(result, result.__struct[i]);\n\n\t\t\t// Find all child tables\n\t\t\tfor (var table in result)\n\t\t\t\tif (result[table].__has_children)\n\t\t\t\t\tdataService.child_table[table] = result[table].__table_meta;\n\n\t\t\t// Set style rules and style sheet to the model\n\t\t\tdataService.style.chg_soc_style_rule = result.chg_soc_style_rule;\n\t\t\tdataService.style.chg_soc_definition_style_rule = result.chg_soc_definition_style_rule;\n\t\t\tdataService.style.chg_soc_def_child_style_rule = result.chg_soc_def_child_style_rule;\n\t\t\tdataService.style.style_sheet = result._css;\n\t\t};\n\n\t\tdataService.addData = function(result) {\n\t\t\tdataService.more = result.__more;\n\t\t\tdataService.count = result.__change_count;\n\n\t\t\tif (result.__struct)\n\t\t\t\tfor (var i = 0; i < result.__struct.length; i++)\n\t\t\t\t\tbuildItem(result, result.__struct[i]);\n\n\t\t\tfor (var table in result)\n\t\t\t\tif (result[table].__has_children)\n\t\t\t\t\tdataService.child_table[table] = result[table].__table_meta;\n\t\t};\n\n\t\tdataService.initPage = function(chgSocDefId, condition) {\n\t\t\tvar deferred = $q.defer();\n\t\t\tvar url = SOC.GET_CHANGE_SCHEDULE + chgSocDefId;\n\t\t\tvar config = {};\n\t\t\tconfig.params = {\n\t\t\t\tsysparm_ck: $window.g_ck\n\t\t\t};\n\t\t\tif (condition)\n\t\t\t\tconfig.params.condition = condition;\n\t\t\t$http.get(url, config).then(function(response){\n\t\t\t\tdeferred.resolve(dataService.buildData(response.data.result));\n\t\t\t}, function(response) {\n\t\t\t\tdeferred.reject(response);\n\t\t\t});\n\t\t\treturn deferred.promise;\n\t\t};\n\n\t\tdataService.getChanges = function() {\n\t\t\tvar deferred = $q.defer();\n\t\t\tvar url = SOC.GET_CHANGE_SCHEDULE + dataService.definition.sys_id.value;\n\t\t\tvar config = {};\n\t\t\tconfig.params = {\n\t\t\t\tsysparm_ck: $window.g_ck,\n\t\t\t\tcount: dataService.count\n\t\t\t};\n\t\t\tif (dataService.definition.condition.dryRun)\n\t\t\t\tconfig.params.condition = dataService.definition.condition.value;\n\n\t\t\t$http.get(url, config).then(function(response){\n\t\t\t\tdeferred.resolve(dataService.addData(response.data.result));\n\t\t\t}, function(response) {\n\t\t\t\tdeferred.reject(response);\n\t\t\t});\n\t\t\treturn deferred.promise;\n\t\t};\n\n\t\tdataService.getChildren = function(parentId) {\n\t\t\tvar res = $filter(\"filter\")(dataService.tasks.data, function(task) {\n\t\t\t\treturn task.parent === parentId;\n\t\t\t});\n\t\t\treturn res;\n\t\t};\n\n\t\tdataService.destroyData = function() {\n\t\t\tdataService.more = false;\n\t\t\tdataService.count = 0;\n\t\t\tdataService.child_table = {};\n\t\t\tdataService.definition = {};\n\t\t\tdataService.style = {\n\t\t\t\tchg_soc_style_rule: {},\n\t\t\t\tchg_soc_definition_style_rule: {},\n\t\t\t\tchg_soc_def_child_style_rule: {},\n\t\t\t\tstyle_sheet: \"\"\n\t\t\t};\n\t\t\tdataService.tasks = {\n\t\t\t\tdata: [],\n\t\t\t\tlinks: []\n\t\t\t};\n\t\t\tdataService.allRecords = {};\n\t\t};\n\n\t\tdataService.parseQuery = function(condition) {\n\t\t\tcondition = condition + \"\";\n\t\t\tvar deferred = $q.defer();\n\t\t\tvar url = SOC.GET_PARSE_QUERY + condition;\n\t\t\tvar config = {};\n\t\t\tconfig.params = {};\n\t\t\tconfig.params.sysparm_ck = $window.g_ck;\n\n\t\t\t$http.get(url, config).then(function(response) {\n\t\t\t\tdeferred.resolve(response.data.result);\n\t\t\t}, function(response) {\n\t\t\t\tdeferred.reject(response);\n\t\t\t});\n\n\t\t\treturn deferred.promise;\n\t\t};\n\n\t\tfunction checkSecurityObject() {\n\t\t\treturn dataService.definition && dataService.definition.__security;\n\t\t}\n\n\t\tdataService.canCreate = function() {\n\t\t\tif (checkSecurityObject() && dataService.definition.__security.canCreate)\n\t\t\t\treturn dataService.definition.__security.canCreate;\n\t\t\treturn false;\n\t\t};\n\n\t\tdataService.canRead = function() {\n\t\t\tif (checkSecurityObject() && dataService.definition.__security.canRead)\n\t\t\t\treturn dataService.definition.__security.canRead;\n\t\t\treturn false;\n\t\t};\n\n\t\tdataService.canWrite = function() {\n\t\t\tif (checkSecurityObject() && dataService.definition.__security.canWrite)\n\t\t\t\treturn dataService.definition.__security.canWrite;\n\t\t\treturn false;\n\t\t};\n\n\t\tdataService.canDelete = function() {\n\t\t\tif (checkSecurityObject() && dataService.definition.__security.canDelete)\n\t\t\t\treturn dataService.definition.__security.canDelete;\n\t\t\treturn false;\n\t\t};\n\n\t\tdataService.trackEvent = function(source) {\n\t\t\tif ($window.GlideWebAnalytics && $window.GlideWebAnalytics.trackEvent)\n\t\t\t\t$window.GlideWebAnalytics.trackEvent('com.snc.change_management.soc', 'Change Schedules', source, 0, 0);\n\t\t};\n\t}]);\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Disable Copy Paste For Portal/README.md",
    "content": "**Steps to Activate**\n1. Open the portals you want to disable copy/paste operation in \"sp_portal\" table.\n2. Open the theme attached to the portal.\nIn the theme under \"JS Includes\" relatd list, create new JS include and select the UI script you created. Go to your portal and try to copy/paste in any catalog item field or any text field on portal.The operation will be prevented with the alert message.\n\n**Use Case**\n1. Many high security organizations like banks do not want the users to copy paste account number or passwords to ensure safety.\n2. Many input form want the users to re-enter the password or username without copying from other fields.\n\nThis UI script is applied through portal theme , so it will be specific to portals using that theme. It will not have instance wide affect.\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Disable Copy Paste For Portal/script.js",
    "content": "/*\nDisable Copy Paste on Portal Pages.\nUI Type : Service Portal/Mobile.\n*/\ndocument.addEventListener('copy', function(e) { //event listner for copy.\n    alert(\"Copy Operation is prevented on this page.\"); // alert for copy\n    e.preventDefault(); // prevent copy\n});\n\ndocument.addEventListener('paste', function(e) { //event listner for paste.\n    alert(\"Paste Operation is prevented on this page.\"); //alert for paste\n    e.preventDefault(); // prevent paste\n});\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Display number of created records/README.md",
    "content": "# Display number of created records\n\nUse case / Requirement : When trying to create a new record in a table, Display a message showing number of records already created by him/her in that table.\n\nSolution : Created a UI script to display number of records and call that UI script from an onLoad client script.\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Display number of created records/onLoad.js",
    "content": "function onLoad() {\n\tif(g_form.isNewRecord())\n\t\t{\n    var obj = new SampleUIScript(); // Initialize the class in UI Script \n      // Call the function in UI Script\n      obj.callFromClientScript(\"incident\"); //you can give your required table name\n    }\n}\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Display number of created records/ui_script.js",
    "content": "var SampleUIScript=Class.create();\nSampleUIScript.prototype={\n\tinitialize: function()\n\t{\n\n\t},\n\n\tcallFromClientScript: function(TableName)\n\t{\n\t\tvar count=0;\n\t\tvar gr= new GlideRecord(TableName);   \n\t\tgr.addQuery('sys_created_by',g_user.userName);\n\t\tgr.query();\n\t\twhile(gr.next())\n\t\t\t{\n\t\t\t\tcount++;\n\t\t\t}\n\t\tg_form.addInfoMessage(\"Total Number of \"+TableName+\" Records Created by you are : \"+count);  //you can update info message as required\n\n\t},\n\n\n};\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Make OOB Attachment Mandatory/README.md",
    "content": "# Make out of the box attahcment mandatory onChange of a field in Catalog Item\n\nThis scripts makes the out of the box attachments to mandatory on the Service Portal for any Catalog Item.\n\nUsage:\n\nStep #1 - Create the UI Script \n* [Click here for script](setAttachmentMandatory.js)\n\nStep #2 - Include the UI Script in the Portal Theme under JS Includes\n\nStep #3 - Create a Catalog Client Script for the respective Catalog Item where you want to make the OOB attachment mandatory for certain criteria\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Make OOB Attachment Mandatory/setAttachmentMandatory.js",
    "content": "function setAttachmentMandatory(isMandatory) {\n\n    var isSuccess = false;\n    \n    try {\n        angular.element(\"#sc_cat_item\").scope().c.data.sc_cat_item.mandatory_attachment = isMandatory;\n        isSuccess = true;\n    } catch (e) {\n        console.log('setAttachmentMandatory() failed: ' + e);\n    }\n\n    return isSuccess;\n}"
  },
  {
    "path": "Client-Side Components/UI Scripts/Observe MRVS Events/MRVSUtils.js",
    "content": "var MRVSUtils = Class.create();\n\n// Default configuration for the MutationObserver\nMRVSUtils.OBSERVER_CONFIG = {\n    subtree: true,\n    childList: true\n};\n\nMRVSUtils.prototype = {\n    /*\n     * initialise the MRVSUtils instance\n     * @param {String} Name of multi-row variable set to watch\n     */\n    initialize: function (mrvsName) {\n        this.mrvsID = g_form.getControl(mrvsName).id;\n        this.mrvsID = this.mrvsID.replace(/(.*:)/, '');\n        this.table = document.getElementById(this.mrvsID + \"_table\");\n        this.columnNames = this._getColumnNames();\n        this.rowIds_Old = [];\n        this.rowIds = [];\n        this.isUpdate = false;\n    },\n\n    /*\n     * Process all mutations from the observer\n     * @param {MutationList} Array of all mutations observed\n     * @returns {JSON} Array of updates\n     */\n    processMutations: function (mutations) {\n        this._updateRowIds();\n        var mutationList = this._filterEvents(mutations);\n        return this._processMutation(mutationList[0]);\n    },\n\n    /*\n     * Get the table ID for the multi-row variable set\n     * @return {string} (sys_id)_table\n     */\n    getTableID: function () {\n        return [this.mrvsID, \"table\"].join('_');\n    },\n\n    /*\n     * get list of column names\n     * used when building the modified data JSON\n     */\n    _getColumnNames: function () {\n        var columnNames = [];\n        var headers = this.table.getElementsByTagName('th');\n        for (var _col = 0; _col < headers.length; _col++) {\n            columnNames.push(headers[_col].innerText);\n        }\n\n        return columnNames;\n    },\n\n    /* \n     * re-scan the MRVS table to get an up to date list of rows\n     */\n    _updateRowIds: function () {\n        this.rowIds = [];\n        var body = this.table.getElementsByTagName('tbody');\n        var rows = body[0].getElementsByTagName('tr');\n        for (var _row = 0; _row < rows.length; _row++) {\n            this.rowIds.push(rows[_row].id);\n        }\n    },\n\n    /*\n     * check if this is an mutation we care about\n     * basically, if it's a update to TBODY and has either added or removed nodes,\n     * or it's a updated row which should give us both added and removed for a single TD (however this is\n     * not guaranteed if the original cell value was empty)\n     */\n    _isUpdateEvent: function (mutation) {\n        var isAddDelete = ((mutation.target.nodeName == 'TBODY' && mutation.target.id == 'empty_table_row') &&\n            ((mutation.removedNodes.length > 0 && mutation.removedNodes[0].className != 'list2_no_records') ||\n                (mutation.addedNodes.length > 0 && mutation.addedNodes[0].className != 'list2_no_records')));\n        var isUpdate = (mutation.target.nodeName == 'TD' && (mutation.removedNodes.length > 0 || mutation.addedNodes.length > 0));\n        return isAddDelete || isUpdate;\n    },\n\n    /*\n     * for the given node, get the cells contents\n     */\n    _getCellData: function (node, rowIds) {\n        var cellData = {};\n        var cells = node.cells;\n        for (var i = 0; i < cells.length; i++) {\n            if (cells[i].className == 'vt') cellData[this.columnNames[i]] = cells[i].innerText;\n        }\n\n        cellData.row_number = rowIds.indexOf(node.id) > -1 ? rowIds.indexOf(node.id) + 1 : undefined;\n        cellData.row_id = node.id;\n        return cellData;\n    },\n\n    /*\n     * process the given nodes (either added or deleted) and build the response using the cell data\n     */\n    _getNodeData: function (nodes, rowIds) {\n        var modifiedRows = [];\n        nodes.forEach(function (_node, _index, _list) {\n            modifiedRows.push(this._getCellData(_node, rowIds));\n        }, this);\n        return modifiedRows;\n    },\n\n    /*\n     * given a mutation, build the JSON response depending on the mutation type\n     */\n    _processMutation: function (mutation) {\n        var modifiedData = {};\n        if (this.isUpdate) { // special handling as some updates only have a single event\n            // this is a row update\n            modifiedData.updated = this._getCellData(mutation.target.parentElement, this.rowIds);\n        } else if (mutation.addedNodes.length && mutation.removedNodes.length == 0) {\n            modifiedData.added = this._getNodeData(mutation.addedNodes, this.rowIds);\n        } else {\n            // pass in the old row_id list otherwise we won't know what row was release.\n            modifiedData.removed = this._getNodeData(mutation.removedNodes, this.rowIds_Old);\n        }\n\n        this.rowIds_Old = this.rowIds;\n        return modifiedData;\n    },\n\n    /*\n     * filter the mutation list for mutations we care about\n     * If only one mutation is provided then it's assumed it's a row update (based on extensive testing!!)\n     */\n    _filterEvents: function (mutationList) {\n        this.isUpdate = false;\n        if (mutationList.length > 1) {\n            mutationList = mutationList.filter(function (_mutation) {\n                return this._isUpdateEvent(_mutation);\n            }, this);\n        } else {\n            this.isUpdate = (mutationList[0].addedNodes.length > 0 && mutationList[0].removedNodes.length > 0);\n        }\n        return mutationList;\n    }\n};\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Observe MRVS Events/README.md",
    "content": "# Observe Multi-row variable set events\n\nUsing the MutationObserver API we can monitor changes to a multi-row variable set (i.e., new rows, deleted rows and updated rows).\nThis currently only works in the platform, not Workspace or Service Portal.\n\nUse in a onLoad client script (Isolate script = false).  Sets up an observer on the named variable set and any changes are returned in the mutationList object.\nReturn value will list changes to the variable set.  For example:\n\n```json\n{\n   \"removed\": [\n      {\n         \"VM #\": \"2\",\n         \"Name\": \"2\",\n         \"row_number\": 1,\n         \"row_id\": \"row_9652c56347f5311001612c44846d433f\"\n      }\n   ]\n}\n```\n\n\n```javascript\n\nfunction onLoad() {\n\t\n    setTimeout(function() {\n\tvar mrvs = new MRVSUtils('name of multi-row-variable-set');\n        var observer = new MutationObserver(function(mutationList, observer) {\n            var modifiedData = mrvs.processMutations(mutationList);\n            console.log(JSON.stringify(modifiedData, '', 3));\n        });\n\n        // create the observer looking for changes to the contents of the MRVS\n        observer.observe($(mrvs.getTableID()), MRVSUtils.OBSERVER_CONFIG);\n\n    }, 1000);\n\n}\n```\n\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/PersistentAnnouncementBanner/README.md",
    "content": "This UI Script is used to inject a custom, highly visible, and persistent notification banner across the top of the entire ServiceNow platform interface. \n\nIt is ideal for communicating critical, non-transactional system messages such as scheduled maintenance, major outages, or company-wide policy announcements.\n\nThe key feature of this banner is that it is dismissible, and it uses a User Preference to ensure that once a user closes a specific version of the announcement\n\nThe Reason to use this is , Announcements Banner Module Sometimes Loads Slower or Doesnt trigger the notification banner , instead this scripts all the time,if user is logged in \n"
  },
  {
    "path": "Client-Side Components/UI Scripts/PersistentAnnouncementBanner/annoucement_banner.js",
    "content": "(function() {\n    var BANNER_TEXT = ' **SYSTEM ALERT:** Scheduled maintenance for all services will occur this Saturday from 1 AM to 4 AM UTC. Expect minor downtime. 🚨';\n    var BANNER_BG_COLOR = '#ffcc00'; // Warning yellow\n    var BANNER_TEXT_COLOR = '#333333';\n\n    // Check if the user has already dismissed this version of the banner\n    var isDismissed = (g_preference.get(PREF_KEY) === 'true');\n\n    if (isDismissed) {\n        return;\n    }\n    var banner = document.createElement('div');\n    banner.setAttribute('id', 'global_announcement_banner');\n    banner.innerHTML = BANNER_TEXT;\n    banner.style.cssText = [\n        'position: fixed;',\n        'top: 0;',\n        'left: 0;',\n        'width: 100%;',\n        'padding: 10px 40px 10px 15px;', // Added padding on the right for the close button\n        'background-color: ' + BANNER_BG_COLOR + ';',\n        'color: ' + BANNER_TEXT_COLOR + ';',\n        'z-index: 10000;', // High z-index to ensure it sits on top of everything\n        'text-align: center;',\n        'font-weight: bold;',\n        'box-shadow: 0 2px 5px rgba(0,0,0,0.2);'\n    ].join('');\n    var closeButton = document.createElement('span');\n    closeButton.innerHTML = '×'; // Times symbol\n    closeButton.style.cssText = [\n        'position: absolute;',\n        'top: 50%;',\n        'right: 15px;',\n        'transform: translateY(-50%);',\n        'font-size: 20px;',\n        'cursor: pointer;',\n        'font-weight: normal;',\n        'line-height: 1;'\n    ].join('');\n\n    closeButton.onclick = function() {\n        // Remove the banner from the DOM\n        banner.remove(); \n        \n        // Set the User Preference so the banner stays dismissed across sessions\n        g_preference.set(PREF_KEY, 'true');\n    };\n    banner.appendChild(closeButton);\n    document.body.appendChild(banner);\n\n})();\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Prevent right click on portals/README.md",
    "content": "**Steps to Activate**\n1. Open the portals you want to disable right-click in \"sp_portal\" table.\n2. Open the theme attached to the portal.\n3. In the theme under \"JS Includes\" relatd list, create new JS include and select the UI script you created.\nGo to your portal and try to roght click, it will prevent and show the alert message.\n\n**Use Case**\n1. Many high security organizations like banks do not want their images or links to be copied through \"inspect\" so right-click need to be disabled.\n2. Many organizations want their source code to be hidden so they prevent right-click.\n\n\n **Note**\n  1. This UI script is applied through portal theme , so it will be specific to portals using that theme. It will not have instance wide affect.\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Prevent right click on portals/script.js",
    "content": "/*\nPrevent right-click on portal for portal pages.\nThis will secure the site code, prevent users from saving images etc.\nIdeal for high security organisations.\n\nUI Type : Mobile/service portal.\n*/\n(function() { // self invoking function\n    document.addEventListener('contextmenu', function(event) {\n        event.preventDefault(); // Prevent right-click operation.\n        alert(\"Right-Click Prevented for Security Reasons.\"); // alert message shown on right click.\n    });\n})();\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Restrict URL Hack using UI script/README.md",
    "content": "1.Go to System UI >UI Scripts >Create a new UI script and Check the Global field.\n2.Below is an example \n\nLets say the Original URL opened is:\n\nhttps://devXXXXX.service-now.com/sys_security_acl.do?sys_id=-1&sys_is_list=true&sys_target=sys_security_acl&sysparm_checked_items=&sysparm_fixed_query=&sysparm_group_sort=&sysparm_list_css=&sysparm_query=name%3dincident%5eoperation%3dread\n\nand we need to monitor \"sysparm_fixed_query\" parameter in the URL.\n\n\n![image](https://user-images.githubusercontent.com/42912180/195846361-d51f40ba-cdc0-40e1-8057-b19a0906a9a8.png)\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/Restrict URL Hack using UI script/script.js",
    "content": "var OriginalURL = window.parent.location.href;\n\nvar ChangedURL = \"https://\" + window.location.host + \"URL you want to monitor\"; //\"window.location.host\" retrives the instance name \n\nif (!url.startURL(ChangedURL)) //check if URL doesn't starts as Original URL\n{\n    window.location = OriginalURL; //Redirect to Original URL\n}\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/User acknowledgement Using UI script and user preferences/UIpage.js",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n    <g:ui_form>\n        <div class=\"modal-body\">\n            <h1 class=\"text-center\">Important Message</h1>\n            <p>Please read and acknowledge this important message before proceeding.</p>\n            <p>\n                Your access will be revoked if you don't log in for 30 days!\n            </p>\n            <div class=\"text-center\" style=\"margin-top: 20px;\">\n                <button id=\"acknowledge_btn\" class=\"btn btn-primary\" onclick=\"return closeDialog();\">Acknowledge</button>\n            </div>\n        </div>\n    </g:ui_form>\n    <script>\n        (function() {\n            function closeDialogAndRedirect() {\n                try {\n                    GlideDialogWindow.get().destroy();\n                } catch (e) {}\n\n                // Set user preference\n                if (typeof setPreference === 'function') {\n                    setPreference('login.consent1', 'true');\n                }\n                // Redirect to home.do\n                window.top.location.href = 'home.do';\n            }\n            document.getElementById('acknowledge_btn').addEventListener('click', closeDialogAndRedirect);\n        })();\n    </script>\n\n</j:jelly>\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/User acknowledgement Using UI script and user preferences/UIscript.js",
    "content": "addLoadEvent(function() {\n    try {\n        // Skip contexts where GlideDialogWindow isn't available (e.g., Service Portal)\n        if (typeof GlideDialogWindow === 'undefined' || (window.NOW && NOW.sp))\n            return;\n        var prefName = 'login.consent1';\n        // Only show the dialog when the pref is explicitly 'false'\n        var val = (typeof getPreference === 'function') ? getPreference(prefName) : null;\n        var shouldShow = String(val || '').toLowerCase() === 'false';\n        //alert(\"val\"+\" \"+val+\" \"+\"shouldShow\"+\" \"+shouldShow);\n        if (!shouldShow)\n            return;\n        var dialog = new GlideDialogWindow('acknowledgement_dialog'); // UI Page name\n        dialog.setTitle('Acknowledge Message');\n        dialog.setSize(500, 300);\n        dialog.render();\n    } catch (e) {\n        if (console && console.warn) console.warn('ack loader error', e);\n    }\n});\n"
  },
  {
    "path": "Client-Side Components/UI Scripts/User acknowledgement Using UI script and user preferences/readme.md",
    "content": "**Create a user preference as follows:**\n<img width=\"1675\" height=\"420\" alt=\"image\" src=\"https://github.com/user-attachments/assets/efcd19dd-f1ad-440a-ae59-10cc63832cad\" />\n**Create a UI script:**\n<img width=\"1646\" height=\"808\" alt=\"image\" src=\"https://github.com/user-attachments/assets/207f9dfa-4c6c-4686-84ac-3ff5294a0771\" />\nThis script runs during login and checks the user preference. \nIf the preference is set to false, it displays the acknowledgement popup by calling UI page\n\n**UI Page details:**\n<img width=\"1511\" height=\"897\" alt=\"image\" src=\"https://github.com/user-attachments/assets/74d4be0f-9401-4733-81df-fca8f52b644e\" />\nSet the user preference to true so that the popup will not appear for every login.\n\n**Output**:\n**On user login:**\n<img width=\"1896\" height=\"748\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1af1b7ec-4647-4bb4-a786-8070817b21f8\" />\n"
  },
  {
    "path": "Client-Side Components/UX Client Script Include/Access global object from page scripts/README.md",
    "content": "# How to access global object from page scripts in UI builder\nWhen creating the page scripts within UI builder, servicenow doesn't allow you to use Web API (except console and timeout), hence you are not able to access `document` or `window` objects.\nOne neat trick that you can use is to create a reusable UX Client Script Include and import it into the page script.\n\n## Steps\n1. create a new UX client script include (type `sys_ux_client_script_include.list` into the app navigator and click `new`)\n2. provide a name e.g. `global` and paste the following code into script field\n```javascript\nfunction include({imports}) { \n  var Function = function(){}.constructor;\n  var global = Function(\"return this\")();\n\n  return global;\n}\n```\n3. save the record\n4. create a new page script within UI builder and import your script include (make sure to use the whole API name of that script include. In a global scope it should be global.global) \n```javascript\nfunction handler({api, event, helpers, imports}) {\n    const {document, window} = imports['global.global']();\n    window.alert(`Hello from ${document.URL}`);    \n}\n```\n5. create a new event handler with your script\n6. fire that event and observe\n\n"
  },
  {
    "path": "Client-Side Components/UX Client Script Include/Access global object from page scripts/script.js",
    "content": "function include({imports}) {\n  var FunctionConstructor = function() {}.constructor;\n  var global = FunctionConstructor(\"return this\")();\n  return global;\n}\n"
  },
  {
    "path": "Client-Side Components/UX Client Script Include/Record Operation Utilities/README.md",
    "content": "#Record Operations Utilities which can be in imported in any UIB client script (Make sure to make the client script include accessible in all scopes)\n1. createRecord - Function to execute create record data broker with necessary arguments\n2. updateRecord - Function to execute update record data broker with necessary arguments\n3. deleteRecord - Function to execute delete record data broker with necessary arguments\n\n/*Sample script to show how to import client script include can be included :-\nfunction handler({api, event, helpers, imports}) {\n  const { createRecord, updateRecord, deleteRecord } = imports['global.Record Operation Utilities']();\n}\n*/\n"
  },
  {
    "path": "Client-Side Components/UX Client Script Include/Record Operation Utilities/script.js",
    "content": "\nfunction include({\n    imports\n}) {\n    return {\n      \n        //Function for executing create record data broker with the required arguments\n        \n      createRecord: (api, dataResource, table, templateFields, useSetDisplayValue) => {\n            try {\n                api.data[dataResource].execute({\n                    table: table,\n                    templateFields: templateFields,\n                    useSetDisplayValue: useSetDisplayValue\n\n                });\n            } catch (ex) {\n                console.log(`Create Operation error: ${ex}`);\n            }\n        },\n        // Function for executing update record data broker with the required arguments\n        updateRecord: (api, dataResource, table, recordId, templateFields, useSetDisplayValue) => {\n            try {\n                api.data[dataResource].execute({\n                    table: table,\n                    recordId: recordId,\n                    templateFields: templateFields,\n                    useSetDisplayValue: useSetDisplayValue\n\n                });\n            } catch (ex) {\n                console.log(`Update Operation error: ${ex}`);\n            }\n        },\n        \n        //Function for executing delete record data broker with the required arguments\n        deleteRecord: (api, dataResource, table, recordId) => {\n            try {\n            api.data[dataResource].execute({\n                table: table,\n                recordId: recordId\n\n            });\n            }\n            catch(ex) {\n                console.log(`Delete Operation error: ${ex}`);\n            }\n        },\n\n\n\n    };\n\n}\n"
  },
  {
    "path": "Client-Side Components/UX Client Script Include/Reusable Debounce/DebounceUtil.js",
    "content": "function include() {\n    class DebounceUtil {\n\n        /**\n         * @param callbackFunc - callback function following timeout\n         * @param timeout - debounce timeout\n         * @param helpers - the helpers object passed from a UX Client Script\n         */\n        static debounce(callbackFunc, timeout = 750, helpers) {\n            let timer;\n            return (...args) => {\n                helpers.timing.clearTimeout(timer); // Clear anything currently in place\n                timer = helpers.timing.setTimeout(() => { callbackFunc.apply(this, args); }, timeout);\n            };\n        }\n    }\n    return DebounceUtil;\n}\n"
  },
  {
    "path": "Client-Side Components/UX Client Script Include/Reusable Debounce/README.md",
    "content": "## Add a debounce to search fields or other inputs with a client script\nInputs, Typeaheads, and other components that can be used for searching the database, caches, or local storage. However, performing a search for every keypress or other change is often unnecessary and uses more resources than strictly necessary. This UX Client Script Include is a small utility for managing debounces, allowing a 'cool-off' from inputs before performing the activity.\n\n### Steps\n1. Create a new UX Client Script Include (`sys_ux_client_script_include`), using the script from the associated snippet\n2. Create a new Client Script in UI Builder, and add the include you created in 1 as a dependency\n3. Within the Client Script, import the Script Include as follows, replacing `global.DebounceUtilName` with the scope and UX Client Script Include name:\n    ```js\n    const DebounceUtil = imports[\"global.DebounceUtilName\"]();\n    ```\n4. Within the Client Script, declare a `function` to be called inside the debounce function\n\n### Example usage\n```js\n/**\n* @param {params} params\n* @param {api} params.api\n* @param {any} params.event\n* @param {any} params.imports\n* @param {ApiHelpers} params.helpers\n*/\nfunction handler({api, event, helpers, imports}) {\n    const DebounceUtil = imports[\"global.DebounceUtil\"]();\n    var debounceSearch = DebounceUtil.debounce(takeAction, 500, helpers);\n    debounceSearch();\n\n    function takeAction(){\n        const searchTerm = event.payload.value;\n        api.setState('fullRefQuery',`nameLIKE${searchTerm}`);\n    }\n}\n```\n"
  },
  {
    "path": "Client-Side Components/UX Client Scripts/debug-event/README.md",
    "content": "# DEBUG Event UX Client Script\n\nThis repository provides a simple UX Client Script for logging events to the console. Named `DEBUG Event`, this client script is designed to assist in debugging by outputting key event details to the console, helping developers track event triggers and properties in real-time.\n\n## Features\n\n- **Console Logging**: Logs event details, including `elementId` and `name`, to the console for easy tracking.\n- **Simplified Debugging**: Useful for monitoring and debugging UX interactions without complex setup.\n\n## Script Overview\n\n```javascript\n/**\n * @param {params} params\n * @param {api} params.api\n * @param {any} params.event\n * @param {any} params.imports\n * @param {ApiHelpers} params.helpers\n */\nfunction handler({ api, event, helpers, imports }) {\n  console.log(`DEBUG Event ${event.elementId} ${event.name}`, event);\n}\n"
  },
  {
    "path": "Client-Side Components/UX Client Scripts/debug-event/debug-event.js",
    "content": "/**\n* @param {params} params\n* @param {api} params.api\n* @param {any} params.event\n* @param {any} params.imports\n* @param {ApiHelpers} params.helpers\n*/\nfunction handler({ api, event, helpers, imports }) {\n  console.log(`DEBUG Event ${event.elementId} ${event.name}`, event);\n}\n"
  },
  {
    "path": "Client-Side Components/UX Client Scripts/debug-state/README.md",
    "content": "# Debug State UX Client Script\n\nThis repository contains a UX Client Script called `Debug State`, designed to log the current client state to the console. This script is useful for developers who want to inspect the state object in real time, making debugging more efficient by allowing quick access to current state values.\n\n## Features\n\n- **Console Logging of State**: Logs the entire `state` object to the console, enabling developers to track and inspect state changes.\n- **Efficient Debugging**: Simplifies the debugging process by providing direct access to the client's state.\n\n## Script Overview\n\n```javascript\n/**\n * @param {params} params\n * @param {api} params.api\n * @param {any} params.event\n * @param {any} params.imports\n * @param {ApiHelpers} params.helpers\n */\nfunction handler({ api, event, helpers, imports }) {\n  console.log(`DEBUG State:`, { ...api.state });\n}\n```\n"
  },
  {
    "path": "Client-Side Components/UX Client Scripts/debug-state/debug-state.js",
    "content": "/**\n* @param {params} params\n* @param {api} params.api\n* @param {any} params.event\n* @param {any} params.imports\n* @param {ApiHelpers} params.helpers\n*/\nfunction handler({ api, event, helpers, imports }) {\n  console.log(`DEBUG State:`, { ...api.state });\n}\n"
  },
  {
    "path": "Client-Side Components/UX Data Broker Transform/FetchSysProperty/README.md",
    "content": "Once this code snippet is added in a new transform data broker in sys_ux_data_broker_transform table, this will start showing up in UIB data resources. \nOnce the data resource is added to the page of any experience, the user only needs to input the property_name field and rest will be take care of. \nJust a note that in the data broker record properties field, please add below json object:\n[ { \"name\": \"property_name\", \n\"label\": \"Property\", \n\"fieldType\": \"string\",\n\"readOnly\": false, \n\"mandatory\": true, \n\"defaultValue\": \"\", \n\"description\": \"System property name\" } ]\n"
  },
  {
    "path": "Client-Side Components/UX Data Broker Transform/FetchSysProperty/sysPropdataBroker.js",
    "content": "//Once this is saved, it will show up in UI Builder as a Data Resource which can be selected if the user wants to fetch system property table values.\nfunction transform(input){\n//property_name is one of the properties that the UX Transform Data broker needs. It's a field name.\nvar property_name = input.property_name;\nvar objProp = {};\nif(!property_name){\nobjProp.errorValue = 'Please provide the property name.';\nreturn objProp;\n}\nvar grSysProp = new GlideRecord('sys_properties');\ngrSysProp.addQuery('name',input.property_name);\ngrSysProp.query();\nif(grSysProp.next()){\nobjProp.value = grSysProp.getValue('value');\n}\nif(!objProp.hasOwnProperty('value')){\nobjProp.noPropertyFound = `The property ${property_name} was not found.`;\nreturn objProp;\n}\nreturn objProp;\n}\n"
  },
  {
    "path": "Client-Side Components/UX Data Broker Transform/create-update-user-preference/README.md",
    "content": "# Create/Update User Preference Data Broker\n\nThis repository provides a data broker for creating or updating user preferences in ServiceNow. The broker allows you to specify a preference name and value, storing or updating it in the user’s preferences.\n\n## Features\n\n- **Create or Update User Preferences**: Provides a simple interface for adding or modifying preferences by name and value.\n- **Error Handling**: Logs errors if the preference name is missing or if an unexpected issue arises.\n\n## Template Overview\n\n```javascript\n/**\n * This data broker creates or updates a user preference of the given name with the given value\n * @param {{name: string, value?: string}} inputs inputs from the properties field above\n */\nfunction transform({ name, value }) {\n  const lib = \"Data Broker\";\n  const func = \"Create Update User Preference\";\n  try {\n    if (!name) throw new Error(\"Missing required param 'name'\");\n\n    gs.getUser().savePreference(name, value);\n  } catch (e) {\n    gs.error(`${lib} ${func} - ${e}`);\n    throw new Error(e);\n  }\n}\n```\n"
  },
  {
    "path": "Client-Side Components/UX Data Broker Transform/create-update-user-preference/create-update-user-preference.js",
    "content": "/**\n * This data broker creates or updates a user preference of the given name with the given value\n * @param {{name: string, value?: string}} inputs inputs from the properties field above\n */\nfunction transform({ name, value }) {\n  const lib = \"Data Broker\";\n  const func = \"Create/Update User Preference\";\n  try {\n    if (!name) throw new Error(\"Missing required param 'name'\");\n\n\n    gs.getUser().savePreference(name, value);\n  } catch (e) {\n    gs.error(`${lib} ${func} - ${e}`);\n    throw new Error(e);\n  }\n}\n\n/**\n * Make sure to select that this data broker mutates data\n * Input this in the properties field:\n * \n [\n    {\n        \"name\": \"name\",\n        \"label\": \"Name\",\n        \"description\": \"The name of the user preference to create or update\",\n        \"readOnly\": false,\n        \"fieldType\": \"string\",\n        \"mandatory\": true,\n        \"defaultValue\": \"\"\n    },\n    {\n        \"name\": \"value\",\n        \"label\": \"Value\",\n        \"description\": \"The value to store in the user preference\",\n        \"readOnly\": false,\n        \"fieldType\": \"string\",\n        \"mandatory\": false,\n        \"defaultValue\": \"\"\n    }\n  ]\n */\n"
  },
  {
    "path": "Client-Side Components/UX Data Broker Transform/starter-template/README.md",
    "content": "# Transform Data Broker Template\n\nThis repository provides a starter template for creating Transform Data Brokers in ServiceNow’s UX framework. This template includes error handling, input validation, and JSDoc annotations for a streamlined setup.\n\n## Features\n\n- **JSDoc Type Annotations** for clarity and type-checking.\n- **Destructured Inputs** for simplified parameter handling.\n- **Error Logging & Propagation**: Logs errors while allowing the data resource to fail if needed.\n- **Properties Example**: Provides an example of what the properties should look like\n\n## Template Overview\n\n```javascript\n/**\n * @param {{param1: string, param2: number, param3?: boolean}} inputs \n * Inputs from the properties field above; param1 and param2 are mandatory.\n * @returns {string} The value returned after transformation.\n */\nfunction transform({ param1, param2, param3 }) {\n  const lib = \"Data Broker\";\n  const func = \"<insert data broker name here>\";\n  let res;\n\n  try {\n    if (!param1) throw new Error(\"Missing required param 'param1'\");\n    if (!param2) throw new Error(\"Missing required param 'param2'\");\n\n    // Add transformation logic here\n\n    return res;\n  } catch (e) {\n    gs.error(`${lib} ${func} - ${e}`);\n    throw new Error(e);\n  }\n}\n\n/**\n * TIPS\n * Make sure to flag mutates data if your data resource changes any data\n * Properties structure (these are the inputs for your data resource):\n [\n    {\n        \"name\": \"param1\",\n        \"label\": \"Param 1\",\n        \"description\": \"An example of the first param as a string, mandatory\",\n        \"readOnly\": false,\n        \"fieldType\": \"string\",\n        \"mandatory\": true,\n        \"defaultValue\": \"\"\n    },\n    {\n        \"name\": \"param2\",\n        \"label\": \"Param 2\",\n        \"description\": \"An example of the second param as a number, mandatory\",\n        \"readOnly\": false,\n        \"fieldType\": \"number\",\n        \"mandatory\": true,\n        \"defaultValue\": \"\"\n    },\n    {\n        \"name\": \"param3\",\n        \"label\": \"Param 3\",\n        \"description\": \"An example of the third param as a boolean, optional\",\n        \"readOnly\": false,\n        \"fieldType\": \"boolean\",\n        \"mandatory\": false,\n        \"defaultValue\": \"\"\n    }\n  ]\n */\n```\n"
  },
  {
    "path": "Client-Side Components/UX Data Broker Transform/starter-template/template.js",
    "content": "/**\n * This is a starter template for a transform data broker with:\n * - JSDoc type annotations\n * - Destructured inputs\n * - Try/catch\n * - Error logging then throw (Enables error logging but still allows the data resource to fail)\n * @param {{param1: string, param2: number, param3?: boolean}} inputs inputs from the properties field above, param1 and param2 are mandatory\n * @returns {string} the value returned\n */\nfunction transform({ param1, param2, param3 }) {\n  const lib = \"Data Broker\";\n  const func = \"<insert data broker name here>\";\n\n  /** @type {string} */\n  let res;\n\n  try {\n    // Handle required param checks\n    if (!param1) throw new Error(\"Missing required param 'param1'\");\n    if (!param2) throw new Error(\"Missing required param 'param2'\");\n\n    // Add logic here\n\n    return res;\n  } catch (e) {\n    gs.error(`${lib} ${func} - ${e}`);\n    throw new Error(e);\n  }\n}\n\n/**\n * TIPS\n * Make sure to flag mutates data if your data resource changes any data\n * Properties structure (these are the inputs for your data resource):\n [\n    {\n        \"name\": \"param1\",\n        \"label\": \"Param 1\",\n        \"description\": \"An example of the first param as a string, mandatory\",\n        \"readOnly\": false,\n        \"fieldType\": \"string\",\n        \"mandatory\": true,\n        \"defaultValue\": \"\"\n    },\n    {\n        \"name\": \"param2\",\n        \"label\": \"Param 2\",\n        \"description\": \"An example of the second param as a number, mandatory\",\n        \"readOnly\": false,\n        \"fieldType\": \"number\",\n        \"mandatory\": true,\n        \"defaultValue\": \"\"\n    },\n    {\n        \"name\": \"param3\",\n        \"label\": \"Param 3\",\n        \"description\": \"An example of the third param as a boolean, optional\",\n        \"readOnly\": false,\n        \"fieldType\": \"boolean\",\n        \"mandatory\": false,\n        \"defaultValue\": \"\"\n    }\n  ]\n */\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count All Open Incidents Per Priority/readme.md",
    "content": "# Count Open Incidents per Priority Using GlideAggregate\n\n## Overview\nThis script dynamically calculates the **number of open incidents** for each priority level using **server-side scripting** in ServiceNow.  \nPriority levels typically include:  \n+ 1 – Critical  \n+ 2 – High  \n+ 3 – Moderate  \n+ 4 – Low  \n\nThe solution leverages **GlideAggregate** to efficiently count records grouped by priority. This approach is useful for:  \n+ Dashboards  \n+ Automated scripts  \n+ Business rules  \n+ SLA monitoring and reporting  \n\n---\n\n## Table and Fields\n+ **Table:** `incident`  \n+ **Fields:** `priority`, `state`  \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count All Open Incidents Per Priority/script.js",
    "content": "(function() {\n    // Create GlideAggregate object on 'incident' table\n    var ga = new GlideAggregate('incident');\n    \n    // Filter only open incidents (state != Closed (7))\n    ga.addQuery('state', '!=', 7);\n    \n    // Group results by priority\n    ga.groupBy('priority');\n    \n    // Count number of incidents per priority\n    ga.addAggregate('COUNT');\n    \n    ga.query();\n    \n    gs.info('Open Incidents by Priority:');\n    \n    while (ga.next()) {\n        var priority = ga.priority.getDisplayValue(); // e.g., Critical, High\n        var count = ga.getAggregate('COUNT');\n        gs.info(priority + ': ' + count);\n    }\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/README.md",
    "content": "Count Active Incidents Assigned to Inactive Users \n\nThis script uses GlideAggregate to efficiently count the number of active incidents assigned to inactive users. \n\nThis is a crucial task for maintaining data hygiene and preventing incidents from being stalled due to inactive assignees. \n\nOverview The script performs the following actions: Initializes GlideAggregate: Creates an aggregate query on the incident table. \n\nFilters Records: Uses addQuery() to restrict the search to incidents that are both active (true) and assigned to a user whose active status is false.\n\nThis filter uses a \"dot-walk\" on the assigned_to field to check the user's active status directly within the query.\n\nAggregates by Count: Uses addAggregate() to count the number of incidents, grouping the results by assigned_to user.\n\nExecutes and Logs: Runs the query, then loops through the results. \n\nFor each inactive user found, it logs their name and the number of active incidents assigned to them. Use case This script is essential for regular cleanup and maintenance. \n\nIt can be used in: Scheduled Job: Automatically run the script daily or weekly to monitor for and report on incidents assigned to inactive users. \n\nInstallation As a Scheduled Job Navigate to System Definition > Scheduled Jobs.\n\n\nClick New and select Automatically run a script of your choosing. Name the job (e.g., Find Incidents Assigned to Inactive Users).\n\n\nSet your desired frequency and time. Paste the script into the Run this script field. Save and activate the job. As a Fix Script Navigate to System Definition > Fix Scripts.\n\nClick New. Name it (e.g., Find Active Incidents with Inactive Assignee).\n\n\nPaste the script into the Script field. Run the script to see the results in the System Log. \n\n\nCustomization Targeted tables: Change the table name from incident to task or any other table with an assigned_to field to check for active records assigned to inactive users.\n\n\nAutomated reassignment: Extend the script to automatically reassign the incidents to a group or another user. This is a common practice to ensure that tickets do not get stuck in the queue. Email notification: Instead of logging the information, modify the script to send an email notification to the group manager or another stakeholder with the list of incidents needing attention.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count Inactive Users with Active incidents/script.js",
    "content": "/* \nThis script uses GlideAggregate to find the number of active incidents\nthat are assigned to users who are currently marked as inactive.\nGlideAggregate is used instead of a standard GlideRecord query\nbecause it is more efficient for performing calculations like COUNT \ndirectly in the database.\n*/\nvar ga = new GlideAggregate('incident');\n\n// Query for active incidents.\nga.addQuery('active', true);\n\n// Use dot-walking to query for incidents assigned to inactive users.\nga.addQuery('assigned_to.active', false);\n\n// Add an aggregate function to count the incidents, grouped by the assigned user.\nga.addAggregate('COUNT', 'assigned_to');\n\n// Execute the query.\nga.query();\n\n// Process the results of the aggregate query.\nwhile (ga.next()) {\n    // Get the display name of the inactive user from the current record.\n    var inactiveUser = ga.assigned_to.getDisplayValue();\n    \n    // Get the count of active incidents for this specific user.\n    var incidentCount = ga.getAggregate('COUNT', 'assigned_to');\n    \n    // Log the result to the system logs.\n    gs.info(\"Inactive user \" + inactiveUser + \" has \" + incidentCount + \" active incidents.\");\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count incidents based on category/Count incidents based on category.js",
    "content": "var incidents = new GlideAggregate('incident');\nincidents.addAggregate('count', 'category');\nincidents.orderBy('category');\nincidents.query();\nwhile(incidents.next())\n{\nvar categories = incidents.category;\nvar count = incidents.getAggregate('count', 'category');\ngs.info('Total number of ' +categories + ' categories are '+count);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count incidents based on category/README.md",
    "content": "Go to background and execute the script then you will get the total count of incidents based on category.\n\nIt will easily help to segregate the data based on categories/priority/ etc anything.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count open Incidents per Priority and State using GlideAggregate/README.md",
    "content": "# Count open Incidents per Priority and State using GlideAggregate\n\n## Overview\nThis script will dynamically calculate the **number of open incidents** for each priority level and also give you a total for what \ncurrent state the Incident is in using **server-side scripting**\nPriority levels typically include:  \n+ 1 – Critical  \n+ 2 – High  \n+ 3 – Moderate  \n+ 4 – Low\n\nIncident State typically include:\n+ New\n+ In Progress\n+ On Hold\n+ Resolved\n+ Closed\n+ Canceled\n\nThe scripting solution leverages **GlideAggregate** to efficiently count records grouped by priority and state. This scripts approach\nis useful for:\n+ Dashboards\n+ Business Rules\n+ SLA monitoring and reporting\n \n--\n## Table and Fields\n+ **Table:** Task\n+ **Fields:** Priority, State\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Count open Incidents per Priority and State using GlideAggregate/script.js",
    "content": "/*\n*Going to define the Incident Closed and Canceled state since we dont want those records as part of our query. \n*Also going to leverage the IncidentStateSNC script from ServiceNow\n*/\n\n/*\n*Going to define the Incident Closed and Canceled state since we dont want those records as part of our query. \n*/\nvar incident_close = IncidentStateSNC.CLOSED;\nvar incident_canceled = IncidentStateSNC.CANCELED;\nvar incident_state_query = incident_close + \",\" + incident_canceled;\n\n/*\n*Creating the Incident State value object that will house the correct incident state since we are working from the Task table.\n*Leveraging the IncidentStateSNC script from ServiceNow to get the values that they should be\n*/\nvar incident_states = {\n  '1':'New',\n  '2':'In Progress',\n  '3':'On Hold',\n  '6':'Resolved',\n  '7':'Closed',\n  '8':'Canceled'\n};\n\n//Going to create the GlideAggregate object\nvar ga = new GlideAggregate('task');\nga.addQuery('state', 'NOT IN', incident_state_query); //Going to exclude the canceled and closed incidents\nga.addQuery('sys_class_name', 'incident'); //Since working on the Task table need to grab only Incident records with task type\nga.groupBy('state');\nga.groubBy('count');\nga.addAggregate('COUNT');\nga.query()\n\ngs.info('The following is a list of Open Incident records');\n\nwhile (ga.next()) {\n\n  var priorityValue = ga.getDisplayValue('priority');\n  var state = ga.getValue('state');\n  var count = ga.getValue('COUNT');\n\n  gs.info(\"There are a total of: \" + count + \" Incidents with a priority of \" + priorityValue + \" and in a state of \" + incident_states[state]);\n  \n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/README.md",
    "content": "Key features\nAutomatic problem creation: The script uses a GlideAggregate query to count the number of incidents opened for a specific CI.\nTime-based threshold: Problems are only created if more than five incidents are opened within a 60-minute window.\nTargeted incidents: The script focuses on incidents related to the same CI, making it effective for identifying recurring infrastructure issues.\nCustomizable conditions: The number of incidents and the time frame are easily configurable within the script.\nEfficient performance: The use of GlideAggregate ensures the database is queried efficiently, minimizing performance impact. \n\nHow it works\nThe script is designed to be executed as a server-side script, typically within a Business Rule. When an incident is inserted or updated, the script performs the following actions:\nQueries incidents: It executes a GlideAggregate query on the incident table.\nSets conditions: The query is filtered to count all incidents that meet the following conditions:\nSame CI: The incident's cmdb_ci matches the cmdb_ci of the current record.\nWithin the last hour: The opened_at time is within the last 60 minutes.\nEvaluates count: After the query is run, the script checks if the count of matching incidents exceeds the threshold (in this case, 5).\nCreates problem: If the threshold is exceeded, a new problem record is initialized.\nThe short_description is automatically populated with a descriptive message.\nThe cmdb_ci is linked to the new problem record.\nThe new record is then inserted into the database. \nImplementation steps\nCreate a Business Rule:\nNavigate to System Definition > Business Rules.\nClick New.\nConfigure the Business Rule:\nName: Auto Create Problem from Multiple Incidents\nTable: Incident [incident]\nAdvanced: true\nWhen to run: Select after and check the Insert and Update checkboxes. This ensures the script runs after an incident has been saved.\nFilter conditions: Optionally, you can add conditions to limit when the script runs (e.g., cmdb_ci is not empty).\nAdd the script:\nNavigate to the Advanced tab.\nCopy and paste the script into the Script field.\nCustomize (optional):\nNumber of incidents: Change the > 5 value to adjust the threshold.\nTime frame: Adjust the gs.minutesAgoStart(60) value to change the time window.\nOther conditions: If you need to check for specific incident statuses or categories, add more addQuery lines to the GlideAggregate call.\nSave the Business Rule. \nCustomization examples\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Create Problem based on incident volume/script.js",
    "content": "var incidentCheck = new GlideAggregate('incident');\n  incidentCheck.addQuery('cmdb_ci', current.cmdb_ci); // Or any specific CI\n  incidentCheck.addQuery('opened_at', '>', 'javascript:gs.minutesAgoStart(60)');\n  incidentCheck.addAggregate('COUNT');\n  incidentCheck.query();\n  \n  if (incidentCheck.next() && parseInt(incidentCheck.getAggregate('COUNT')) > 5) {\n      var problem = new GlideRecord('problem');\n      problem.initialize();\n      problem.short_description = 'Multiple incidents reported for CI: ' + current.cmdb_ci.getDisplayValue();\n      problem.cmdb_ci = current.cmdb_ci;\n      problem.insert();\n  }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Find Oldest Open Incidents per Group/README.md",
    "content": "ServiceNow Script: Find Oldest Open Incidents per Group\nThis script leverages GlideAggregate to efficiently find the oldest active incident for each assignment group. This is a powerful tool for monitoring and reporting on potential service level agreement (SLA) risks and improving incident management processes.\nOverview\nThe script performs the following actions:\nInitializes GlideAggregate: Creates an aggregate query on the incident table.\nFilters Active Incidents: Uses addActiveQuery() to restrict the search to only open (active) incidents.\nAggregates by Minimum Date: Finds the minimum (MIN) opened_at date, which represents the oldest record.\nGroups by Assignment Group: Groups the results by the assignment_group to get a separate result for each team.\nIterates and Logs: Loops through the query results and logs the assignment group and the opening date of its oldest open incident.\nHow to use\nThis script is intended to be used in a server-side context within a ServiceNow instance. Common use cases include:\nScheduled Job: Run this script on a regular schedule (e.g., daily) to generate a report on aging incidents.\nScript Include: Incorporate the logic into a reusable function within a Script Include, allowing other scripts to call it.\n\nUse code with caution.\n\nInstallation\nAs a Scheduled Job\nNavigate to System Definition > Scheduled Jobs.\nClick New and select Automatically run a script of your choosing.\nName the job (e.g., Find Oldest Open Incidents).\nSet your desired frequency and time.\nPaste the script into the Run this script field.\nSave and activate the job.\nAs a Script Include\nNavigate to System Definition > Script Includes.\nClick New.\nName it (e.g., IncidentHelper).\nAPI Name: global.IncidentHelper\n\n\nCustomization\nChange the output: Modify the gs.info() line to instead write to a custom log, send an email, or create a report.\nRefine the query: Add more addQuery() statements to filter incidents by other criteria, such as priority or category.\nChange the aggregate: Use MAX instead of MIN to find the newest incident in each group.\nGet incident details: To get the actual incident record (e.g., its number), you would need to perform a secondary GlideRecord query based on the aggregated data.\nDependencies\nThis script uses standard ServiceNow APIs (GlideAggregate, gs). No external libraries are required.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Find Oldest Open Incidents per Group/script.js",
    "content": "var ga = new GlideAggregate('incident');\n    ga.addActiveQuery();\n    ga.addAggregate('MIN', 'opened_at');\n    ga.groupBy('assignment_group');\n    ga.query();\n\n    while (ga.next()) {\n        var group = ga.assignment_group.getDisplayValue();\n        var oldestIncidentDate = ga.getAggregate('MIN', 'opened_at');\n        gs.info(\"Oldest open incident for \" + group + \" was created on: \" + oldestIncidentDate);\n    }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Get Incident Count by Priority/README.md",
    "content": "Many of times, you are being asked to report the total count of incidents grouped by Incident priority. This code  will be helpful to get that information. The code could be further enhanced or modified to get more detailed requirements like getting the latest incident being raised within the respective priority.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Get Incident Count by Priority/get-incident-count-by-priority.js",
    "content": "// Get the count of Incidents grouped by Priority\nvar incidentGr = new GlideAggregate('incident');\nincidentGr.addAggregate('COUNT', 'priority');\nincidentGr.query();\nwhile (incidentGr.next()) {\n    gs.print(incidentGr.priority.getDisplayValue() + '=' + incidentGr.getAggregate('COUNT', 'priority'));\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Get top 5 CIs with most number of Open Incidents/README.md",
    "content": "Use-case:\n**Fetch Top 5 CIs with the most number of Open Incidents along with the count**\n\nType of Script writted: **Background Script**\n\n**How the code works:**\nThe code uses the GlideAggregate API to efficiently calculate and retrieve the results -\n1. A GlideAggregate query is initiated on the Incident table. The query is restricted to only active Incidents.\n2. The query instructs the database to COUNT records grouped by the configuration item(cmdb_ci).\n3. Furthermore, the records are instructed to be in descending order of number of incidents related to one CI, also a limit\n   of 5 records are applied to be fetched.\n4. The query is executed and a loop is iterated over these 5 records to fetch and print\n   the CI name and its corresponding incident count.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Get top 5 CIs with most number of Open Incidents/getCIwithmostActiveInc.js",
    "content": "var countOfCI = 5;\nvar inc = new GlideAggregate('incident');\ninc.addActiveQuery();\ninc.addAggregate('COUNT', 'cmdb_ci');\ninc.groupBy('cmdb_ci');\ninc.orderByAggregate('COUNT', 'cmdb_ci');\ninc.setLimit(countOfCI);\ninc.query();\ngs.info('---Top ' + countOfCI + ' CIs with Most Open Incidents---');\n\n\nwhile (inc.next()) {\n    var ciName;\n    var ciSysID = inc.cmdb_ci;\n    var count = inc.getAggregate('COUNT', 'cmdb_ci');\n    var ci = new GlideRecord('cmdb_ci');\n    if (ci.get(ciSysID)) {                  //retrieving the CI record\n        ciName = ci.name.toString();\n    } else {\n        ciName = 'Invalid CI';\n    }\n\n    gs.info('. CI: ' + ciName + ' | Count of Inc: ' + count);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Group Count/GlideQuery.js",
    "content": "function getGroupCountGQ(table, groupBy, min) {\n    var gqCount = new GlideQuery(table)\n\t\t.aggregate('count', groupBy)\n\t\t.whereNotNull(groupBy)\n\t\t.groupBy(groupBy)\n\t\t.having('count', groupBy, '>=', min)\n\t\t.select()\n\t\t.toArray(10);\n    return gqCount;\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Group Count/GlideQuery_readme.md",
    "content": "# Group Count\nRedo mostly similar example with GlideQuery\n\n## Example Script\n```javascript\nvar table = \"sys_user\";\nvar groupBy = \"employee_number\";\nvar minGroupCount = 2;\n\nvar countOutputGQ = getGroupCountGQ(\n    table, groupBy, minGroupCount\n);\ngs.info('GlideQuery Output is ' + JSON.stringify(countOutputGQ, null, 4));\n\n```\n## Example Result\n```json\n[\n {\n        \"group\": {\n            \"employee_number\": \"321\"\n        },\n        \"count\": 10\n    },\n    {\n        \"group\": {\n            \"employee_number\": \"657\"\n        },\n        \"count\": 7\n    },\n    {\n        \"group\": {\n            \"employee_number\": \"831\"\n        },\n        \"count\": 3\n    }\n]\n```"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Group Count/README.md",
    "content": "# Group Count\nSometimes you need to filter a list by group count (e.g. finding duplicates) unfortunately as of now the UI(16) doesn't provide a way to filter records based on group count. This function should help with that.\n\n## Example Script\n```javascript\nvar table = \"sys_user\";\nvar encodedQuery = \"employee_numberISNOTEMPTY\";\nvar groupBy = \"employee_number\";\nvar minGroupCount = 2;\nvar transformFn = function(ga, groupCount) {\n  return {\n    \"employee_number\": ga.getValue(\"employee_number\"),\n    \"group_count\": groupCount\n  };\n};\nvar countOutput = getGroupCount(\n  table, encodedQuery, groupBy, minGroupCount, transformFn\n);\ngs.info('Output is ' + JSON.stringify(countOutput, null, 4));\n```\n## Example Result\n```json\n[\n  {\n    \"employee_number\": \"000051\",\n    \"group_count\": \"2\"\n  },\n  {\n    \"employee_number\": \"000055\",\n    \"group_count\": \"2\"\n  },\n  {\n    \"employee_number\": \"000058\",\n    \"group_count\": \"3\"\n  }\n]\n```"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Group Count/code.js",
    "content": "var getGroupCount = function (table, query, groupBy, min, transform) {\n    var gaCount = new GlideAggregate(table);\n    gaCount.addAggregate(\"COUNT\", groupBy);\n    gaCount.addEncodedQuery(query);\n    gaCount.groupBy(groupBy);\n    gaCount.query();\n    var result = [];\n    while (gaCount.next()) {\n        var groupCount = gaCount.getAggregate(\"COUNT\", groupBy);\n        if (groupCount >= min) result.push(transform(gaCount, groupCount));\n    }\n    return result;\n};"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Grouping by three columns/README.md",
    "content": "**GlideAggregate**\n\nScript which allows to group by three columns (you can expand or decrease number of columns to fit your needs). In this example, the script allows counting number of active changes grouped by state, type and priority fields.\n\nSpecial thanks to *SN-AJB* for his post about GlideAggregate: https://developer.servicenow.com/blog.do?p=/post/glideaggregate/. Absolutely worth taking a look!\n\n**Example execution logs**\n\n![Logs](ScreenShot_1.PNG)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Grouping by three columns/script.js",
    "content": "//GlideAggregate script for grouping by three columns\n//In this case it is displaying the number of active changes grouped by state, type and priority\n\nvar gaChange = new GlideAggregate('change_request');\ngaChange.addAggregate('COUNT', 'state');\n\n//Chained orderBy allows to group by mutliply columns\ngaChange.orderBy('state');\ngaChange.orderBy('type');\ngaChange.orderBy('priority');\n\ngaChange.addActiveQuery();\ngaChange.query();\n\nwhile (gaChange.next()) {\n    //Getting values of fields needed to logging \n    var ChangeCount = gaChange.getAggregate('COUNT', 'state');\n    var state = gaChange.getDisplayValue('state');\n    var type = gaChange.getValue('type');\n    var priority = gaChange.getDisplayValue('priority');\n\n    //Logging information about the number of changes in group of state, type and priority\n    gs.info('Active changes in state: [' + state + '] with type: [' + type + '] and priority: [' + priority + '] = ' + ChangeCount);\n\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Improve incident handling/README.md",
    "content": "Suppose you want to gather data about incident resolution in your system. \nSpecifically, you need to find the total number of incidents, the average time to resolution (in hours), and the number of incidents per assignment group. \nThis information can help analyze the efficiency of different groups and improve incident handling.\n\nBelow are the added Aggregations:\n\ninc.addAggregate('COUNT') gets the total count of resolved incidents.\ninc.addAggregate('AVG', 'calendar_duration') calculates the average calendar duration for incident resolution (measured in hours).\ninc.addAggregate('COUNT', 'assignment_group') counts incidents per assignment group, and ga.groupBy('assignment_group') groups the result by assignment group to produce totals per group.\n\nResult:\n- It fetches total counts, average resolution time, and group-specific counts.\n- Logs the total number of resolved incidents and the average resolution time.\n- For each assignment group, it logs the group’s sys_id and the corresponding incident count.\n\nBenefits of Using GlideAggregate:\n- Reduces the number of queries and records you need to process, as it performs calculations at the database level.\n- Works well with large datasets, making it suitable for summary reports and dashboards.\n- Allows grouping and multiple aggregations (e.g., AVG, COUNT, MIN, MAX) on various fields in a single query.\n  \nThis GlideAggregate example provides a consolidated view of incident resolution statistics, which can aid in optimizing group efficiency and improving response times.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Improve incident handling/script.js",
    "content": "// Create a GlideAggregate instance for the Incident table\nvar inc = new GlideAggregate('incident');\n\n// Filter for resolved incidents only\ninc.addQuery('state', 6); \n\n// Add aggregations\ninc.addAggregate('COUNT');                    // Total number of incidents\ninc.addAggregate('AVG', 'calendar_duration'); // Average resolution time in hours\ninc.addAggregate('COUNT', 'assignment_group'); // Count of incidents per assignment group\ninc.groupBy('assignment_group'); // Group by assignment group to get the count per group\ninc.query();\n\nvar totalIncidents = 0;\nvar averageResolution = 0;\nvar results = [];\n\nwhile (inc.next()) {\n    totalIncidents = inc.getAggregate('COUNT');\n    averageResolution = inc.getAggregate('AVG', 'calendar_duration');\n\n    // Get assignment group and incident count per group\n    var groupSysId = inc.assignment_group.toString();\n    var groupIncidentCount = inc.getAggregate('COUNT', 'assignment_group');\n\n    results.push({\n        groupSysId: groupSysId,\n        groupIncidentCount: groupIncidentCount\n    });\n}\n\n// Display results in logs\ngs.info(\"Total Resolved Incidents are: \" + totalIncidents);\ngs.info(\"Average Resolution Time (hours) is: \" + averageResolution);\n\nresults.forEach(function(result) {\n    gs.info(\"Assignment Group: \" + result.groupSysId + \" | Incident Count: \" + result.groupIncidentCount);\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Incident Analysis and Resolution Calculation using Glideaggregate/Incident_analysis_Resolution_Calculation_GlideAggregate.js",
    "content": "var ga = new GlideAggregate('incident');\nga.addQuery('state', '!=', 'Closed'); // Filter for open incidents only\nga.groupBy('assigned_to');\nga.addAggregate('COUNT', 'number');\nga.addAggregate('AVG', 'time_to_close');\nga.query();\nwhile (ga.next()) {\n  var assigned_to = ga.getDisplayValue('assigned_to');\n  var count = ga.getAggregate('COUNT', 'number');\n  var avgTimeToClose = ga.getAggregate('AVG', 'time_to_close');\n  gs.info('Assigned to: ' + assigned_to + ', Incident Count: ' + count + ', Average Time to Close: ' + avgTimeToClose);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Incident Analysis and Resolution Calculation using Glideaggregate/README.md",
    "content": "These scripts explain the usage of Glideaggregate. This script helps to group the incidents by their assigned user, counts the number of incidents assigned to each user, and calculates the average time it takes to close these incidents.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Incident resolution percentile by assignment group/PercentileMetrics.js",
    "content": "// Script Include: PercentileMetrics\n// Purpose: Compute percentile resolution times by group using nearest-rank selection.\n// Scope: global or scoped. Client callable false.\n\nvar PercentileMetrics = Class.create();\nPercentileMetrics.prototype = {\n  initialize: function() {},\n\n  /**\n   * Compute percentiles for incident resolution times by group.\n   * @param {Object} options\n   *   - windowDays {Number} lookback window (default 30)\n   *   - groupField {String} field to group by (default 'assignment_group')\n   *   - percentiles {Array<Number>} e.g. [0.5, 0.9]\n   *   - table {String} table name (default 'incident')\n   * @returns {Array<Object>} [{ group: <sys_id>, count: N, avgMins: X, p: { '0.5': v, '0.9': v } }]\n   */\n  resolutionPercentiles: function(options) {\n    var opts = options || {};\n    var table = opts.table || 'incident';\n    var groupField = opts.groupField || 'assignment_group';\n    var windowDays = Number(opts.windowDays || 30);\n    var pct = Array.isArray(opts.percentiles) && opts.percentiles.length ? opts.percentiles : [0.5, 0.9];\n\n    // Build date cutoff for resolved incidents\n    var cutoff = new GlideDateTime();\n    cutoff.addDaysUTC(-windowDays);\n\n    // First pass: find candidate groups with counts and avg\n    var ga = new GlideAggregate(table);\n    ga.addQuery('resolved_at', '>=', cutoff);\n    ga.addQuery('state', '>=', 6); // resolved/closed states\n    ga.addAggregate('COUNT');\n    ga.addAggregate('AVG', 'calendar_duration'); // average of resolution duration\n    ga.groupBy(groupField);\n    ga.query();\n\n    var results = [];\n    while (ga.next()) {\n      var groupId = ga.getValue(groupField);\n      var count = parseInt(ga.getAggregate('COUNT'), 10) || 0;\n      if (!groupId || count === 0) continue;\n\n      // Second pass: ordered sample to pick percentile ranks\n      var ordered = new GlideRecord(table);\n      ordered.addQuery('resolved_at', '>=', cutoff);\n      ordered.addQuery('state', '>=', '6');\n      ordered.addQuery(groupField, groupId);\n      ordered.addNotNullQuery('closed_at');\n      // Approx resolution minutes using dateDiff: closed_at - opened_at in minutes\n      ordered.addQuery('opened_at', 'ISNOTEMPTY');\n      ordered.addQuery('closed_at', 'ISNOTEMPTY');\n      ordered.orderBy('closed_at'); // for stability\n      ordered.query();\n\n      var durations = [];\n      while (ordered.next()) {\n        var opened = String(ordered.getValue('opened_at'));\n        var closed = String(ordered.getValue('closed_at'));\n        var mins = gs.dateDiff(opened, closed, true) / 60; // seconds -> minutes\n        durations.push(mins);\n      }\n      durations.sort(function(a, b) { return a - b; });\n\n      var pvals = {};\n      pct.forEach(function(p) {\n        var rank = Math.max(1, Math.ceil(p * durations.length)); // nearest-rank\n        pvals[String(p)] = durations.length ? Math.round(durations[rank - 1]) : 0;\n      });\n\n      results.push({\n        group: groupId,\n        count: count,\n        avgMins: Math.round(parseFloat(ga.getAggregate('AVG', 'calendar_duration')) / 60),\n        p: pvals\n      });\n    }\n    return results;\n  },\n\n  type: 'PercentileMetrics'\n};\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Incident resolution percentile by assignment group/README.md",
    "content": "# Incident resolution percentile by assignment group\n\n## What this solves\nLeaders often ask for P50 or P90 of incident resolution time by assignment group. Out-of-box reports provide averages, but percentiles are more meaningful for skewed distributions. This utility computes configurable percentiles from incident resolution durations.\n\n## Where to use\n- Script Include callable from Background Scripts, Scheduled Jobs, or Flow Actions\n- Example Background Script is included\n\n## How it works\n- Uses `GlideAggregate` to get candidate groups with resolved incidents in a time window\n- For each group, queries resolved incidents ordered by resolution duration (ascending)\n- Picks percentile ranks (for example 0.5, 0.9) using nearest-rank method\n- Returns a simple object per group with count, average minutes, and requested percentiles\n\n## Configure\n- `WINDOW_DAYS`: number of days to look back (default 30)\n- `GROUP_FIELD`: field to group by (default `assignment_group`)\n- Percentiles array (for example `[0.5, 0.9]`)\n\n## References\n- GlideAggregate API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideAggregate/concept/c_GlideAggregateAPI.html\n- GlideRecord API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideRecord/concept/c_GlideRecordAPI.html\n- GlideDateTime API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Incident resolution percentile by assignment group/example_background_usage.js",
    "content": "// Background Script: example usage for PercentileMetrics\n(function() {\n  var util = new PercentileMetrics();\n  var out = util.resolutionPercentiles({\n    windowDays: 30,\n    groupField: 'assignment_group',\n    percentiles: [0.5, 0.9, 0.95]\n  });\n\n  out.forEach(function(r) {\n    gs.info('Group=' + r.group + ' count=' + r.count + ' avg=' + r.avgMins + 'm P50=' + r.p['0.5'] + 'm P90=' + r.p['0.9'] + 'm P95=' + r.p['0.95'] + 'm');\n  });\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/LicensedUserCount/licensed_user_count_by_role.js",
    "content": "(function() {\n    // Purpose: Count how many users hold each licensed role\n    // Roles: sys_approver, itil, business_stakeholder, admin\n\n    var roles = ['sys_approver', 'itil', 'business_stakeholder', 'admin'];\n\n    for (var i = 0; i < roles.length; i++) {\n        var roleName = roles[i];\n\n        var ga = new GlideAggregate('sys_user_has_role');\n        ga.addQuery('role.name', roleName);\n        ga.addAggregate('COUNT');\n        ga.query();\n\n        if (ga.next()) {\n            var count = parseInt(ga.getAggregate('COUNT'), 10);\n            gs.info(roleName + ': ' + count + ' licensed users');\n        } else {\n            gs.info(roleName + ': no users found.');\n        }\n    }\n\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/LicensedUserCount/readme.md",
    "content": "# Licensed User Count by Role Using GlideAggregate\n\n# Overview\nThis script counts how many **licensed users** hold specific ServiceNow roles using the `GlideAggregate` API.  \nIt’s useful for **license compliance**, **role audits**, and **access management reporting**.\n\nThe licensed roles analyzed:\n- sys_approver  \n- itil  \n- business_stakeholder  \n- admin  \n\n# Objective\nTo provide a simple, fast, and accurate way to count licensed users per role directly at the database level using `GlideAggregate`.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/List of Child Incident of All Incidents/README.md",
    "content": "This code will show all the Incidents who are Parent Incidents based on filter condition query with how many Child Incidents they have.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/List of Child Incident of All Incidents/allChildIncidents.js",
    "content": "var inc = new GlideAggregate('incident');\ninc.addEncodedQuery('parent_incidentISNOTEMPTY'); // This encoded query can be modified to more condition based on requirement.\ninc.groupBy('parent_incident');\ninc.addAggregate('COUNT');\ninc.query();\n\n\nwhile(inc.next())\n{\n\tgs.info('Number of Child Incident of '+ inc.getDisplayValue('parent_incident')+ ' are ' +inc.getAggregate('COUNT')+ '.');\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/List of Managers in User Table/ListOfManagers.js",
    "content": "\nvar mngrs = [];\nvar usr = new GlideAggregate('sys_user');\nusr.addEncodedQuery(\"managerISNOTEMPTY\"); // If you want this based on other condition also like Depenartment, Service Line, etc, you can add it on Query accordingly\nusr.groupBy('manager');\nusr.query();\n\nwhile(usr.next())\n{\n\tmngrs.push(usr.manager.getDisplayValue());\n\n}\ngs.info(\"Please find the list Managers - \" + mngrs);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/List of Managers in User Table/README.md",
    "content": "This code will display the list of Users who are at Managers level and someone's manager. \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/List the incident priority count under each category/README.md",
    "content": "Purpose: This piece of code will be helpful to get the count of incidents by Priority under each Category.\n\nSample Output:\n==============\n*** Script: Category Name:  --> 4\n*** Script: Priority-1 = 2\n*** Script: Priority-2 = 1\n*** Script: Priority-4 = 1\n*** Script: Category Name: Database --> 3\n*** Script: Priority-1 = 1\n*** Script: Priority-4 = 1\n*** Script: Priority-5 = 1\n*** Script: Category Name: Hardware --> 10\n*** Script: Priority-1 = 5\n*** Script: Priority-3 = 2\n*** Script: Priority-5 = 3\n*** Script: Category Name: Inquiry / Help --> 84\n*** Script: Priority-1 = 10\n*** Script: Priority-2 = 2\n*** Script: Priority-3 = 13\n*** Script: Priority-4 = 17\n*** Script: Priority-5 = 42\n*** Script: Category Name: Network --> 12\n*** Script: Priority-1 = 2\n*** Script: Priority-2 = 2\n*** Script: Priority-3 = 1\n*** Script: Priority-4 = 2\n*** Script: Priority-5 = 5\n*** Script: Category Name: Software --> 131\n*** Script: Priority-1 = 15\n*** Script: Priority-2 = 15\n*** Script: Priority-3 = 23\n*** Script: Priority-4 = 28\n*** Script: Priority-5 = 50\n\n\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/List the incident priority count under each category/code.js",
    "content": "// Get the count of Incidents by Category and then by Priority.\n\nvar incCATGR = new GlideAggregate('incident');\nincCATGR.addAggregate('COUNT', 'category');\nincCATGR.orderBy('category');\nincCATGR.query();\n\nwhile (incCATGR.next()) {\n\tvar cat = incCATGR.category;\n\tgs.print(\"Category Name: \" +incCATGR.category.getDisplayValue() + ' --> ' + incCATGR.getAggregate('COUNT', 'category'));\n  var incPriorityGR = new GlideAggregate('incident');\n\tincPriorityGR.addQuery('category', incCATGR.category);\n  incPriorityGR.addAggregate('COUNT', 'priority');\n\tincPriorityGR.orderBy('priority');\n  incPriorityGR.query();\n\n\twhile(incPriorityGR.next()){\n    gs.print(\"Priority-\" +incPriorityGR.priority + \" = \" +incPriorityGR.getAggregate('COUNT', 'priority'));\n\t}\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/SLA Compliance Ratio by Assignment Group/readme.md",
    "content": "Overview\n\nThis script calculates the SLA breach percentage for each assignment group based on closed incidents in ServiceNow.\nIt leverages GlideAggregate to count both total SLAs and breached SLAs efficiently, providing key SLA performance insights.\n\nUseful for:\n\t•\tSLA dashboards\n\t•\tSupport performance tracking\n\t•\tService improvement reports\n\nObjective\n\nTo determine, for each assignment group:\n\t•\tHow many SLAs were closed\n\t•\tHow many of those breached\n\t•\tThe resulting SLA compliance percentage\n\nScript Logic\n\t1.\tQuery the task_sla table.\n\t2.\tFilter for closed SLAs linked to incidents.\n\t3.\tAggregate total SLAs (COUNT) and breached SLAs (COUNT, 'breach', 'true').\n\t4.\tGroup results by assignment group.\n\t5.\tCalculate breach percentage.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/SLA Compliance Ratio by Assignment Group/script.js",
    "content": "(function() {\n    var ga = new GlideAggregate('task_sla');\n    ga.addEncodedQuery('task.sys_class_name=incident^active=false');\n    ga.addAggregate('COUNT'); // All SLAs\n    ga.addAggregate('COUNT', 'breach', 'true'); // breached SLAs\n    ga.groupBy('task.assignment_group');\n    ga.query();\n\n    gs.info('SLA Compliance Ratio by Group');\n\n    while (ga.next()) {\n        var total = parseInt(ga.getAggregate('COUNT'));\n        var breached = parseInt(ga.getAggregate('COUNT', 'breach', 'true'));\n        var rate = breached ? ((breached / total) * 100).toFixed(2) : 0;\n        gs.info(ga.getDisplayValue('task.assignment_group') + ': ' + rate + '% breached (' + breached + '/' + total + ')');\n    }\n    \n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/SLA Count by Assignment Group/README.md",
    "content": "# SLA Count by Assignment Group\n\nThis script retrieves active SLAs (task_sla table) grouped by assignment groups in ServiceNow. It counts the number of active SLAs per group and logs the result, displaying the group name and corresponding SLA count.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/SLA Count by Assignment Group/SLA Count by Assignment Group.js",
    "content": "var slaAgg = new GlideAggregate('task_sla');\nslaAgg.addQuery('task.active', true); // Only active tasks\nslaAgg.groupBy('task.assignment_group'); // Group by assignment group\nslaAgg.addAggregate('COUNT'); // Count the number of active SLAs per assignment group\nslaAgg.query();\n\nwhile (slaAgg.next()) {\n    var assignmentGroup = slaAgg.getValue('task.assignment_group.name');\n    var slaCount = slaAgg.getAggregate('COUNT');\n    gs.info('Assignment Group: ' + assignmentGroup + ' has ' + slaCount + ' active SLAs.');\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/ScheduleJob by ExectionTime_perDay/README.md",
    "content": "*********LONG RUNNING SCHEDULE JOBS PER DAY BY NUMBER OF TIMES EACH EXECUTED AND PROCESSING TIME********\n\nScript to get Top 10 scheduled jobs by processing time and number of times executed per day\n\n - Query the table SYS_LOG_TRANSACTION to identify the TOP 10 Schedule Job by Number of times it executed in one day and How much processing time it took to complete the execution\n\n>>>>> Go to https://<your instance URL>/syslog_transaction_list.do?sysparm_query=urlLIKE<your scheduled job name> and check the \"Transaction processing time\"\n\n - This will help to identify top contibutors that cconsume instance resource and can potentially cause slowness due to long running schedule jobs\n\n - You can execute this as Background scipt or Fix script to get the output.\n - This can be executed as scheduled script to gte the top contributor details daily to take proactive actions\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/ScheduleJob by ExectionTime_perDay/script.js",
    "content": "/*\nQuery the table SYS_LOG_TRANSACTION to identify the TOP 10 Schedule Job by Number of times it executed in one day and How much processing time it took to complete the execution\n\n>>>>> Go to https://<your instance URL>/syslog_transaction_list.do?sysparm_query=urlLIKE<your scheduled job name> and check the \"Transaction processing time\"\n\nThis will help to identify top contibutors that consume instance resource and can potentially cause slowness\n\nYou can execute this as Background scipt or Fix script\n*/\ntopN('syslog_transaction', 'url', 10);\n\nfunction topN(pTable, pColumn, pCount) {\n    var ga = new GlideAggregate(pTable);\n    ga.addAggregate('COUNT', pColumn);\n    ga.orderByAggregate('COUNT', pColumn);\n    //ga.addEncodedQuery('sys_created_onONYesterday@javascript:gs.beginningOfYesterday()@javascript:gs.endOfYesterday()^type=scheduler'); // Schedle job executed yesterday to identify Top 10 by execution time\n    ga.addEncodedQuery('type=scheduler^sys_created_onONLast 15 minutes@javascript:gs.beginningOfLast15Minutes()@javascript:gs.endOfLast15Minutes()'); // Schedle job executed in last 15 min to identify Top 10 by execution time\n    ga.query();\n    var i = 0;\n    var stdout = [];\n    var responseTime = [];\n    stdout.push('\\nTop ' + pCount + ' ' + pColumn + ' values from ' + pTable + '\\n'); //Get all Top 10 ScheduleJon details\n    while (ga.next() && (i++ < pCount)) {\n        stdout.push('\\n\\n***Execution Details for the column ' + ga.getValue(pColumn) + '***\\n');\n        var result1 = getResponseTimeDetails(pTable, 'type=scheduler^sys_created_onONLast 15 minutes@javascript:gs.beginningOfLast15Minutes()@javascript:gs.endOfLast15Minutes()^url=' + ga.getValue(pColumn)); // Schedle job executed in last 15 min to identify Top 10 by execution time\n        stdout.push('Executed total number of times : ' + ga.getValue(pColumn) + ' ' + ga.getAggregate('COUNT', pColumn));\n        stdout.push('\\nTop 10 response times : ' + result1);\n    }\n    gs.print(stdout.join(\"\\n\"));\n}\n\n// Fetch response Time of the schedule job Execution\nfunction getResponseTimeDetails(table, query) {\n    var responseTime = [];\n    var gr = new GlideAggregate(table);\n    gr.addEncodedQuery(query);\n    gr.orderByDesc('response_time');\n    gr.setLimit(10); // Set limit to 10\n    gr.query();\n\n    while (gr._next()) {\n        responseTime.push(gr.response_time.toString());\n    }\n    return responseTime.join(',');\n}\n\n/*\n******************OUTPUT************\n*** Script: \nTop 10 url values from syslog_transaction\n*** Execution Details for the column JOB: Flow Engine Event Handler ***\nExecuted total number of times : JOB: Flow Engine Event Handler[ 290 ]\nTop 10 response times : 58018,57294,56949,39272,38874,38174,38085,37490,37138,36447,25947\n********** Execution Details for the column JOB: BackgroundProgressJob **********\nExecuted total number of times : JOB: BackgroundProgressJob[ 221 ] \nTop 10 response times : 8671,7646,7050,7040,7035,7008,6993,6987,6880,6861,6803\n********** Execution Details for the column JOB: ASYNC: AgentNowResponse**********\nExecuted total number of times : JOB: ASYNC: AgentNowResponse [ 576 ]\nTop 10 response times : 17680,13488,12094,11999,11579,11281,10672,10620,9688,9552,9373\n********** Execution Details for the column JOB: events process**********\nExecuted total number of times : JOB: events process [ 075 ]\nTop 10 response times : 26986,14921,14102,13640,13603,3870,3808,3665,3360,3277,3001\n********** Execution Details for the column JOB: Service Mapping**********\nExecuted total number of times : JOB: Service Mapping Recomputation[ 167 ]\nTop 10 response times : 24035,11209,9297,8431,7857,7142,6555,6541,6218,6124,5855\n********** Execution Details for the column JOB: Event Management **********\nExecuted total number of times : JOB: Event Management[ 64 ]\nTop 10 response times : 939,744,729,644,629,598,585,534,533,518,452\n*/\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate/Readme.md",
    "content": "SimpleGlideAggregate Utility\n**Overview**\nSimpleGlideAggregate is a developer utility Script Include for ServiceNow that provides a simplified, chainable API around the native GlideAggregate class. It abstracts complexities of writing aggregation queries and returns results in an easy-to-use JavaScript object format.\nBecause OOTB glideAggregate API is little bit different so I tried to create a new function with a simper version.\n**Purpose**\nSimplify aggregate queries such as COUNT, SUM, MIN, and MAX for developers, especially those less familiar with GlideAggregate methods.\nProvide an intuitive interface for common aggregation operations with chaining support.\nFacilitate viewing aggregate results alongside individual records matching the same criteria for better analysis.\n\n**Sample Usage of the functions :**\n var sga = new SimpleGlideAggregate('incident');\n\n        // Build query and add all supported aggregates\n        var results = sga\n            .addQuery('active', true)             // Filter: active incidents only\n            .addQuery('priority', '>=', 2)        // Priority 2 or higher\n            .count()                             // Count matching records\n            .sum('duration')                    // Sum of duration field instead of impact\n            .min('priority')                     // Minimum priority value in results\n            .max('sys_updated_on')               // Most recent update timestamp\n            .execute();\n\n        gs.info('Aggregate Results:');\n        gs.info('Count: ' + results.COUNT);\n        gs.info('Sum of Duration: ' + (results.SUM_duration !== undefined ? results.SUM_duration : 'N/A'));\n        gs.info('Minimum Priority: ' + (results.MIN_priority !== undefined ? results.MIN_priority : 'N/A'));\n        gs.info('Most Recent Update (max sys_updated_on timestamp): ' + (results.MAX_sys_updated_on !== undefined ? results.MAX_sys_updated_on : 'N/A'));\n\n        // Optionally fetch some matching record details to complement the aggregate data\n        var gr = new GlideRecord('incident');\n        gr.addQuery('active', true);\n        gr.addQuery('priority', '>=', 2);\n        gr.orderByDesc('sys_updated_on');\n        gr.setLimit(5);\n        gr.query();\n\n        gs.info('Sample Matching Incidents:');\n        while (gr.next()) {\n            gs.info('Number: ' + gr.getValue('number') + ', Priority: ' + gr.getValue('priority') + ', Updated: ' + gr.getValue('sys_updated_on'));\n        }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/SimpleGlideAggregate/SimpleGlideAggregate.js",
    "content": "var SimpleGlideAggregate = Class.create();\nSimpleGlideAggregate.prototype = {\n    initialize: function(tableName) {\n        if (!tableName) {\n            throw new Error(\"Table name is required.\");\n        }\n        this._table = tableName;\n        this._ga = new GlideAggregate(tableName);\n        this._fields = [];\n        this._conditionsAdded = false;\n    },\n\n    /**\n     * Adds a query condition.\n     * Usage: addQuery('priority', '=', '1') or addQuery('active', true)\n     */\n    addQuery: function(field, operator, value) {\n        if (value === undefined) {\n            this._ga.addQuery(field, operator);\n        } else {\n            this._ga.addQuery(field, operator, value);\n        }\n        this._conditionsAdded = true;\n        return this;\n    },\n\n    /**\n     * Adds COUNT aggregate.\n     */\n    count: function() {\n        this._fields.push({type: 'COUNT', field: null});\n        return this;\n    },\n\n    /**\n     * Adds SUM aggregate on a field.\n     */\n    sum: function(field) {\n        if (!field) throw new Error(\"Field name required for sum.\");\n        this._fields.push({type: 'SUM', field: field});\n        return this;\n    },\n\n    /**\n     * Adds MIN aggregate on a field.\n     */\n    min: function(field) {\n        if (!field) throw new Error(\"Field name required for min.\");\n        this._fields.push({type: 'MIN', field: field});\n        return this;\n    },\n\n    /**\n     * Adds MAX aggregate on a field.\n     */\n    max: function(field) {\n        if (!field) throw new Error(\"Field name required for max.\");\n        this._fields.push({type: 'MAX', field: field});\n        return this;\n    },\n\n    /**\n     * Executes the aggregate query and returns results as an object.\n     * Keys are aggregate type or type_field (for field aggregates).\n     */\n    execute: function() {\n        var self = this;\n\n        if (this._fields.length === 0) {\n            throw new Error(\"At least one aggregate function must be added.\");\n        }\n\n        this._fields.forEach(function(agg) {\n            if (agg.field) {\n                self._ga.addAggregate(agg.type, agg.field);\n            } else {\n                self._ga.addAggregate(agg.type);\n            }\n        });\n\n        this._ga.query();\n\n        var results = {};\n        if (this._ga.next()) {\n            this._fields.forEach(function(agg) {\n                var key = agg.field ? agg.type + '_' + agg.field : agg.type;\n                var value = agg.field ? self._ga.getAggregate(agg.type, agg.field) : self._ga.getAggregate(agg.type);\n                results[key] = agg.type === 'COUNT' ? parseInt(value, 10) : parseFloat(value);\n            });\n        } else {\n            // No rows matched, all aggregates 0 or null\n            this._fields.forEach(function(agg) {\n                var key = agg.field ? agg.type + '_' + agg.field : agg.type;\n                results[key] = 0;\n            });\n        }\n\n        return results;\n    },\n\n    type: 'SimpleGlideAggregate'\n};\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Tiered grouping of an integer column/README.md",
    "content": "# Tiered grouping of an integer column\n\nIf you have a column with points (as an integer), this script breaks them down by percentile tiers.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Tiered grouping of an integer column/script.js",
    "content": "var count = new GlideAggregate('x_snc_code_snippet_event');\n//count.addEncodedQuery('sys_created_onONLast 90 days@javascript:gs.beginningOfLast90Days()@javascript:gs.endOfLast90Days()^targetISNOTEMPTY');\ncount.addEncodedQuery('userISNOTEMPTY^pointISNOTEMPTY^point!=0');\ncount.groupBy('user');\ncount.addAggregate('SUM', 'point');\ncount.orderByAggregate('SUM', 'point');\ncount.query();\nvar items = [];\nwhile (count.next()){\n  var item = {};\n  item.username = count.user.username.toString();\n  item.points = parseInt(count.getAggregate('SUM', 'point').split('.')[0]);\n  items.push(item);\n}\nvar leaderboard = [];\nvar leaderboard_index = 0;\nvar count = 0;\nif (Math.floor(items.length*.05) > 0){\n  leaderboard.push('Top 5% of contributors:');\n  count = Math.floor(items.length*.05);\n  for (var i05 = 0; i05 < count; i05++){\n    leaderboard.push(items[leaderboard_index].username + '.');\n    leaderboard_index++;\n  }\n  leaderboard.push('');\n}\nif (Math.floor(items.length*.1) > 0){\n  leaderboard.push('Top 10% of contributors:');\n  count = Math.floor(items.length*.1) - Math.floor(items.length*.05);\n  for (var i10 = 0; i10 < count; i10++){\n    leaderboard.push(items[leaderboard_index].username + '.');\n    leaderboard_index++;\n  }\n  leaderboard.push('');\n}\nif (Math.floor(items.length*.25) > 0){\n  leaderboard.push('Top 25% of contributors:');\n  count = Math.floor(items.length*.25) - Math.floor(items.length*.1) - Math.floor(items.length*.05);\n  for (var i25 = 0; i25 < count; i25++){\n    leaderboard.push(items[leaderboard_index].username + '.');\n    leaderboard_index++;\n  }\n  leaderboard.push('');\n}\nif (Math.floor(items.length*.5) > 0){\n  leaderboard.push('Top contributors (50%):');\n  count = Math.floor(items.length*.5) - Math.floor(items.length*.25) - Math.floor(items.length*.1) - Math.floor(items.length*.05);\n  for (var i50 = 0; i50 < count; i50++){\n    leaderboard.push(items[leaderboard_index].username + '.');\n    leaderboard_index++;\n  }\n  leaderboard.push('');\n}\nleaderboard.push('Other contributors:');\nwhile (leaderboard_index < items.length){\n  //leaderboard.push(items[leaderboard_index].username + ' (' + items[leaderboard_index].points + ').');\n  leaderboard.push(items[leaderboard_index].username + '.');\n  leaderboard_index++;\n}\n\noutputs.leaderboard = leaderboard.join('\\n');\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Top 5 Users with Most Incidents/README.md",
    "content": "# Top 5 Incident Submitters Script\n\nThis ServiceNow background script uses GlideAggregate to identify and report the top 5 users who have submitted the most incidents. It demonstrates efficient use of aggregation, grouping, and sorting in ServiceNow queries, providing valuable insights for incident management and user behavior analysis.\n\nKey features:\n- Utilizes GlideAggregate for efficient data retrieval\n- Sorts and limits results to top 5 users\n- Logs results for easy verification\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Top 5 Users with Most Incidents/top-five-users-with-most-incidents.js",
    "content": "var ga = new GlideAggregate('incident');\nga.addAggregate('COUNT', 'sys_id');\nga.groupBy('caller_id');\nga.orderByAggregate('COUNT', 'sys_id');\nga.setLimit(5);\nga.query();\n\nvar result = [];\nwhile (ga.next()) {\n    var count = ga.getAggregate('COUNT', 'sys_id');\n    var userId = ga.getValue('caller_id');\n    var userName = ga.getDisplayValue('caller_id');\n    \n    result.push({\n        userId: userId,\n        userName: userName,\n        incidentCount: parseInt(count)\n    });\n}\n\nfor (var i = 0; i < result.length; i++) {\n    var user = result[i];\n    gs.log('User: ' + user.userName + ', Incident Count: ' + user.incidentCount);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Using addHaving/README.md",
    "content": "**GlideAggregate**\n\nScript which shows how to use addHaving() function, which allows adding conditions on aggregate functions. In this example, the script will show which users have more than 35 roles assignments in sys_user_has_role table. You can use it also in different scenarios, modifying the table and other conditions.\n\n\n\nFunction addHaving() can not be used in Scoped Applications!\n\n**Example execution logs**\n\n![Logs](ScreenShot_1.PNG)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/Using addHaving/script.js",
    "content": "//Script which is showing how to use addHaving\n//In this example, we are displaying only users which have more than 35 roles\n\n//GlideAggregate query to sys_user_has_role table\nvar gaRole = new GlideAggregate('sys_user_has_role');\n\n//Aggregate count on user field\ngaRole.addAggregate('count', 'user');\n\n//Adding addHaving is an equivalent to addQuery but on aggregate functions\n//In this case we are getting from the list only users which exists more than 35 times - that means they have more than 35 role assignments.\ngaRole.addHaving('count', 'user', '>', '35');\ngaRole.query();\n\n//Going through all examples which are matching addHaving\nwhile (gaRole.next()) {\n\n    //Logging which users match addHaving condition\n    gs.info('User: ' + gaRole.user.getDisplayValue() + ' (' + gaRole.user + ') have more than 35 roles!');\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/addTrend/README.md",
    "content": "### GlideAggregate - addTrend()\n\n#### To show number of closed incidents by Assignment group monthly.\n\n\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/addTrend/addTrend.js",
    "content": "\nvar trend = new GlideAggregate('incident');  \ntrend.addTrend('closed_at','month');\ntrend.addQuery('assignment_group','assignment_group_sysid');\ntrend.addQuery('state',7);  // Closed state\ntrend.addAggregate('COUNT');  \ntrend.query();  \nwhile(trend.next()) {  \n   gs.print(trend.getValue('timeref') + ': ' + trend.getAggregate('COUNT'));  \n}  "
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/getCountAfterDate/README.md",
    "content": "## GlideAggregate\n1. Instantiate GlideAggregate object, include table in parameter.\n2. `addQuery` method will restrict returned data-set based on added queries.\n3. `addAggregate` groups the returned data-set by second argument within parameters & the first argument within the parameters is the calulation ran based on that grouping.\n4. `query` runs glideaggregate.\n5. `getAggregate` collects the data-set grouped by data."
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/getCountAfterDate/getCountAfterDate.js",
    "content": "var gldAgg = new GlideAggregate('sn_grc_issue');\n\n// Restrict returned data-set to records created within the current year to date\ngldAgg.addQuery('sys_created_on', '>=', 'javascript:gs.beginningOfThisYear()');\n\n// Will group returned query data-set by rule\n// Example: Will group returned data-set by assigned_to & use that grouping to caluclate COUNT using grouping\ngldAgg.addAggregate('COUNT', 'assigned_to');\ngldAgg.query();\n\nwhile (gldAgg.next()) {\n    // Retrieve group calculated values\n    var recordCount = gldAgg.getAggregate('COUNT', 'assigned_to');\n\n    // Retrieve value from group\n    var assignedTo = gldAgg.getDisplayValue('assigned_to');\n\n    if (assignedTo == \"\") {\n        assignedTo = '[Empty]';\n    }\n\n    gs.info('There were {0} issues assigned to {1} as of this year', [recordCount, assignedTo]);\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/getTotal of aggregate value/README.md",
    "content": "### GlideAggregate - getTotal()\n\n#### Script to get total of aggregate value\n\nExample\nTo show total number of closed P1 incidents which was illustrated in a trend (count of incident every month).\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAggregate/getTotal of aggregate value/getTotal.js",
    "content": "var trend = new GlideAggregate('incident');  \ntrend.addTrend ('closed_at','month');\ntrend.addQuery('priority',1);\ntrend.addQuery('state',7);  \ntrend.addAggregate('COUNT');  \ntrend.query();  \nwhile(trend.next()) {  \n   gs.print(trend.getValue('timeref') + ': ' + trend.getAggregate('COUNT'));  \n}  \ngs.print('Total closed incidents: ' + trend.getTotal('COUNT')); "
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/AjaxAsyncOnSubmit/README.md",
    "content": "On submit client scripts do not support in making asynchronous calls to server on both platform and portal due to its nature of execution. This had always been a problem and there was a need to make validations work asynchronously on submitting a form/record. The support for getXMLWait() had also been removed which prevents the usage of synchronous GlideAjax call on a service portal. The snippet provides a workaround to execute async calls in both forms and catalog items, thus enabling server side validations during onsubmit.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/AjaxAsyncOnSubmit/ajaxasynconsubmitclient.js",
    "content": "function onSubmit() {\n    //Only submit if submitForm is flagged as true\n    if (g_scratchpad.submitForm)\n        return true;\n\n    var user = g_user.userID;\n    var ga = new GlideAjax('UserUtils');\n    ga.addParam('sysparm_name', 'getUserInfo');\n    ga.addParam('sysparm_user', user);\n    ga.getXMLAnswer(function getAnswer(answer) {\n        if (answer == 'false') {\n            g_form.addErrorMessage('User is not a VIP, Cant proceed');\n            return false;\n        }\n        //Allow force submission upon receiving a response\n        var actionName = g_form.getActionName();\n        g_scratchpad.submitForm = true;\n        g_form.submit(actionName);\n    });\n\n    //Do not submit initially and force to wait for response from the asynchronous GlideAjax call\n    return false;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/AjaxAsyncOnSubmit/ajaxasynconsubmitserver.js",
    "content": "var UserUtils = Class.create();\nUserUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getUserInfo: function() {\n        var user = this.getParameter(\"sysparm_user\");\n        var userGr = new GlideRecord(\"sys_user\");\n        if (userGr.get(user) && userGr.getValue(\"vip\") == true) {\n            return true;\n        }\n        return false;\n    },\n\n    type: 'UserUtils'\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Check Weekend - Client Side/README.md",
    "content": "\n# ServiceNow Weekend Checker (Client-Side Utility)\n\nA reusable client-side script to detect weekends (Saturday/Sunday) and modify ServiceNow form behaviour accordingly.\n\n# Overview\n\nThis project contains a simple, reusable utility for determining if the current date (based on the user’s browser timezone) falls on a weekend.  \nIt’s ideal for Client Scripts, Catalog Client Scripts, or Service Portal widgets.\n\n# Features\n \n- ✅ Lightweight — no dependencies  \n- ✅ Works across all client script contexts  \n- ✅ Includes helper method for automatic info messages   \n\n## ⚙️ Usage\nCreate the Script Include present in the WeekendChecker.js file\nUse the Client-Side Script to call GlideAjax and determine if the day/date is a weekend or not.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Check Weekend - Client Side/WeekendChecker.js",
    "content": "//Script Include\nvar DateUtilityAjax = Class.create();\nDateUtilityAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n//Returns true if the current server date is a weekend\n    isWeekend: function() {\n        var gdt = new GlideDateTime();\n        var dayOfWeek = gdt.getDayOfWeekLocalTime(); // Sunday = 1, Monday = 2, ..., Saturday = 7 in Servicenow\n        return (dayOfWeek === 1 || dayOfWeek === 7);\n    },\n  \n    type: 'DateUtilityAjax'\n});\n\n//Client Script - GlideAjax\nfunction onLoad() {\n    var ga = new GlideAjax('DateUtilityAjax');\n    ga.addParam('sysparm_name', 'isWeekend');\n    \n    ga.getXMLAnswer(function(answer) {\n        var isWeekend = (answer === 'true');\n        \n        if (isWeekend) {\n            g_form.addInfoMessage('Server reports it’s the weekend - some actions are restricted.');\n        } else {\n            g_form.addInfoMessage('Weekday detected - normal operations available.');\n        }\n    });\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/EfficientGlideRecord (Client-side)/ClientGlideRecordAJAX.js",
    "content": "/*\n\tServer-side client-callable Script Include.\n\tSee related article for full usage instructions and API documentation:\n\thttps://snprotips.com/efficientgliderecord\n*/\nvar ClientGlideRecordAJAX = Class.create();\nClientGlideRecordAJAX.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\t\n\tgr_config : {},\n\t\n\tgetPseudoGlideRecord : function() {\n\t\tvar grQuery;\n\t\tvar responseObj = {\n\t\t\t'_records' : [],\n\t\t\t'_row_count' : 0,\n\t\t\t'_config' : {},\n\t\t\t'_executing_as' : {\n\t\t\t\t'user_name' : gs.getUserName(),\n\t\t\t\t'user_id' : gs.getUserID()\n\t\t\t}\n\t\t};\n\t\t\n\t\tthis.gr_config = {};\n\t\t\n\t\tthis.gr_config.table_to_query = this.getParameter('table_to_query');\n\t\t//@type {{get_display_value: boolean, name: string}}\n\t\tthis.gr_config.fields_to_get = this.getParameter('fields_to_get');\n\t\tthis.gr_config.record_limit = this.getParameter('record_limit');\n\t\tthis.gr_config.order_by_field = this.getParameter('order_by_field');\n\t\tthis.gr_config.order_by_desc_field = this.getParameter('order_by_desc_field');\n\t\tthis.gr_config.encoded_queries = this.getParameter('encoded_queries');\n\t\tthis.gr_config.queries = this.getParameter('queries');\n\t\t\n\t\t//Parse queries/encoded queries array and fields_to_get object\n\t\tif (this.gr_config.hasOwnProperty('queries') && this.gr_config.queries) {\n\t\t\tthis.gr_config.queries = JSON.parse(this.gr_config.queries);\n\t\t}\n\t\tif (this.gr_config.hasOwnProperty('fields_to_get') && this.gr_config.fields_to_get) {\n\t\t\tthis.gr_config.fields_to_get = JSON.parse(this.gr_config.fields_to_get);\n\t\t}\n\t\tif (this.gr_config.hasOwnProperty('encoded_queries') && this.gr_config.encoded_queries) {\n\t\t\tthis.gr_config.encoded_queries = JSON.parse(this.gr_config.encoded_queries);\n\t\t}\n\t\t\n\t\tgs.debug('EfficientGlideRecord config: \\n' + JSON.stringify(this.gr_config, null, 2));\n\t\t\n\t\tif (!this._validateMandatoryConfig()) {\n\t\t\tthrow new Error(\n\t\t\t\t'Mandatory value not specified. ' +\n\t\t\t\t'Cannot perform query. Halting.\\n' +\n\t\t\t\t'Config: \\n' +\n\t\t\t\tJSON.stringify(this.gr_config)\n\t\t\t);\n\t\t}\n\t\t\n\t\tgrQuery = this._constructAndGetGlideRecord();\n\t\tgrQuery.query();\n\t\t\n\t\twhile (grQuery.next()) {\n\t\t\tresponseObj._records.push(\n\t\t\t\tthis._getRequestedRecordData(grQuery, this.gr_config)\n\t\t\t);\n\t\t}\n\t\t\n\t\tresponseObj._row_count = responseObj._records.length;\n\t\tresponseObj._config = this.gr_config;\n\t\t\n\t\treturn JSON.stringify(responseObj);\n\t},\n\t\n\t_constructAndGetGlideRecord : function() {\n\t\tvar i, queryField, queryOperator, queryValue;\n\t\tvar grQuery = new GlideRecordSecure(this.gr_config.table_to_query);\n\t\t\n\t\t//Add limit, if specified\n\t\tif (\n\t\t\tthis.gr_config.hasOwnProperty('record_limit') &&\n\t\t\tthis.gr_config.record_limit >= 1\n\t\t) {\n\t\t\tgrQuery.setLimit(this.gr_config.record_limit);\n\t\t}\n\t\t\n\t\t//add order_by or order_by_desc field, if specified\n\t\tif (\n\t\t\tthis.gr_config.hasOwnProperty('order_by_desc_field') &&\n\t\t\tthis.gr_config.order_by_desc_field\n\t\t) {\n\t\t\tgrQuery.orderByDesc(this.gr_config.order_by_desc_field);\n\t\t}\n\t\tif (\n\t\t\tthis.gr_config.hasOwnProperty('order_by_field') &&\n\t\t\tthis.gr_config.order_by_field\n\t\t) {\n\t\t\tgrQuery.orderBy(this.gr_config.order_by_field);\n\t\t}\n\t\t\n\t\t//Add encoded query, if specified\n\t\tif (\n\t\t\tthis.gr_config.hasOwnProperty('encoded_queries') &&\n\t\t\tthis.gr_config.encoded_queries\n\t\t) {\n\t\t\tfor (i = 0; i < this.gr_config.encoded_queries.length; i++) {\n\t\t\t\tif (this.gr_config.encoded_queries[i]) {\n\t\t\t\t\tgrQuery.addEncodedQuery(this.gr_config.encoded_queries[i]);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\t//Add field queries if specified\n\t\tif (\n\t\t\tthis.gr_config.hasOwnProperty('queries') &&\n\t\t\tthis.gr_config.queries.length > 0\n\t\t) {\n\t\t\tfor (i = 0; i < this.gr_config.queries.length; i++) {\n\t\t\t\tqueryField = this.gr_config.queries[i].field;\n\t\t\t\tqueryOperator = this.gr_config.queries[i].operator;\n\t\t\t\tqueryValue = this.gr_config.queries[i].value;\n\t\t\t\t\n\t\t\t\tgrQuery.addQuery(queryField, queryOperator, queryValue);\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn grQuery;\n\t},\n\t\n\t_validateMandatoryConfig : function() {\n\t\tvar i, currentQuery;\n\t\t//May add more later if necessary\n\t\tvar mandatoryFields = [\n\t\t\t'table_to_query',\n\t\t\t'fields_to_get'\n\t\t];\n\t\t\n\t\tfor (i = 0; i < mandatoryFields.length; i++) {\n\t\t\tif (\n\t\t\t\t!this.gr_config.hasOwnProperty(mandatoryFields[i]) ||\n\t\t\t\t!this.gr_config[mandatoryFields[i]]\n\t\t\t) {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t}\n\t\t\n\t\t//If both order_by and order_by_desc are specified, log a warning and ignore order_by_desc.\n\t\t// if (\n\t\t// \t(\n\t\t// \t\tthis.gr_config.hasOwnProperty('order_by_field') &&\n\t\t// \t\tthis.gr_config.order_by_field\n\t\t// \t) &&\n\t\t// \t(\n\t\t// \t\tthis.gr_config.hasOwnProperty('order_by_desc_field') &&\n\t\t// \t\tthis.gr_config.order_by_desc_field\n\t\t// \t)\n\t\t// ) {\n\t\t// \tgs.warn(\n\t\t// \t\t'ClientGlideRecordAJAX client-callable Script Include called with ' +\n\t\t// \t\t'both an \"order by\" and \"orderby descending\" field. It is only possible to ' +\n\t\t// \t\t'specify one field to sort by, either ascending or descending. \\n' +\n\t\t// \t\t'Ignoring the descending field, and ordering by the order_by_field field.'\n\t\t// \t);\n\t\t// \tthis.gr_config.order_by_desc_field = '';\n\t\t// }\n\t\t\n\t\t/*\n\t\t\tDecided to remove the above code and allow the user to order their results\n\t\t\thowever they like, I'm not their dad.\n\t\t*/\n\t\t\n\t\tif (\n\t\t\tthis.gr_config.hasOwnProperty('queries') &&\n\t\t\tthis.gr_config.queries\n\t\t) {\n\t\t\tfor (i = 0; i < this.gr_config.queries.length; i++) {\n\t\t\t\tcurrentQuery = this.gr_config.queries[i];\n\t\t\t\tif (\n\t\t\t\t\t(!currentQuery.hasOwnProperty('field') || !currentQuery.field) ||\n\t\t\t\t\t(!currentQuery.hasOwnProperty('operator') || !currentQuery.operator) ||\n\t\t\t\t\t(!currentQuery.hasOwnProperty('value') || !currentQuery.value)\n\t\t\t\t) {\n\t\t\t\t\tgs.error(\n\t\t\t\t\t\t'Malformed query provided to ClientGlideRecordAJAX Script Include:\\n' +\n\t\t\t\t\t\tJSON.stringify(currentQuery)\n\t\t\t\t\t);\n\t\t\t\t\treturn false;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn true;\n\t},\n\t\n\t_getRequestedRecordData : function(grRecord, config) {\n\t\tvar i, canReadField, fieldName, fieldValue, fieldDisplayValue, getDisplay;\n\t\tvar recordData = {\n\t\t\t'_config' : config,\n\t\t\t'_table_name' : grRecord.getTableName(),\n\t\t\t'_field_values' : {}\n\t\t};\n\t\t\n\t\tfor (i = 0; i < recordData._config.fields_to_get.length; i++) {\n\t\t\tfieldName = recordData._config.fields_to_get[i].name;\n\t\t\tgetDisplay = !!recordData._config.fields_to_get[i].get_display_value;\n\t\t\t//Must get canReadField in this way and use it to see if we can see the field values,\n\t\t\t// cause otherwise GlideRecordSecure freaks alll the way out.\n\t\t\tcanReadField = (grRecord.isValidField(fieldName) && grRecord[fieldName].canRead());\n\t\t\tfieldValue = canReadField ? (grRecord.getValue(fieldName) || '') : '';\n\t\t\tfieldDisplayValue = (getDisplay && canReadField && fieldValue) ?\n\t\t\t\t(grRecord[fieldName].getDisplayValue() || '') : ('');\n\t\t\t\n\t\t\t//Retrieve value (and display value if requested)\n\t\t\trecordData._field_values[fieldName] = {\n\t\t\t\t'name' : fieldName,\n\t\t\t\t'value' : fieldValue,\n\t\t\t\t'display_value' : fieldDisplayValue,\n\t\t\t\t//If false, may be caused by ACL restriction, or by invalid field\n\t\t\t\t'can_read' : canReadField\n\t\t\t};\n\t\t}\n\t\t\n\t\treturn recordData;\n\t},\n\t\n\ttype : 'ClientGlideRecordAJAX'\n});"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/EfficientGlideRecord (Client-side)/EfficientGlideRecord (minified) UI Script.js",
    "content": "/*\n\tThis is a minified, closure-compiled UI Script containing the EfficientGlideRecord class.\n\tSee https://snprotips.com/efficientgliderecord for full usage and API documentation.\n*/\nvar EfficientGlideRecord=function(a){if(!a)throw Error(\"EfficientGlideRecord constructor called without a valid tableName argument. Cannot continue.\");this._config={table_to_query:a,fields_to_get:[],record_limit:0,order_by_field:\"\",order_by_desc_field:\"\",encoded_queries:[],queries:[]};this._row_count=-1;this._query_complete=!1;this._records=[];this._current_record_index=-1;this._current_record={};this._gaQuery=new GlideAjax(\"ClientGlideRecordAJAX\");this._gaQuery.addParam(\"sysparm_name\",\"getPseudoGlideRecord\");\n\treturn this};EfficientGlideRecord.prototype.addField=function(a,b){this._config.fields_to_get.push({name:a,get_display_value:!!b});return this};EfficientGlideRecord.prototype.addQuery=function(a,b,d){\"undefined\"===typeof d&&(d=b,b=\"=\");this._config.queries.push({field:a,operator:b,value:d});return this};EfficientGlideRecord.prototype.addNotNullQuery=function(a){this.addQuery(a,\"!=\",\"NULL\");return this};EfficientGlideRecord.prototype.addNullQuery=function(a){this.addQuery(a,\"=\",\"NULL\");return this};\nEfficientGlideRecord.prototype.addEncodedQuery=function(a){if(!a||\"string\"!==typeof a)throw Error(\"Invalid encoded query string specified. Encoded query must be a valid non-empty string.\");this._config.encoded_queries.push(a);return this};EfficientGlideRecord.prototype.setEncodedQuery=function(a){this._config.encoded_queries=[a];return this};EfficientGlideRecord.prototype.addOrderBy=function(a){this.orderBy(a);return this};\nEfficientGlideRecord.prototype.orderBy=function(a){this._config.order_by_field=a;return this};EfficientGlideRecord.prototype.orderByDesc=function(a){this._config.order_by_desc_field=a;return this};EfficientGlideRecord.prototype.setLimit=function(a){if(\"number\"!==typeof a||0>=a)throw Error(\"EfficientGlideRecord.setLimit() method called with an invalid argument. Limit must be a number greater than zero.\");this._config.record_limit=a;return this};\nEfficientGlideRecord.prototype.get=function(a,b){this.addQuery(\"sys_id\",a);this.setLimit(1);this.query(b)};\nEfficientGlideRecord.prototype.query=function(a){var b;if(!this._readyToSend())return!1;for(b in this._config)if(this._config.hasOwnProperty(b)){var d=void 0;d=\"object\"===typeof this._config[b]?JSON.stringify(this._config[b]):this._config[b];this._gaQuery.addParam(b,d)}this._gaQuery.getXMLAnswer(function(c,e){c=JSON.parse(c);if(!c.hasOwnProperty(\"_records\"))throw Error(\"Something went wrong when attempting to get records from the server.\\nResponse object: \\n\"+JSON.stringify(c));e._query_complete=\n\t!0;e._records=c._records;e._row_count=c._row_count;e._executing_as=c._executing_as;a(e)},null,this)};EfficientGlideRecord.prototype.hasNext=function(){return this._query_complete?this._row_count>this._current_record_index+1:!1};EfficientGlideRecord.prototype.next=function(){if(!this._query_complete||!this.hasNext())return!1;this._current_record_index++;this._current_record=this._records[this._current_record_index];return!0};\nEfficientGlideRecord.prototype.canRead=function(a){if(!this._query_complete)throw Error(\"The .canRead() method of EfficientGlideRecord can only be called from the callback function after calling .query(callbackFn)\");return this._current_record._field_values.hasOwnProperty(a)?this._current_record._field_values[a].hasOwnProperty(\"can_read\")?!!this._current_record._field_values[a].can_read||!1:(console.warn('The requested field \"'+a+'\" has no can_read node. This should not happen. Returning a blank false.'),\n\t!1):(console.warn(\"There is no field with the name \"+a+\" in the EfficientGlideRecord object. Did you remember to specify that you want to get that field in the query using .addField()?\"),!1)};\nEfficientGlideRecord.prototype.getValue=function(a){if(!this._query_complete)throw Error(\"The .getValue() method of EfficientGlideRecord can only be called from the callback function after calling .query(callbackFn)\");return this._current_record._field_values.hasOwnProperty(a)?this._current_record._field_values[a].hasOwnProperty(\"value\")?this._current_record._field_values[a].value||\"\":(console.warn('The requested field \"'+a+'\" has no value node. This should not happen. Returning a blank string.'),\n\t\"\"):(console.warn(\"There is no field with the name \"+a+\" in the EfficientGlideRecord object. Did you remember to specify that you want to get that field in the query using .addField()?\"),\"\")};\nEfficientGlideRecord.prototype.getDisplayValue=function(a){if(!this._query_complete)throw Error(\"The .getDisplayValue() method of EfficientGlideRecord can only be called from the callback function after calling .query(callbackFn)\");return this._current_record._field_values.hasOwnProperty(a)?this._current_record._field_values[a].hasOwnProperty(\"display_value\")&&this._current_record._field_values[a].display_value?this._current_record._field_values[a].display_value||\"\":(console.warn(\"There is no display value for the field with the name \"+\n\ta+\" in the EfficientGlideRecord object. Did you remember to specify that you want to get that field's display value in the query using .addField(fieldName, true)?\"),\"\"):(console.warn(\"There is no field with the name \"+a+\" in the EfficientGlideRecord object. Did you remember to specify that you want to get that field in the query using .addField()?\"),\"\")};EfficientGlideRecord.prototype.getRowCount=function(){return this._row_count};\nEfficientGlideRecord.prototype._readyToSend=function(){if(!this._config.table_to_query)return console.error(\"EfficientGlideRecord not ready to query. Table name was not specified in the constructor's initialize argument.\"),!1;if(!this._config.fields_to_get||1>this._config.fields_to_get.length)return console.error(\"EfficientGlideRecord not ready to query. No fields were specified to retrieve. \\nPlease specify which fields you want to retrieve from the GlideRecord object using .addField(fieldName, getDisplayValue). Afterward, in your callback, you can use .getValue(fieldName). If you set getDisplayValue to true, you can also use .getDisplayValue(fieldName).\"),\n\t!1;(!this._config.hasOwnProperty(\"queries\")||1>this._config.queries.length)&&(!this._config.hasOwnProperty(\"encoded_queries\")||1>this._config.encoded_queries.length)&&(!this._config.hasOwnProperty(\"record_limit\")||1>this._config.record_limit)&&console.warn(\"The EfficientGlideRecord query has no query and no record limit associated with it. This may result in poor performance when querying larger tables. Please make sure that you need all records in the specified table, as all records will be returned by this query.\");\n\treturn!0;};"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/EfficientGlideRecord (Client-side)/EfficientGlideRecord UI Script.js",
    "content": "/**\n * UI Script (Desktop-Global)\n * @description See related article for full usage instructions and API documentation:\n * https://snprotips.com/efficientgliderecord\n * @classdesc https://snprotips.com/efficientgliderecord\n * @author\n *  Tim Woodruff (https://TimothyWoodruff.com)\n *  SN Pro Tips (https://snprotips.com)\n * @version 1.0.1\n * @class\n *\n * @license\n * Copyright (c) 2022 Tim Woodruff (https://TimothyWoodruff.com)\n * & SN Pro Tips (https://snprotips.com).\n *\n * Permission is hereby granted, free of charge, to any person obtaining a copy\n *  of this software and associated documentation files (the \"Software\"), to deal\n *  in the Software without restriction, including without limitation the rights\n *  to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\n *  copies of the Software, and to permit persons to whom the Software is\n *  furnished to do so, subject to the following conditions:\n *\n * The above copyright notice and this permission notice shall be included in all\n *  copies or substantial portions of the Software.\n *\n * Alternative licensing is available upon request. Please contact tim@snc.guru\n *  for more info.\n *\n * THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\n *  IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\n *  FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\n *  AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\n *  LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\n *  OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\n *  SOFTWARE.\n */\nclass EfficientGlideRecord {\n\t\n\t/**\n\t * Instantiated with the 'new' keyword (as classes typically are when instantiated), this\n\t *  will construct a client-side EfficientGlideRecord object. The methods of this class can\n\t *  then be called to construct a client-side GlideRecord query. EfficientGlideRecord\n\t *  replicates *most* of the functionality of the client-side GlideRecord object, but\n\t *  with more and enhanced functionality.\n\t * EfficientGlideRecord is FAR preferable to using the out-of-box (OOB) client-side\n\t *  GlideRecord query (even asynchronously), because GlideRecord returns a massive amount\n\t *  of unnecessary data, and can be much, much slower. EfficientGlideRecord aims to return\n\t *  only that data which is necessary and requested from the server, thus providing an\n\t *  efficient interface to query records asynchronously without all the additional overhead\n\t *  related to information that you don't need.\n\t *\n\t * Additional documentation can be found on the SN Pro Tips blog, at https://go.snc.guru/egr\n\t * NOTE: For info on performing async queries in onSubmit Client Scripts, see\n\t *  https://go.snc.guru/onsubmit\n\t *\n\t * @param {String} tableName - The name of the table on which to execute your GlideRecord query\n\t * @returns {EfficientGlideRecord}\n\t * @example\n\t * var egrIncident = new EfficientGlideRecord('incident');\n\t * egrIncident.addField('number')\n\t * \t.addField('assignment_group', true)\n\t * \t.addField('assigned_to', true);\n\t *\n\t * egrIncident.get('some_incident_sys_id', function(egrInc) {\n\t * \tg_form.addInfoMessage(\n\t * \t\tegrInc.getValue('number') + '\\'s assignment group is ' +\n\t * \t\tegrInc.getDisplayValue('assignment_group') + ' (sys_id: ' +\n\t * \t\tegrInc.getValue('assignment_group') + ')\\n' +\n\t * \t\t'The assignee is ' + egrInc.getDisplayValue('assigned_to') + ' (sys_id: ' +\n\t * \t\tegrInc.getValue('assigned_to') + ')'\n\t * \t);\n\t * });\n\t * @constructor\n\t */\n\tconstructor(tableName) {\n\t\tif (!tableName) {\n\t\t\tthrow new Error(\n\t\t\t\t'EfficientGlideRecord constructor called without a valid tableName ' +\n\t\t\t\t'argument. Cannot continue.'\n\t\t\t);\n\t\t}\n\t\t\n\t\tthis._config = {\n\t\t\t'table_to_query' : tableName,\n\t\t\t'fields_to_get' : [],\n\t\t\t'record_limit' : 0,\n\t\t\t'order_by_field' : '',\n\t\t\t'order_by_desc_field' : '',\n\t\t\t'encoded_queries' : [],\n\t\t\t'queries' : []\n\t\t};\n\t\t\n\t\tthis._row_count = -1;\n\t\tthis._query_complete = false;\n\t\t\n\t\tthis._records = [];\n\t\tthis._current_record_index = -1;\n\t\tthis._current_record = {};\n\t\t\n\t\tthis._gaQuery = new GlideAjax('ClientGlideRecordAJAX');\n\t\tthis._gaQuery.addParam('sysparm_name', 'getPseudoGlideRecord');\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Add a field to retrieve from the target record(s).\n\t * Any fields not specified by calling this method will not be available on the resulting\n\t *  EfficientGlideRecord object in the callback function after calling .query(). In this\n\t *  case, a warning will be shown in the console, and .getValue('field_name') will return\n\t *  a blank string.\n\t * If a second argument (getDisplayValue) is not specified and set to true, then the\n\t *  field's display value will not be available on the resulting EfficientGlideRecord\n\t *  object in the callback function. In this case, .getDisplayValue('field_name') will\n\t *  return a blank string.\n\t * @param {String} fieldName - The name of the field to retrieve from the server for the\n\t *  specified record(s).\n\t * @param {Boolean} [getDisplayValue=false] - Set this argument to true in order to\n\t *  retrieve the display value for the specified field. If this is not set to true then\n\t *  calling .getDisplayValue('field_name') will cause a warning to be logged to the\n\t *  console, and a blank string will be returned.\n\t * @returns {EfficientGlideRecord}\n\t * @example\n\t * var egrIncident = new EfficientGlideRecord('incident');\n\t * egrIncident.addField('number')\n\t * \t.addField('assignment_group', true)\n\t * \t.addField('assigned_to', true);\n\t *\n\t * egrIncident.get('some_incident_sys_id', function(egrInc) {\n\t * \tg_form.addInfoMessage(\n\t * \t\tegrInc.getValue('number') + '\\'s assignment group is ' +\n\t * \t\tegrInc.getDisplayValue('assignment_group') + ' (sys_id: ' +\n\t * \t\tegrInc.getValue('assignment_group') + ')\\n' +\n\t * \t\t'The assignee is ' + egrInc.getDisplayValue('assigned_to') + ' (sys_id: ' +\n\t * \t\tegrInc.getValue('assigned_to') + ')'\n\t * \t);\n\t * });\n\t */\n\taddField(fieldName, getDisplayValue) {\n\t\tthis._config.fields_to_get.push({\n\t\t\t'name' : fieldName,\n\t\t\t'get_display_value' : (!!getDisplayValue)\n\t\t});\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Add a query to the EfficientGlideRecord object.\n\t * By specifying a field name, operator, and value, you can perform all sorts of queries.\n\t * If only two arguments are specified, then it's assumed that the first is the field\n\t *  name and the second is the field value. The operator will automatically be set to \"=\".\n\t *\n\t * @param {String} fieldName - The name of the field to perform the query against.\n\t * @param {String} [operator=\"=\"] - The operator to use for the query.\n\t * Valid operators:\n\t * Numbers: =, !=, >, >=, <, <=\n\t * Strings: =, !=, STARTSWITH, ENDSWITH, CONTAINS, DOES NOT CONTAIN, IN, NOT IN, INSTANCEOF\n\t * Note: If only two arguments are specified (fieldValue is not defined), then the second\n\t *  argument will be treated as the value, and the operator will automatically be set to \"=\".\n\t * @param {String} fieldValue - The value to compare, using the specified operator, against\n\t *  the specified field.\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling (as seen in the example below).\n\t * @example\n\t * new EfficientGlideRecord('incident')\n\t * \t.setLimit(10)\n\t * \t.addQuery('assignment_group', '!=', 'some_group_sys_id')\n\t * \t.addQuery('assigned_to', 'some_assignee_sys_id')\n\t * \t.addNotNullQuery('assignment_group')\n\t * \t.addField('number')\n\t * \t.addField('short_description')\n\t * \t.addField('assignment_group', true) //Get display value as well\n\t * \t.orderBy('number')\n\t * \t.query(function (egrIncident) {\n\t * \t\twhile (egrIncident.next()) {\n\t * \t\t\tconsole.log(\n\t * \t\t\t\t'Short description value: ' + egrIncident.getValue('short_description') + '\\n' +\n\t * \t\t\t\t'Number: ' + egrIncident.getValue('number') + '\\n' +\n\t * \t\t\t\t'Assignment group: ' + egrIncident.getValue('assignment_group') + ' (' +\n\t * \t\t\t\tegrIncident.getDisplayValue('assignment_group') + ')'\n\t * \t\t\t);\n\t * \t\t}\n\t * \t});\n\t */\n\taddQuery(fieldName, operator, fieldValue) {\n\t\tif (typeof fieldValue === 'undefined') {\n\t\t\tfieldValue = operator;\n\t\t\toperator = '=';\n\t\t}\n\t\t\n\t\tthis._config.queries.push({\n\t\t\t'field' : fieldName,\n\t\t\t'operator' : operator,\n\t\t\t'value' : fieldValue\n\t\t});\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Shorthand for this.addQuery(fieldName, '!=', 'NULL');.\n\t * @param {String} fieldName - The name of the field to ensure is not empty on returned\n\t *  records.\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t *  @example\n\t *  new EfficientGlideRecord('incident')\n\t * \t.setLimit(10)\n\t * \t.addQuery('assignment_group', '!=', 'some_group_sys_id')\n\t * \t.addQuery('assigned_to', 'some_assignee_sys_id')\n\t * \t.addNotNullQuery('assignment_group')\n\t * \t.addField('number')\n\t * \t.addField('short_description')\n\t * \t.addField('assignment_group', true) //Get display value as well\n\t * \t.orderBy('number')\n\t * \t.query(function (egrIncident) {\n\t * \t\twhile (egrIncident.next()) {\n\t * \t\t\tconsole.log(\n\t * \t\t\t\t'Short description value: ' + egrIncident.getValue('short_description') + '\\n' +\n\t * \t\t\t\t'Number: ' + egrIncident.getValue('number') + '\\n' +\n\t * \t\t\t\t'Assignment group: ' + egrIncident.getValue('assignment_group') + ' (' +\n\t * \t\t\t\tegrIncident.getDisplayValue('assignment_group') + ')'\n\t * \t\t\t);\n\t * \t\t}\n\t * \t});\n\t */\n\taddNotNullQuery(fieldName) {\n\t\tthis.addQuery(fieldName, '!=', 'NULL');\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Shorthand for .addQuery(fieldName, '=', 'NULL')\n\t * @param {String} fieldName - The name of the field to use in your query, getting only\n\t *  records where this field is empty.\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\taddNullQuery(fieldName) {\n\t\tthis.addQuery(fieldName, '=', 'NULL');\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Add an encoded query string to your query. Records matching this encoded query will\n\t *  be available in your callback function after calling .query().\n\t * @param {String} encodedQueryString - The encoded query string to use in your query.\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\taddEncodedQuery(encodedQueryString) {\n\t\tif (!encodedQueryString || typeof encodedQueryString !== 'string') {\n\t\t\tthrow new Error(\n\t\t\t\t'Invalid encoded query string specified. Encoded query must be a valid ' +\n\t\t\t\t'non-empty string.'\n\t\t\t);\n\t\t}\n\t\t\n\t\tthis._config.encoded_queries.push(encodedQueryString);\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Very similar to .addEncodedQuery(), except that it REPLACES any existing encoded\n\t *  queries on the GlideRecord, rather than adding to them.\n\t * @param {String} encodedQueryString - The exact encoded query, as a string, to use in\n\t *  your query.\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\tsetEncodedQuery(encodedQueryString) {\n\t\t//REPLACE existing encoded queries, rather than add to them like .addEncodedQuery().\n\t\tthis._config.encoded_queries = [encodedQueryString];\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Orders the queried table by the specified column, in ascending order\n\t * (Alternate call for .orderBy(fieldName).)\n\t * @param orderByField\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\taddOrderBy(orderByField) {\n\t\tthis.orderBy(orderByField);\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Orders the queried table by the specified column, in ascending order\n\t * @param {String} orderByField - Orders the queried table by the specified column,\n\t *  in ascending order\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\torderBy(orderByField) {\n\t\tthis._config.order_by_field = orderByField;\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Orders the queried table by the specified column, in descending order\n\t * @param {String} orderByDescField - Orders the queried table by the specified column,\n\t *  in descending order\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\torderByDesc(orderByDescField) {\n\t\tthis._config.order_by_desc_field = orderByDescField;\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Limits the number of records queried from the database and\n\t * returned to the response.\n\t * @param {Number} limit - The limit to use in the database query. No more than this number\n\t *  of records will be returned.\n\t * @returns {EfficientGlideRecord} - Returns the instantiated object for optional\n\t *  chain-calling.\n\t */\n\tsetLimit(limit) {\n\t\tif (typeof limit !== 'number' || limit <= 0) {\n\t\t\tthrow new Error(\n\t\t\t\t'EfficientGlideRecord.setLimit() method called with an invalid argument. ' +\n\t\t\t\t'Limit must be a number greater than zero.'\n\t\t\t);\n\t\t}\n\t\tthis._config.record_limit = limit;\n\t\t\n\t\treturn this;\n\t}\n\t\n\t/**\n\t * Gets a single record, efficiently, from the database by sys_id.\n\t * @param {String} sysID - The sys_id of the record to retrieve. Must be the sys_id of\n\t *  a valid record which the user has permissions to see, in the table specified in the\n\t *  constructor when instantiating this object.\n\t * @param {function} callbackFn - The callback function to be called when the query is\n\t *  complete.\n\t * When the query is complete, this callback function will be called with one argument:\n\t *  the EfficientGlideRecord object containing the records resultant from your query.\n\t *  After querying (in your callback function), you can call methods such as .next()\n\t *  and .getValue() to iterate through the returned records and get field values.\n\t */\n\tget(sysID, callbackFn) {\n\t\tthis.addQuery('sys_id', sysID);\n\t\tthis.setLimit(1);\n\t\t\n\t\tthis.query(callbackFn);\n\t}\n\t\n\t/**\n\t * Perform the async query constructed by calling methods in this class, and get the\n\t * field(s) from the resultant record that were requested by calling\n\t *  .addField(fieldName, getDisplayValue)\n\t * @async\n\t * @param {function} callbackFn - The callback function to be called\n\t *  when the query is complete.\n\t * When the query is complete, this callback function will be called with one argument:\n\t *  the EfficientGlideRecord object containing the records resultant from your query.\n\t *  After querying (in your callback function), you can call methods such as .next()\n\t *  and .getValue() to iterate through the returned records and get field values.\n\t */\n\tquery(callbackFn) {\n\t\tlet paramName;\n\t\t\n\t\tif (!this._readyToSend()) {\n\t\t\t//Meaningful errors are logged by this._readyToSend().\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tfor (paramName in this._config) {\n\t\t\t//Prevent iteration into non-own properties\n\t\t\tif (!this._config.hasOwnProperty(paramName)) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\t\n\t\t\tlet paramVal;\n\t\t\t\n\t\t\tif (typeof this._config[paramName] === 'object') {\n\t\t\t\tparamVal = JSON.stringify(this._config[paramName]);\n\t\t\t} else {\n\t\t\t\tparamVal = this._config[paramName];\n\t\t\t}\n\t\t\t\n\t\t\tthis._gaQuery.addParam(\n\t\t\t\tparamName,\n\t\t\t\tparamVal\n\t\t\t);\n\t\t}\n\t\tthis._gaQuery.getXMLAnswer(function (answer, eGR) {\n\t\t\t\n\t\t\tanswer = JSON.parse(answer);\n\t\t\t//let answer = response.responseXML.documentElement.getAttribute('answer');\n\t\t\t// answer = JSON.parse(answer); //Throws if unparseable -- good.\n\t\t\tif (!answer.hasOwnProperty('_records')) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'Something went wrong when attempting to get records from the server.\\n' +\n\t\t\t\t\t'Response object: \\n' +\n\t\t\t\t\tJSON.stringify(answer)\n\t\t\t\t);\n\t\t\t}\n\t\t\t\n\t\t\teGR._query_complete = true;\n\t\t\teGR._records = answer._records;\n\t\t\teGR._row_count = answer._row_count;\n\t\t\teGR._executing_as = answer._executing_as;\n\t\t\t\n\t\t\t\n\t\t\tcallbackFn(eGR);\n\t\t}, null, this);\n\t\t\n\t}\n\t\n\t/* The following methods can only be called after the query is performed */\n\t\n\t/**\n\t * Check if there is a \"next\" record to iterate into using .next() (without actually positioning the current record to the next one).\n\t * Can only be called from the callback function passed into .query()/.get() after the query\n\t *  has completed.\n\t * @returns {boolean} - True if there is a \"next\" record to iterate into, or false if not.\n\t */\n\thasNext() {\n\t\tif (!this._query_complete) {\n\t\t\t/*throw new Error(\n\t\t\t\t'The .hasNext() method of EfficientGlideRecord can only be called from the ' +\n\t\t\t\t'callback function after calling .query()'\n\t\t\t);*/\n\t\t\t\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn (this._row_count > (this._current_record_index + 1));\n\t}\n\t\n\t/**\n\t * Iterate into the next record, if one exists.\n\t * Usage is the same as GlideRecord's .next() method.\n\t * @returns {boolean} - True if there was a \"next\" record, and we've successfully positioned\n\t *  into it. False if not. Can only be run from the callback function after a query using .query() or .get().\n\t */\n\tnext() {\n\t\tif (!this._query_complete) {\n\t\t\t/*throw new Error(\n\t\t\t\t'The .next() method of EfficientGlideRecord can only be called from the ' +\n\t\t\t\t'callback function after calling .query()'\n\t\t\t);*/\n\t\t\t\n\t\t\treturn false;\n\t\t}\n\t\tif (!this.hasNext()) {\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tthis._current_record_index++;\n\t\tthis._current_record = this._records[this._current_record_index];\n\t\t\n\t\treturn true;\n\t}\n\t\n\t/**\n\t * Returns true if the specified field exists and can be read (even if it's blank).\n\t * Will return false in the following cases:\n\t *  -The specified field on the current record cannot be read\n\t *  -The specified field does not exist in the response object (which may happen if you don't\n\t *   add the field to your request using .addField()).\n\t *  -The specified field does not exist in the database\n\t * @param {String} fieldName - The name of the field to check whether the user can read or not.\n\t * @returns {Boolean} - Returns true if the specified field exists and can be read, or\n\t *  false otherwise.\n\t */\n\tcanRead(fieldName) {\n\t\tif (!this._query_complete) {\n\t\t\tthrow new Error(\n\t\t\t\t'The .canRead() method of EfficientGlideRecord can only be called from the ' +\n\t\t\t\t'callback function after calling .query(callbackFn)'\n\t\t\t);\n\t\t}\n\t\t\n\t\tif (!this._current_record._field_values.hasOwnProperty(fieldName)) {\n\t\t\tconsole.warn(\n\t\t\t\t'There is no field with the name ' + fieldName + ' in the ' +\n\t\t\t\t'EfficientGlideRecord object. Did you remember to specify that you want to ' +\n\t\t\t\t'get that field in the query using .addField()?'\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif (!this._current_record._field_values[fieldName].hasOwnProperty('can_read')) {\n\t\t\tconsole.warn(\n\t\t\t\t'The requested field \"' + fieldName + '\" has no can_read node. ' +\n\t\t\t\t'This should not happen. Returning a blank false.'\n\t\t\t)\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\treturn (!!this._current_record._field_values[fieldName].can_read) || false;\n\t}\n\t\n\t/**\n\t * Retrieve the database value for the specified field, if the user has permissions to read that field's value.\n\t * @param fieldName\n\t * @returns {string}\n\t */\n\tgetValue(fieldName) {\n\t\tif (!this._query_complete) {\n\t\t\tthrow new Error(\n\t\t\t\t'The .getValue() method of EfficientGlideRecord can only be called from the ' +\n\t\t\t\t'callback function after calling .query(callbackFn)'\n\t\t\t);\n\t\t}\n\t\t\n\t\tif (!this._current_record._field_values.hasOwnProperty(fieldName)) {\n\t\t\tconsole.warn(\n\t\t\t\t'There is no field with the name ' + fieldName + ' in the ' +\n\t\t\t\t'EfficientGlideRecord object. Did you remember to specify that you want to ' +\n\t\t\t\t'get that field in the query using .addField()?'\n\t\t\t);\n\t\t\treturn '';\n\t\t}\n\t\t\n\t\tif (!this._current_record._field_values[fieldName].hasOwnProperty('value')) {\n\t\t\tconsole.warn(\n\t\t\t\t'The requested field \"' + fieldName + '\" has no value node. ' +\n\t\t\t\t'This should not happen. Returning a blank string.'\n\t\t\t)\n\t\t\treturn '';\n\t\t}\n\t\t\n\t\treturn this._current_record._field_values[fieldName].value || '';\n\t}\n\t\n\t/**\n\t * Retrieve the display value for the specified field, if the user has permission to view\n\t *  that field's value.\n\t * Can only be called from the callback function after the query is complete.\n\t * @param fieldName\n\t * @returns {string|*|string}\n\t */\n\tgetDisplayValue(fieldName) {\n\t\tif (!this._query_complete) {\n\t\t\tthrow new Error(\n\t\t\t\t'The .getDisplayValue() method of EfficientGlideRecord can only be called from the ' +\n\t\t\t\t'callback function after calling .query(callbackFn)'\n\t\t\t);\n\t\t}\n\t\tif (!this._current_record._field_values.hasOwnProperty(fieldName)) {\n\t\t\tconsole.warn(\n\t\t\t\t'There is no field with the name ' + fieldName + ' in the ' +\n\t\t\t\t'EfficientGlideRecord object. Did you remember to specify that you want to ' +\n\t\t\t\t'get that field in the query using .addField()?'\n\t\t\t);\n\t\t\treturn '';\n\t\t}\n\t\tif (\n\t\t\t!this._current_record._field_values[fieldName].hasOwnProperty('display_value') ||\n\t\t\t!this._current_record._field_values[fieldName].display_value\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t'There is no display value for the field with the name ' + fieldName +\n\t\t\t\t' in the EfficientGlideRecord object. Did you remember to specify that you ' +\n\t\t\t\t'want to get that field\\'s display value in the query using ' +\n\t\t\t\t'.addField(fieldName, true)?'\n\t\t\t);\n\t\t\treturn '';\n\t\t}\n\t\t\n\t\treturn this._current_record._field_values[fieldName].display_value || '';\n\t}\n\t\n\t/**\n\t * Retrieves the number of records returned from the query.\n\t * If used in conjunction with .setLimit(), then the maximum value returned from this\n\t *  method will be the limit number (since no more records than the specified limit can\n\t *  be returned from the server).\n\t *\n\t * @returns {number} - The number of records returned from the query.\n\t * @example\n\t * //Show the number of child Incidents missing Short Descriptions.\n\t * new EfficientGlideRecord('incident')\n\t * \t.addQuery('parent', g_form.getUniqueValue())\n\t * \t.addNullQuery('short_description')\n\t * \t.addField('number')\n\t * \t.query(function (egrIncident) {\n\t * \t\tif (egrIncident.hasNext()) {\n\t * \t\t\tg_form.addErrorMessage(\n\t * \t\t\t\tegrIncident.getRowCount() + ' child Incidents are missing a short description.'\n\t * \t\t\t);\n\t * \t\t}\n\t * \t});\n\t * \t@since 1.0.1\n\t */\n\tgetRowCount() {\n\t\treturn this._row_count;\n\t}\n\t\n\t/* Private helper methods below */\n\t\n\t_readyToSend() {\n\t\tif (!this._config.table_to_query) {\n\t\t\tconsole.error(\n\t\t\t\t'EfficientGlideRecord not ready to query. Table name was not specified in ' +\n\t\t\t\t'the constructor\\'s initialize argument.'\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\tif (\n\t\t\t!this._config.fields_to_get ||\n\t\t\tthis._config.fields_to_get.length < 1\n\t\t) {\n\t\t\tconsole.error(\n\t\t\t\t'EfficientGlideRecord not ready to query. No fields were specified to ' +\n\t\t\t\t'retrieve. \\nPlease specify which fields you want to retrieve from the ' +\n\t\t\t\t'GlideRecord object using .addField(fieldName, getDisplayValue). ' +\n\t\t\t\t'Afterward, in your callback, you can use .getValue(fieldName). If ' +\n\t\t\t\t'you set getDisplayValue to true, you can also use ' +\n\t\t\t\t'.getDisplayValue(fieldName).'\n\t\t\t);\n\t\t\treturn false;\n\t\t}\n\t\t\n\t\t//Warn if queries AND encoded queries are both empty and limit is unspecified\n\t\t// (but don't return false)\n\t\tif (\n\t\t\t(\n\t\t\t\t!this._config.hasOwnProperty('queries') ||\n\t\t\t\tthis._config.queries.length < 1\n\t\t\t) &&\n\t\t\t(\n\t\t\t\t!this._config.hasOwnProperty('encoded_queries') ||\n\t\t\t\tthis._config.encoded_queries.length < 1\n\t\t\t) &&\n\t\t\t(\n\t\t\t\t!this._config.hasOwnProperty('record_limit') ||\n\t\t\t\tthis._config.record_limit < 1\n\t\t\t)\n\t\t) {\n\t\t\tconsole.warn(\n\t\t\t\t'The EfficientGlideRecord query has no query and no record limit ' +\n\t\t\t\t'associated with it. This may result in poor performance when querying larger ' +\n\t\t\t\t'tables. Please make sure that you need all records in the specified table, ' +\n\t\t\t\t'as all records will be returned by this query.'\n\t\t\t);\n\t\t}\n\t\t\n\t\t//Return true if none of the above validations have failed.\n\t\treturn true;\n\t}\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/EfficientGlideRecord (Client-side)/Example usage (client-side code).js",
    "content": "new EfficientGlideRecord('incident')\n\t.setLimit(10)\n\t.addQuery('assignment_group', '!=', 'some_group_sys_id')\n\t.addQuery('assigned_to', 'some_assignee_sys_id')\n\t.addNotNullQuery('assignment_group')\n\t.addField('number')\n\t.addField('short_description')\n\t.addField('assignment_group', true) //Get display value as well\n\t.orderBy('number')\n\t.query(function (egrIncident) {\n\t\twhile (egrIncident.next()) {\n\t\t\tvar logMsg = '';\n\t\t\tif (egrIncident.canRead('short_description')) {\n\t\t\t\tlogMsg += 'Short description value: ' + egrIncident.getValue('short_description') + '\\n';\n\t\t\t}\n\t\t\tif (egrIncident.canRead('number')) {\n\t\t\t\tlogMsg += 'Number: ' + egrIncident.getValue('number') + '\\n';\n\t\t\t}\n\t\t\tif (egrIncident.canRead('assignment_group')) {\n\t\t\t\tlogMsg += 'Assignment group: ' + egrIncident.getValue('assignment_group') + ' (' +\n\t\t\t\t\tegrIncident.getDisplayValue('assignment_group') + ')';\n\t\t\t}\n\t\t\t\n\t\t\tconsole.log(logMsg);\n\t\t}\n\t});\n\n\n//Get IDs, numbers, and assignment groups for all child Incidents with missing short descriptions\nnew EfficientGlideRecord('incident')\n\t.addQuery('parent', g_form.getUniqueValue())\n\t.addNullQuery('short_description')\n\t.addField('number')\n\t.addField('assignment_group', true) //Get display value as well\n\t.orderBy('number')\n\t.query(function (egrIncident) {\n\t\tvar incidentsWithoutShortDesc = [];\n\t\twhile (egrIncident.next()) {\n\t\t\tincidentsWithoutShortDesc.push(egrIncident.getValue('number'));\n\t\t}\n\t\tif (incidentsWithoutShortDesc.length > 0) {\n\t\t\tg_form.addErrorMessage(\n\t\t\t\t'The following child Incidents have no short description:<br />* ' +\n\t\t\t\tincidentsWithoutShortDesc.join('<br />* ')\n\t\t\t);\n\t\t}\n\t});\n\n//Does at least one child Incident exist without a short description?\nnew EfficientGlideRecord('incident')\n\t.addQuery('parent', g_form.getUniqueValue())\n\t.addNullQuery('short_description')\n\t.addField('number')\n\t.query(function (egrIncident) {\n\t\tif (egrIncident.hasNext()) {\n\t\t\tg_form.addErrorMessage(\n\t\t\t\t'At least one child Incident exists without a short description.'\n\t\t\t);\n\t\t}\n\t});\n\n//Show the number of child Incidents missing Short Descriptions.\nnew EfficientGlideRecord('incident')\n\t.addQuery('parent', g_form.getUniqueValue())\n\t.addNullQuery('short_description')\n\t.addField('number')\n\t.query(function (egrIncident) {\n\t\tif (egrIncident.hasNext()) {\n\t\t\tg_form.addErrorMessage(\n\t\t\t\tegrIncident.getRowCount() + ' child Incidents are missing a short description.'\n\t\t\t);\n\t\t}\n\t});\n\n//Get assignment groups for child Incidents for some reason\nnew EfficientGlideRecord('incident')\n\t.addQuery('parent', g_form.getUniqueValue())\n\t.addField('assignment_group', true) //Get display value as well\n\t.query(function (egrIncident) {\n\t\tvar assignmentGroupNames = [];\n\t\twhile (egrIncident.next()) {\n\t\t\tassignmentGroupNames.push(egrIncident.getDisplayValue('assignment_group'));\n\t\t}\n\t\t//todo: Do something with the assignment group names\n\t});"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/EfficientGlideRecord (Client-side)/README.md",
    "content": "# EfficientGlideRecord\nSee related article for full usage instructions and API documentation:  \nhttps://go.snc.guru/egr"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Fetch Multiple Values in GlideAjax without JSON/ClientScript.js",
    "content": "function onLoad() {\n\n    var ga = new GlideAjax(\"TestScriptInclude\");\n    ga.addParam(\"sysparm_name\", \"getCalculations\");\n    ga.addParam(\"sysparm_input1\", 50);\n    ga.addParam(\"sysparm_input2\", 10);\n    ga.getXML(function(response) {\n\t\tvar ele = response.responseXML.documentElement;\n\n\t\tvar add = ele.getAttribute(\"add\");\n\t\tvar sub = ele.getAttribute(\"sub\");\n\t\tvar mul = ele.getAttribute(\"mul\");\n\t\tvar div = ele.getAttribute(\"div\");\n\n\t\tvar message = \"\";\n\t\tmessage = message + \"Addition: \" + add + \"\\n\";\n\t\tmessage = message + \"Subtraction: \" + sub + \"\\n\";\n\t\tmessage = message + \"Multiplication: \" + mul + \"\\n\";\n\t\tmessage = message + \"Subtraction: \" + div + \"\\n\";\n\n\t\tg_form.addInfoMessage(message);\n    });\n\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Fetch Multiple Values in GlideAjax without JSON/README.md",
    "content": "This code snippet allows to receive multiple response from server side to client side using GlideAjax without using the complex JSON format. You can set multiple attributes in server side, and use the same attribute at the client side to receive responses.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Fetch Multiple Values in GlideAjax without JSON/TestScriptInclude.js",
    "content": "var TestScriptInclude = Class.create();\nTestScriptInclude.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\tgetCalculations: function(){\n\t\tvar input1 = Number(this.getParameter(\"sysparm_input1\"));\n\t\tvar input2 = Number(this.getParameter(\"sysparm_input2\"));\n\n\t\tvar add = input1 + input2;\n\t\tthis.getRootElement().setAttribute('add', add);\n\n\t\tvar sub = input1 - input2;\n\t\tthis.getRootElement().setAttribute('sub', sub);\n\n\t\tvar mul = input1 * input2;\n\t\tthis.getRootElement().setAttribute('mul', mul);\n\n\t\tvar div = input1 / input2;\n\t\tthis.getRootElement().setAttribute('div', div);\n\t},\n\n    type: 'TestScriptInclude'\n});"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Get Field Values/GetFieldValuesAjax.js",
    "content": "var GetFieldValuesAjax = Class.create();\nGetFieldValuesAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getValues: function() {\n        var table = this.getParameter(\"sysparm_table_name\");\n        var sys_id = this.getParameter(\"sysparm_sys_id\");\n        var fields = '' + this.getParameter(\"sysparm_field_names\");\n\n        return JSON.stringify(\n            this._getValues(table, sys_id, fields.split(','))\n        );\n    },\n\n    /**SNDOC\n     @name _getValues\n     @private\n     @description \n     Get the requested values from the table/record specified.\n\t Uses GlideRecord to allow for dot-walking and top-level values from the same field\n     @param {string} [table_name] - table to get record from\n     @param {string} [record_sys_id] - sys_id of record to query\n     @param {array} [field_names] - list of field names to retrieve\n     @returns {JSON} JSON object containing each field and its display equivalent\n     @example\n     _getValues('sys_user', '62826bf03710200044e0bfc8bcbe5df1', ['user_name', 'first_name', 'last_name', 'email', 'manager.email' ])\n    */\n    _getValues: function(table, sys_id, fields) {\n        var json = {};\n        try {\n\n            var gr = new GlideRecord(table);\n            gr.get(sys_id);\n\n            fields.forEach(function(_field) {\n                var ge = gr.getElement(_field);\n                if (ge) {\n                    json[_field] = {\n                        \"value\": ge.getValue(),\n                        \"displayValue\": ge.getDisplayValue()\n                    };\n                }\n            });\n            return json;\n        } catch (e) {\n            gs.error(\"(GetFieldValuesAjax) Problem getting field values \" + e.toString());\n            return {};\n        }\n    },\n\n    type: 'GetFieldValuesAjax'\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Get Field Values/README.md",
    "content": "# Get Field Values\n\nSimple GlideAjax script to get field values from a specified record.  Can query fields including dot walked. \nReturns value and displayValue as JSON object.\n\n```json\n{\n   \"number\": {\n      \"value\": \"INC0010051\",\n      \"displayValue\": \"INC0010051\"\n   },\n   \"caller_id.email\": {\n      \"value\": \"fran.zanders@example.com\",\n      \"displayValue\": \"fran.zanders@example.com\"\n   },\n   \"short_description\": {\n      \"value\": \"New Ticket\",\n      \"displayValue\": \"New Ticket\"\n   },\n   \"state\": {\n      \"value\": \"2\",\n      \"displayValue\": \"In Progress\"\n   },\n   \"priority\": {\n      \"value\": \"1\",\n      \"displayValue\": \"1 - Critical\"\n   }\n}\n```\n\n\n```javascript\nfunction onLoad() {\n\n    var ga = new GlideAjax(\"GetFieldValuesAjax\");\n    ga.addParam('sysparm_name', 'getValues');\n    ga.addParam('sysparm_table_name', 'incident');\n    ga.addParam('sysparm_sys_id', '915a93a9473d751001612c44846d4367');\n    ga.addParam('sysparm_field_names', 'number,caller_id.email,short_description,state,priority');\n    ga.getXMLAnswer(function(_answer) {\n\t\tvar json = JSON.parse(_answer);\n        console.log(JSON.stringify(json, '', 3));\n    })\n\n}\n```\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Get choices from Decision Table/GetChoicesFromDT.js",
    "content": "var GetChoicesFromDT = Class.create();\nGetChoicesFromDT.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\n    getChoices: function() {\n\t    \n         /**\n\t * Gets the defined choices for the passed in catalog item\n  \t * \n    \t * @author Laszlo Balla\n\t * @param {String} sysparm_cat_item\n  \t *    The sys_id of the catalog item to get choices for - mandatory\n    \t * @param {String} sysparm_cat_variable\n      \t *    Value from an additional catalog variable to evaluate as part of your decision - optional\n\t * @return {String}\n  \t *    A stringified array (since it goes to client script) of choices\n  \t */\n\t\n\t\n\t /**\n         * In addition to the above, the following variable MUST be set for the script to work:\n\t *\n\t ** decisionTableId : Sys ID of the decision table. Store in a system property and set with gs.getProperty()\n         ** dtInput1, 2, etc. :  the technical names of the Decision Table inputs\n\t ** resultColumn : the technical name of the result column of your Decision Table that has the choices set\n         */\n\n        var catItem = gs.nil(this.getParameter('sysparm_cat_item')) ? null : this.getParameter('sysparm_cat_item'); // Mandatory parameter\n        var catVar = gs.nil(this.getParameter('sysparm_cat_variable')) ? null : this.getParameter('sysparm_cat_variable'); // Optional parameter example (variable from record producer). Multiple as needed, or remove if not.\n        var decisionTableId = ''; //Sys ID of the decision table. Store in a system property and set with gs.getProperty()\n        var dtInput1 = 'u_catalog_item'; // Make sure you set this to the technical name of the first input of your Decision Table\n        var dtInput2 = 'u_catalog_variable'; // Make sure you set this to the technical name of the second input of your Decision Table, if you have one. Multiply as needed, or remove if not.\n        var resultColumn = 'u_choice_result'; // Set this to the technical name of the result column that contains your choices\n        var answerArray = [];\n        var choiceArr = [];\n        var iter1 = 0;\n\n        if (!gs.nil(catItem) && !gs.nil(decisionTableId)) {\n            var choiceQuery = 'var__m_sys_decision_multi_result_element_' + decisionTableId;\n            var decisonTable = new sn_dt.DecisionTableAPI();\n            var inputs = new Object();\n            inputs[dtInput1] = '' + catItem;\n\n            // Repeat this block as necessary with additional parameters and inputs\n            if (!gs.nil(catVar)) {\n                inputs[dtInput2] = '' + catVar;\n            }\n\n            var dtResponse = decisonTable.getDecisions(decisionTableId, inputs);\n            while (iter1 < dtResponse.length) {\n                answerArray.push(dtResponse[iter1]['result_elements'][resultColumn].toString());\n                iter1++;\n            }\n\t   // Now find the the actual choices with labels\n            var choiceGr = new GlideRecord('sys_choice');\n            choiceGr.addQuery('name', choiceQuery);\n            choiceGr.addQuery('value', 'IN', answerArray.toString());\n            choiceGr.setLimit(30); // The Choice table is huge, so I recommend setting a reasonable query limit. You should have an idea of the max # of results anyway.\n            choiceGr.query();\n            while (choiceGr.next()) {\n                var choice = {};\n                choice['value'] = choiceGr.getValue('value');\n                choice['label'] = choiceGr.getValue('label');\n                choiceArr.push(choice);\n            }\n\n            return JSON.stringify(choiceArr); // Return a stringified array to the client\n\n        } else {\n            gs.error('GetChoicesFromDT Script include did not run as the catItem mandatory variable is null: ' + catItem + ' or decision table sys_id is empty: ' + decisionTableId);\n            return;\n        }\n    },\n\n    type: 'GetChoicesFromDT'\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Get choices from Decision Table/README.md",
    "content": "# Prerequisites\n## Decision table\n> [!IMPORTANT]\n> Create a Decision table with a result column of type '**Choice**', and at least one, **mandatory input** that is a reference to sc_cat_item.\n\nYou will have to define the decisions for each catalog item separately. One catalog item can have multiple results - this is how you will get multiple choices for your selectbox variable at the end.\nIf you have other inputs, i.e. for different values from different variables, you simple add those conditions for each decision line for the related catalog item.\n> [!NOTE]\n> If you created the choices inside the Decision Table, make sure the values are not too long. They end up getting truncated in the sys_decision_answer table, and sebsequently the values stored in sys_choice will not match. If necessary, change the default value to something short and unique (or use an existing choice list if you can).\n\n## Variables in the Script Include\n> [!IMPORTANT]\n> Make sure the following variables have a valid value in your script include:\n* `var decisionTableId = '';`  The Sys ID of the decision table. Store in a system property and set with gs.getProperty().\n* `var dtInput1 = 'u_catalog_item';`  Make sure you set this to the technical name of the first input of your Decision Table. It will always start with u_. If unsure, check the **sys_decision_input** table.\n* `var dtInput2 = 'u_catalog_variable';` Make sure you set this to the technical name of the second input of your Decision Table, if you have one. Multiply as needed (if you have more inuts), or remove / comment out if not.\n* `var resultColumn = 'u_choice_result';` Set this to the technical name of the result column that contains your choices\n\n## Variables in the client script\n> [!IMPORTANT]\n> Remember to define the target field (the one you want to add the choices to) in row 3: `var targetChoiceField = 'choice_field';`\n\nIf you want send values from other variables for your decision table to consider, add them as additional parameters, in line with what you have defined in the Script include, for instance: `dtChoiceAjax.addParam('sysparm_cat_variable', g_form.getValue('some_variable'));`\n\n# Usage\nUse the provided Script Include and Client Script, and update them as mentioned in the [Prerequisites](#prerequisites) section. The example client script is **onLoad**, but if you are looking to use variable values as additional inputs, you will want to have it run as an **onChange** script instead, or as a scripted UI Policy - it should work the same way.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Get choices from Decision Table/addChoicesClient.js",
    "content": "function onLoad() {\n\n    var targetChoiceField = 'choice_field'; // Set this to the name of the Selectbox variable you want to populate\n    g_form.clearOptions(targetChoiceField);\n\n    var catItem = g_form.getUniqueValue();\n    var dtChoiceAjax = new GlideAjax('global.GetChoicesFromDT'); // Set this to the name of the script include with the relevant scope\n    dtChoiceAjax.addParam('sysparm_name', 'getChoices');\n    dtChoiceAjax.addParam('sysparm_cat_item', catItem);\n    /*\n     * Add an other option parameter, e.g.:\n     * dtChoiceAjax.addParam('sysparm_cat_variable', g_form.getValue('some_variable'));\n     */\n    dtChoiceAjax.getXMLAnswer(setChoices);\n\n    function setChoices(answer) {\n        if (answer) {\n            var choiceArray = JSON.parse(answer);\n            if (choiceArray.length == 0) {\n                // Do something if the response is empty\n                g_form.setReadOnly(targetChoiceField, false);\n                g_form.setMandatory(targetChoiceField, false);\n                g_form.setDisplay(targetChoiceField, false);\n            } else {\n                g_form.setDisplay(targetChoiceField, true);\n\n                // Similarly, you might want to do something if there is only one choice, e.g. set that by default and make the field read-only. \n                var isSingleChoice = choiceArray.length == 1 ? true : false;\n                if (isSingleChoice) {\n                    g_form.addOption(targetChoiceField, choiceArray[0].value, choiceArray[0].label);\n                    g_form.setValue(targetChoiceField, choiceArray[0].value);\n                    g_form.setReadOnly(targetChoiceField, true);\n                } else {\n                    // And finally, if you have multiple options, decide how you want your field to behave\n                    g_form.setReadOnly(targetChoiceField, false);\n                    g_form.addOption(targetChoiceField, '', '-- None --'); // Adding None option - this is also optional\n                    for (i = 0; i < choiceArray.length; i++) {\n                        g_form.addOption(targetChoiceField, choiceArray[i].value, choiceArray[i].label, i + 1);\n                    }\n                    g_form.setMandatory(targetChoiceField, true); \n                }\n            }\n        } else {\n\t\t\t// What if there was no answer return from the script include at all?\n            g_form.setReadOnly(targetChoiceField, false);\n            g_form.setMandatory(targetChoiceField, false);\n            g_form.setDisplay(targetChoiceField, false);\n        }\n    }\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/GlideAjax Example Template/README.md",
    "content": "# GlideAjax Example Template\n\nWhen you need a client to contact the server after all OnLoad scripts have concluded.\n\nCopy and paste and just change values around.\n\n### Changes\n  * Used GlideQuery instead of GlideRecord for better performance for counting records.\n  * Reducing couple of lines of code.\n  * Used g_form.addInfoMessage instead of alert for showing message.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/GlideAjax Example Template/code.js",
    "content": "//Example AJAX call to get number of related incidents for a change. Notice that this example has two scripts that should be on two different tables.\n\n//Client Script: \nfunction onLoad() { \n\n    var chgSysId = g_form.getUniqueValue(); \n\n    var ga = new GlideAjax('ChangeManagementRelatedRecords'); \n    ga.addParam('sysparm_name','getIncidentCount'); \n    ga.addParam('sysparm_change_id', chgSysId); \n    ga.getXMLAnswer(GetRelatedIncidentCount); \n\n    function GetRelatedIncidentCount(answer) {\n        if (answer) { //Doing some check before showing the message\n            g_form.addInfoMessage('Related Incidents: ' + answer); //using g_form instead of alert\n        }\n    } \n} \n\n\n//Script Include: \nvar ChangeManagementRelatedRecords = Class.create(); \nChangeManagementRelatedRecords.prototype = Object.extendsObject(AbstractAjaxProcessor, { \n\n    getIncidentCount: function() { \n\n        var changeID = this.getParameter('sysparm_change_id');\n        var incCount = new global.GlideQuery('incident') // Using GlideQuery instead of GlideRecord for better performance related to counting records. \n                       .where('rfc', changeID);\n                       .count();\n        return incCount;\n    }, \n      \n       _privateFunction: function() { // this function is not client callable      \n    } \n      \n}); \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/GlideAjax Example Template/example from community.js",
    "content": "// From https://community.servicenow.com/community?id=community_article&sys_id=9f7ce2e1dbd0dbc01dcaf3231f96196e\n// Client Side (Client Script):\nfunction onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n    var ga = new GlideAjax('asu_GetLocationData');\n    ga.addParam('sysparm_name', 'getCampus');\n    ga.addParam('sysparm_buildingid', g_form.getValue(\"u_building\"));\n    ga.getXML(updateCampus);\n}\n\nfunction updateCampus(response) {\n    var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n    var clearvalue; // Stays Undefined\n    if (answer) {\n        var returneddata = JSON.parse(answer);\n        g_form.setValue(\"campus\", returneddata.sys_id, returneddata.name);\n    } else {\n        g_form.setValue(\"campus\", clearvalue);\n    }\n}\n\n// Server Side (Script Include):\nvar asu_GetLocationData = Class.create();\nasu_GetLocationData.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getCampus: function() {\n        var buildingid = this.getParameter('sysparm_buildingid');\n        var loc = new GlideRecord('cmn_location');\n        if (loc.get(buildingid)) {\n            var campus = new GlideRecord('cmn_location');\n            if (campus.get(loc.parent)) {\n                var json = new JSON();\n                var results = {\n                    \"sys_id\": campus.getValue(\"sys_id\"),\n                    \"name\": campus.getValue(\"name\")\n                };\n                return JSON.stringify(results);\n            }\n        } else {\n            return null;\n        }\n    }\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Return Asset(s) for User/FindUserAsset.js",
    "content": "var getUserAsset = Class.create();\ngetUserAsset.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\n    ///////////////////////////////////////////////////////////////////////////////////////////////////////Return 1 Asset Any Model//////////////////////////////////////////////////////\n/////////////////////////////////////////////////////////////////////////////////////////\t\n\n    getOneAsset: function() {\n\n        var userSys = this.getParameter('sysparm_user');\n\n        var mAsset = new GlideRecord('alm_hardware');\n        mAsset.addQuery(\"assigned_to\", userSys);\n        mAsset.setLimit(1);\n        mAsset.query();\n        while (mAsset.next()) {\n            var astID = mAsset.sys_id;\n        }\n        return astID;\n\n    },\n\n    /////////////////////////////////////////////////////////////////////////////////////////\n //////////////////////Return 1 with specific Model Category////////////////////////////\n ///////////////////////////////////////////////////////////////////////////////////////\n\n    getAssetCustom: function() {\n\n        var userSys = this.getParameter('sysparm_user');\n        var astCat = this.getParameter('sysparm_category');\n\n        var mAsset = new GlideRecord('alm_hardware');\n        mAsset.addEncodedQuery('model_category=' + astCat + '^assigned_to=' + userSys);\n        mAsset.setLimit(1);\n        mAsset.query();\n        while (mAsset.next()) {\n            var astID = mAsset.sys_id;\n        }\n        return astID;\n\n    },\n\n    /////////////////////////////////////////////////////////////////////////////////////////\n ///////// Reutns All Assets for User////////////////////////////////////////////////////\n ///////////////////////////////////////////////////////////////////////////////////////\n\n    getAllAssets: function() {\n\n        var userSys = this.getParameter('sysparm_user');\n        var aList = [];\n\n        var mAsset = new GlideRecord('alm_hardware');\n        mAsset.addQuery(\"assigned_to\", userSys);\n        mAsset.setLimit(20); //Set Limi to 20 to prevent too many returns in case of a generic account/blank account is passed.\n        mAsset.query();\n        while (mAsset.next()) {\n            alist.push(mAsset.sys_id);\n        }\n        return aList;\n\n    },\n\n\n    type: 'getUserAsset'\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Return Asset(s) for User/README.md",
    "content": "Client Callable GlideAjax script include to return one of the Following based on passed user's sys_id:\n\n-Single Asset for User\n\n-Single Asset for User with Model Category filtering\n\n-All Assets for User\n\nScript queries the \"alm_hardware\" table. Intended to be called from a client script/catalog client script, one use case would be to populate a users asset on a catalog item form.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/ReturnMultipleProperties/README.md",
    "content": "## Use values from returned object in Ajax call\n- With this code snippet, make the ajax call from a client script.\n- Replace `HM_Task_Details` with script include name of your choosing.\n- Replace `sysparm_tableName` & `sysparm_recordID` with variable names from script include function. (can add as many as needed).\n- Replace second argument (next to `sysparm_name`) with name of funtion from script include you would like to call. \n- In callback function use obj to access the property values of that object as shown in snippet."
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/ReturnMultipleProperties/ReturnMultipleProperties.js",
    "content": "// From Client Script, make Ajax call to Script Include, include parameters (Examples below)\nvar ajax = new GlideAjax('HM_Task_Details');\najax.addParam('sysparm_tableName', 'sc_req_item');\najax.addParam('sysparm_recordID', g_form.getValue('icr_ritm_number'));\najax.addParam('sysparm_name', 'getDetails');\najax.getXMLAnswer(setAnswer);\n\n// Define callback function for async\nfunction setAnswer(answer) {\n\n    // Return parsed JSON object for answer\n    var obj = JSON.parse(answer);\n    \n    // Select object property and return its value via dot walking\n    if (!obj.short_description) {\n        g_form.setValue('icr_short_description', 'N/A');\t\t\t\t\n    } else {\n        g_form.setValue('icr_short_description', obj.short_description);\n    }\n}\n\n// !!! Script Include being called by client script (via AJAX) needs to return object in order for this to operate as intended !!!\n// Reach out to me if you have any questions, DevinValencia@yahoo.com"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Reusable GlideAjax/README.md",
    "content": "Reusable GlideAjax Example\nWhen you need a client side script to contact the server and return few values from a table record matching your query. You can use this reusable GlideAjax code.\n\n**An Example**\nNeed user fields like manager, Phone, Email matching sys_id of user:\nfunction onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n    var g_ajax = new GlideAjax('ScriptIncludeName'); // pass name of Client callable script include name\n    g_ajax.addParam('sysparm_name', 'scriptIncludeFunctionName'); // pass function name which will return result\n    g_ajax.addParam('sysparm_table', 'sys_user'); \n    g_ajax.addParam('sysparm_query', 'sys_id='+g_form.getValue('requested_for')); \n    g_ajax.addParam('sysparm_returnAttributes', 'manager,email,phone'); \n    g_ajax.getXMLAnswer(callbackFunctionName);\n\n    function callbackFunctionName(response) {\n        var answer = JSON.parse(response);\n        g_form.setValue('user_manager', answer.manager);\n        g_form.setValue('email', answer.email);\n        g_form.setValue('phone', answer.phone);\n    }\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Reusable GlideAjax/clientCallableScriptInclude.js",
    "content": "var ScriptIncludeName = Class.create();\nScriptIncludeName.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    scriptIncludeFunctionName: function() {\n        var resultObj = {};\n        var tableName = this.getParameter('sysparm_table');\n        var query = this.getParameter('sysparm_query');\n        var columns = this.getParameter('sysparm_returnAttributes').split(',');\n        if (tableName != '') {\n            var grObj = new GlideRecordSecure(tableName);\n            grObj.addEncodedQuery(query);\n            grObj.setLimit(1);\n            grObj.query();\n            if (grObj.next() && columns.length > 0) {\n                for (var i = 0; i < columns.length; i++) {\n                    resultObj[columns[i]] = grObj.getValue(columns[i]);\n                }\n            }\n        }\n        return JSON.stringify(resultObj);\n    },\n\n    type: 'ScriptIncludeName'\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Reusable GlideAjax/clientSideGlideAjax.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n    var g_ajax = new GlideAjax('scope.ScriptIncludeName'); // pass name of Client callable script include\n    g_ajax.addParam('sysparm_name', 'scriptIncludeFunctionName'); // pass function name which will return result\n    g_ajax.addParam('sysparm_table', 'table_name'); // e.g. incident\n    g_ajax.addParam('sysparm_query', 'anyencodedQueryString'); // e.g. 'number=INC0000010'\n    g_ajax.addParam('sysparm_returnAttributes', 'column1,column2'); // e.g. caller_id,assigned_to\n    g_ajax.getXMLAnswer(callbackFunctionName);\n\n    function callbackFunctionName(response) {\n        var answer = JSON.parse(response);\n        g_form.setValue('variable1', answer.column1);\n        g_form.setValue('variable2', answer.column2);\n    }\n\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Reusable glideajax table query/README.md",
    "content": "# glideAjax-table-column\n\nA Client Callable script include which will query to the table and will return the set of columns. Below is the example of this code.\nClient Side Usage Example:\n\nvar tableName = 'sys_user';\nvar user = g_user.userID;\nvar query = 'sys_id='+user;\nvar col = 'user_name,email';\nvar ga = new GlideAjax('getTableColumnsClientSide');\nga.addParam('sysparm_name','getColumns');\nga.addParam('sysparm_tableName',tableName);\nga.addParam('sysparm_encodedQuery',query);\nga.addParam('sysparm_columns',col);\nga.getXML(HelloWorldParse);\n\nfunction HelloWorldParse(response) {\nvar answer = response.responseXML.documentElement.getAttribute(\"answer\");\nalert(answer);\n}\nServer Side usage example:\n\nvar si = new getTableColumnsClientSide();\nvar user = gs.getUserID();\nvar query = 'sys_id='+user;\nvar col = 'user_name,email';\ngs.print(si.getColumns('sys_user',query,col));\n\nBenefit: We can share this scripts usage examples with the regional developer, who don't have access to write custom scripts for catalog client script.\nBenefit: Client Call and Server Call both use the same Script Include Function.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideAjax/Reusable glideajax table query/getTableColumnsClientSide.js",
    "content": "var getTableColumnsClientSide = Class.create();\ngetTableColumnsClientSide.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getColumns: function(tableName, encodedQuery, columns) {\n        /**\n        * Consolidates the Ajax and Server callable script includes to reduce future maintenance time.\n        */\n        if(tableName === undefined){\n           var tableName = this.getParameter('sysparm_tableName');\n           var encodedQuery = this.getParameter('sysparm_encodedQuery');\n           var columns = this.getParameter('sysparm_columns');\n        }\n        var returnData = [];\n        var mappCol = columns.split(',');\n        var fetchColumn = new GlideRecordSecure(tableName);\n        fetchColumn.addEncodedQuery(encodedQuery);\n        fetchColumn.setLimit(1); //Fetch only one record {Check the Query if it return wrong data}\n        fetchColumn.query();\n        if (fetchColumn.next()) {\n            //gs.log('found record');\n            for (var i = 0; i < mappCol.length; i++) {\n                var itemCol = {};\n                itemCol[mappCol[i]] = fetchColumn.getValue(mappCol[i].toString());\n                returnData.push(itemCol);\n            }\n    \n        }\n        return JSON.stringify(returnData);\n    },\n    type: 'getTableColumnsClientSide'\n});\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDate/Convert text date to GlideDate Format/Extract and Convert Date in a Text or String to GlideDate Format.js",
    "content": "/**\n * This code snippet extracts a date from a string formatted as \"20 Nov 2020\",\n * converts it to GlideDate format, and assigns the date value to the u_last_patch_date field.\n * \n * While this example is intended for use in a Business Rule (BR), it can be modified\n * and utilized in any script within ServiceNow.\n */\n(function executeRule(current, previous /*null when async*/) {\n\n    // Example value: \"kernel-headers-2.6.32-754.35.1.el6.x86_64 20 Nov 2020\"\n    var patchDetails = current.u_patch_description;\n\n    // Split the patchDetails string by spaces\n    var parts = patchDetails.split(' ');\n\n    // Find the last three parts which should be day, month, and year\n    var day = parts[parts.length - 3]; // Example: \"20\"\n    var month = parts[parts.length - 2]; // Example: \"Nov\"\n    var year = parts[parts.length - 1];  // Example: \"2020\"\n\n    // Construct the formatted date string in yyyy/mm/dd format\n    var formattedDate = year + '/' + monthToNumber(month) + '/' + (day ? day : '01');\n\n    // Function to convert month abbreviation to number (e.g., Jan to 01)\n    function monthToNumber(monthAbbreviation) {\n        var months = {\n            \"Jan\": \"01\", \"Feb\": \"02\", \"Mar\": \"03\", \"Apr\": \"04\", \"May\": \"05\", \"Jun\": \"06\",\n            \"Jul\": \"07\", \"Aug\": \"08\", \"Sep\": \"09\", \"Oct\": \"10\", \"Nov\": \"11\", \"Dec\": \"12\"\n        };\n        return months[monthAbbreviation] || '01'; // Default to '01' if month is not found\n    }\n\n    // Output the formatted date (for debugging purposes)\n    gs.info(\"Formatted Date: \" + formattedDate);\n\n    // Set the formatted date into the u_last_patch_date field using GlideDate\n    var gd = new GlideDate();\n    gd.setValue(formattedDate);\n    current.u_last_patch_date = gd.getDisplayValue(); // Store the date in GlideDate format\n\n    // Update the current record\n    current.setWorkflow(false);\n    current.update();\n\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDate/Convert text date to GlideDate Format/README.md",
    "content": "# Extract and Convert Date in a Text or String to GlideDate Format\n\n## Use Case / Requirement\nThis script extracts a date from a string formatted as \"20 Nov 2020\" and converts it into the GlideDate format used in ServiceNow. This is particularly useful for maintaining accurate records of software patches and updates.\n\n## Solution\nThe solution utilizes the `GlideDate` object to parse the date from the string and format it appropriately. The script extracts the day, month, and year, then constructs a formatted date string.\n\n## Example Input and Output\n- **Input Format**: Example string containing patch information\n  - Example: `\"kernel-headers-2.6.32-754.35.1.el6.x86_64 20 Nov 2020\"`\n- **Output Format**: \n  - GlideDate format: `2020-11-20` (if displayed in `yyyy-MM-dd` format)\n\n## Code Snippet\nPlease find the attached javascript file to know how to implement it. Thank you!\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/AddDays/README.md",
    "content": "A code snippet to set a default value on a date variable to a date relative (+/- days from today)\n\nExample Syntax:\n\njavascript:var gdt = new GlideDateTime(); gdt.addDays(-1);gdt.getDate();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/AddDays/addDays.js",
    "content": "//prefix with javascript: in default value field\n//Add or Subtract a relative amount of days from today\njavascript:var gdt = new GlideDateTime(); gdt.addDays(-1);gdt.getDate();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/BusinessTimeUtils.js",
    "content": "var BusinessTimeUtils = Class.create();\nBusinessTimeUtils.prototype = {\n  initialize: function() {},\n\n  /**\n   * Add working hours to a start date, respecting schedule and holidays.\n   * @param {String} scheduleSysId - sys_id of the GlideSchedule\n   * @param {Number} hoursToAdd - working hours to add (can be fractional)\n   * @param {GlideDateTime|String} startGdt - start time; if string, must be ISO/Glide-friendly\n   * @param {String} [timeZone] - optional IANA TZ, else schedule/instance TZ\n   * @returns {Object} { ok:Boolean, due:GlideDateTime|null, minutesAdded:Number, message:String }\n   */\n  addWorkingHours: function(scheduleSysId, hoursToAdd, startGdt, timeZone) {\n    var result = { ok: false, due: null, minutesAdded: 0, message: '' };\n    try {\n      this._assertSchedule(scheduleSysId);\n      var start = this._toGdt(startGdt);\n      var msToAdd = Math.round(Number(hoursToAdd) * 60 * 60 * 1000);\n      if (!isFinite(msToAdd) || msToAdd <= 0) {\n        result.message = 'hoursToAdd must be > 0';\n        return result;\n      }\n\n      var sched = new GlideSchedule(scheduleSysId, timeZone || '');\n      var due = sched.add(new GlideDateTime(start), msToAdd); // returns GlideDateTime\n\n      // How many working minutes were added according to the schedule\n      var mins = Math.round(sched.duration(start, due) / 60000);\n\n      result.ok = true;\n      result.due = due;\n      result.minutesAdded = mins;\n      return result;\n    } catch (e) {\n      result.message = String(e);\n      return result;\n    }\n  },\n\n  /**\n   * Calculate working minutes between two times using the schedule.\n   * @returns {Object} { ok:Boolean, minutes:Number, message:String }\n   */\n  workingMinutesBetween: function(scheduleSysId, startGdt, endGdt, timeZone) {\n    var out = { ok: false, minutes: 0, message: '' };\n    try {\n      this._assertSchedule(scheduleSysId);\n      var start = this._toGdt(startGdt);\n      var end = this._toGdt(endGdt);\n      if (start.after(end)) {\n        out.message = 'start must be <= end';\n        return out;\n      }\n      var sched = new GlideSchedule(scheduleSysId, timeZone || '');\n      out.minutes = Math.round(sched.duration(start, end) / 60000);\n      out.ok = true;\n      return out;\n    } catch (e) {\n      out.message = String(e);\n      return out;\n    }\n  },\n\n  /**\n   * Find the next time that is inside the schedule window at or after fromGdt.\n   * @returns {Object} { ok:Boolean, nextOpen:GlideDateTime|null, message:String }\n   */\n  nextOpen: function(scheduleSysId, fromGdt, timeZone) {\n    var out = { ok: false, nextOpen: null, message: '' };\n    try {\n      this._assertSchedule(scheduleSysId);\n      var from = this._toGdt(fromGdt);\n      var sched = new GlideSchedule(scheduleSysId, timeZone || '');\n\n      // If already inside schedule, return the same timestamp\n      if (sched.isInSchedule(from)) {\n        out.ok = true;\n        out.nextOpen = new GlideDateTime(from);\n        return out;\n      }\n\n      // Move forward minute by minute until we hit an in-schedule time, with a sane cap\n      var probe = new GlideDateTime(from);\n      var limitMinutes = 24 * 60 * 30; // cap search to 30 days\n      for (var i = 0; i < limitMinutes; i++) {\n        probe.addSecondsUTC(60);\n        if (sched.isInSchedule(probe)) {\n          out.ok = true;\n          out.nextOpen = new GlideDateTime(probe);\n          return out;\n        }\n      }\n      out.message = 'No open window found within 30 days';\n      return out;\n    } catch (e) {\n      out.message = String(e);\n      return out;\n    }\n  },\n\n  /**\n   * Check if a time is inside the schedule.\n   * @returns {Object} { ok:Boolean, inSchedule:Boolean, message:String }\n   */\n  isInSchedule: function(scheduleSysId, whenGdt, timeZone) {\n    var out = { ok: false, inSchedule: false, message: '' };\n    try {\n      this._assertSchedule(scheduleSysId);\n      var when = this._toGdt(whenGdt);\n      var sched = new GlideSchedule(scheduleSysId, timeZone || '');\n      out.inSchedule = sched.isInSchedule(when);\n      out.ok = true;\n      return out;\n    } catch (e) {\n      out.message = String(e);\n      return out;\n    }\n  },\n\n  // ---------- helpers ----------\n\n  _toGdt: function(val) {\n    if (val instanceof GlideDateTime) return new GlideDateTime(val);\n    if (typeof val === 'string' && val) return new GlideDateTime(val);\n    if (!val) return new GlideDateTime(); // default now\n    throw 'Unsupported datetime value';\n  },\n\n  _assertSchedule: function(sysId) {\n    if (!sysId) throw 'scheduleSysId is required';\n    var gr = new GlideRecord('cmn_schedule');\n    if (!gr.get(sysId)) throw 'Schedule not found: ' + sysId;\n  },\n\n  type: 'BusinessTimeUtils'\n};\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/README.md",
    "content": "# Business time utilities (add, diff, next open, in schedule)\n\n## What this solves\nTeams repeatedly reimplement the same business-time maths. This utility wraps `GlideSchedule` with four practical helpers so you can:\n- Add N working hours to a start date\n- Calculate working minutes between two dates\n- Find the next open time inside a schedule\n- Check if a specific time is inside the schedule window\n\nAll functions return simple objects that are easy to log, test, and consume in Flows or Rules.\n\n## Where to use\n- Script Include in global or scoped app\n- Call from Business Rules, Flow Actions, or Background Scripts\n\n## Functions\n- `addWorkingHours(scheduleSysId, hoursToAdd, startGdt, tz)`\n- `workingMinutesBetween(scheduleSysId, startGdt, endGdt, tz)`\n- `nextOpen(scheduleSysId, fromGdt, tz)`\n- `isInSchedule(scheduleSysId, whenGdt, tz)`\n\n## Notes\n- The schedule determines both business hours and holidays.\n- `tz` is optional; if omitted, the schedule’s TZ or instance default applies.\n- All inputs accept either `GlideDateTime` or ISO strings (UTC-safe).\n\n## References\n- GlideSchedule API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideSchedule/concept/c_GlideScheduleAPI.html\n- GlideDateTime API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Business time utilities (add, diff, next open, in schedule)/example_background_usage.js",
    "content": "// Background Script demo for BusinessTimeUtils\n(function() {\n  var SCHEDULE_SYS_ID = 'PUT_YOUR_SCHEDULE_SYS_ID_HERE';\n  var TZ = 'Europe/London';\n\n  var util = new BusinessTimeUtils();\n\n  var start = new GlideDateTime(); // now\n  var addRes = util.addWorkingHours(SCHEDULE_SYS_ID, 16, start, TZ);\n  gs.info('Add 16h ok=' + addRes.ok + ', due=' + (addRes.due ? addRes.due.getDisplayValue() : addRes.message));\n\n  var end = new GlideDateTime(addRes.due || start);\n  var diffRes = util.workingMinutesBetween(SCHEDULE_SYS_ID, start, end, TZ);\n  gs.info('Working minutes between start and due: ' + diffRes.minutes);\n\n  var openRes = util.nextOpen(SCHEDULE_SYS_ID, new GlideDateTime(), TZ);\n  gs.info('Next open ok=' + openRes.ok + ', at=' + (openRes.nextOpen ? openRes.nextOpen.getDisplayValue() : openRes.message));\n\n  var inRes = util.isInSchedule(SCHEDULE_SYS_ID, new GlideDateTime(), TZ);\n  gs.info('Is now in schedule: ' + inRes.inSchedule);\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Calculate Due date using user defined schedules/README.md",
    "content": "**Description:**\nThis  Script Include  calculates a future due date by adding a specified number of business days to a given start date, based on a defined schedule.\nThis can be used anywhere within the server side scripts like fix scripts, background scripts, UI Action (server script).\n\n**Pre-requisite:**\nA schedule record with valid schedule entries should be created in the cmn_schedule table\nA business hours value per day need to be configured\nIn this sample, the business hours per day is configured as 8 hours i.e 9AM - 5PM.\n\n**Sample:**\nvar daysToAdd = 4; // No of days need to be added\nvar script = new CaclculateDueDate().calculateDueDate(new GlideDateTime(),daysToAdd); // Passing the current date and daysToAdd value to script include\ngs.print(script);\n\n**Output:**\n*** Script: 2025-10-13 13:56:07\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Calculate Due date using user defined schedules/script.js",
    "content": "var CaclculateDueDate = Class.create();\nCaclculateDueDate.prototype = {\n    initialize: function() {},\n\n    calculateDueDate: function(date, days_to_add) {\n        var business_hour_per_day = 8; // This can be stored in the system property (Value in Hours) and reused\n        var duration_script = new DurationCalculator(); // OOB Script include\n        var tz = gs.getSysTimeZone(); // Get the system timezone\n\n        duration_script.setSchedule('c798c1dfc3907e1091ea5242b40131c8', tz); // Sys id of the schedule\n        duration_script.setStartDateTime(new GlideDateTime(date));\n        var total_duration = days_to_add * (business_hour_per_day * 60 * 60); // Converting the days to seconds\n        duration_script.calcDuration(total_duration);\n\n        var calculated_due_date = duration_script.getEndDateTime();\n        return calculated_due_date.getDisplayValue();\n    },\n\n    type: 'CaclculateDueDate'\n};\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Check if today is weekend/Check if today is weekend.js",
    "content": "var gr = new GlideDateTime(); \nvar day = gr.getDayOfWeekLocalTime(); \nif(day == 7 || day == 6)\n{\ngs.print(\"Today is Weekend\");\n}\nelse\n{\ngs.print(\"Today is working day\");\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Check if today is weekend/README.md",
    "content": "Use this script to know whether today is weekend or weekday.\n\nIt helps to restrict the schedule job/ any other activity during weekends.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Convert UTC Time To Local Time/readme.md",
    "content": "## Overview\nThis script converts a UTC date/time field in ServiceNow to the local time of the user that the script runs under using GlideDateTime.\nThis distinction is important in certain contexts, such as asynchronous business rules, scheduled jobs, or background scripts, where the executing user may differ from the record owner.\nIt is useful for notifications, reports, dashboards, or any situation where users need localized timestamps that reflect the correct timezone.\n\n## Table and Field Example\nTable: incident\nField: opened_at (stored in UTC)\n\n## How It Works\nThe script queries the incident table for the most recent active incident.\nRetrieves the opened_at field (in UTC).\nCreates a GlideDateTime object to convert this UTC timestamp into the local time of the executing user.\nLogs both the original UTC time and the converted local time.\n\n## Key Notes\nConversion is always based on the timezone of the user executing the script.\nIn asynchronous operations (background scripts, scheduled jobs, async business rules), this is the system user running the script.\n\n## Reference\nhttps://developer.servicenow.com/dev.do#!/reference/api/zurich/server_legacy/c_GlideDateTimeAPI\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Convert UTC Time To Local Time/script.js",
    "content": "(function() {\n    var gr = new GlideRecord('incident');\n    gr.addQuery('active', true);\n    gr.orderByDesc('opened_at');\n    gr.setLimit(1); // Example: take the latest active incident\n    gr.query();\n\n    if (gr.next()) {\n        // GlideDateTime object from UTC field\n        var utcDateTime = gr.opened_at;\n\n        // Convert to user's local time zone\n        var localTime = new GlideDateTime(utcDateTime);\n        var displayValue = localTime.getDisplayValue(); // Returns local time in user's timezone\n\n        gs.info('UTC Time: ' + utcDateTime);\n        gs.info('Local Time: ' + displayValue);\n    } else {\n        gs.info('No active incidents found.');\n    }\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Convert date format/README.md",
    "content": "# Convert date format\n\n**Use-case / Requirement :**\nConvert date from one format to another format\nFor example from dd-mm-yyyy to yyyy-mm-dd or yyyy/mm/dd etc\n\n**Solution :**\nUse GlideDate object and getByFormat method for conversion.\nCheck the script.js file to find the code\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Convert date format/script.js",
    "content": "var gd = new GlideDate(); //Intitialize GlideDate object\ngd.setValue('2021-13-10');  //you can pass date for which you need conversion\n//Now you can choose the format as required. Below are few examples\ngs.info(gd.getByFormat(\"dd-MM-yyyy\"));\ngs.info(gd.getByFormat(\"yyyy/MM/dd\"));\ngs.info(gd.getByFormat(\"dd/MM/yyyy\"));\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/ConvertTicksToGlideDateTime/ConvertTicksToGlideDateTime.js",
    "content": "/*\n    Parameters:\n    \tticks : Number\n    \n    Returns:\n        - GlideDateTime Object\n*/\nfunction convertTicksToGlideDateTime(ticks){\n    if(gs.nil(ticks)){\n        return new GlideDateTime(); //Return today if ticks is empty\n    }\n    \n    // Return the max date if ticks starts with '9'\n    var ticks_string = ticks.toString();\n    if (ticks_string.startsWith('9')){\n        return new GlideDateTime(\"2100-12-31 23:59:59\");\n    }\n\n    var ticks = ticks - (11644475008000 * 10000); //Trim the offset\n    var ms = ticks / 10000; //Convert to Milli seconds\n    var gdt = new GlideDateTime();\n    gdt.setNumericValue(ms);\n    return gdt;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/ConvertTicksToGlideDateTime/README.md",
    "content": "# .Net Ticks to GlideDateTime\n\nAn utility function to convert .Net ticks to GlideDateTime.\n\nA tick is 1/10000 of a milli second (1 Milli second = 10,000 ticks)\n\nThis is more useful when you are bringing the Date Time data from Microsoft tools such as Active Directory, which will provide date time values in ticks. By using this utility function we can convert it to ServiceNow native GlideDateTime object.\n\n### Example\n\n`var gdt = convertTicksToGlideDateTime(5954484981710000)`\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Current Date with Fixed Time/CurrentDateFixedTime.js",
    "content": "var gd = new GlideDate();\nvar gdt = new GlideDateTime();\ngdt.setValue(gd + \" 18:00:00\");\ngs.info(gdt);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Current Date with Fixed Time/README.md",
    "content": "This script helps to set fixed time to Present Date..\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Due date generation/README.md",
    "content": "For the workflows created using flow designer, the due date for sctasks is not populated automatically.\nThis code populates the due date of sctask automatically when RITMs are created through flow designers.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Due date generation/duedate_generation.js",
    "content": "var dateTime= new GlideDateTime();\nvar dc= new DurationCalculator(); \ndc.setStartDateTime(dateTime);\n//dc.setSchedule('08fcd0830a0a0b2600079f56b1adb9ae'); \ndc.calcDuration(30*24*3600); // Provide duration for the priority here, here I have provided T6 priority duration which is 30 days.\nreturn dc.getEndDateTime();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Find Incidents Older Than X Days/readme.md",
    "content": "## Overview\nThis script retrieves incidents that were opened more than X days ago using **GlideDateTime** and **GlideRecord**.  \nUseful for reporting, escalations, notifications, and cleanup tasks.\n\n## Table and Field\n- **Table:** `incident`\n- **Field:** `opened_at`\n\n## Parameters\n- **X (number of days):** Defines the threshold for old incidents (e.g., 30 days).\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Find Incidents Older Than X Days/script.js",
    "content": "(function() {\n    var days = 30; // Change this to your required number of days\n\n    // Calculate the date X days ago\n    var cutoffDate = new GlideDateTime();\n    cutoffDate.addDaysUTC(-days);\n\n    // Query incidents opened before the cutoff date\n    var gr = new GlideRecord('incident');\n    gr.addQuery('opened_at', '<', cutoffDate);\n    gr.query();\n\n    gs.info('Incidents opened more than ' + days + ' days ago:');\n\n    while (gr.next()) {\n        gs.info('Incident Number: ' + gr.number + ', Opened At: ' + gr.opened_at.getDisplayValue());\n    }\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Get Date Difference/GetDiffernceBtnDates.js",
    "content": "var date1 = new GlideDateTime(); \nvar date2 = new GlideDateTime(gs.daysAgo(-1000));\n\n//This function can be used to find the difference between two dates and get response in different formats\n\nfunction getDifferenceBtnDates(date1, date2, type){\n    var dur = new GlideDuration(new GlideDateTime.subtract(date1,date2));// find difference between two dates in duration\n    \n    switch(type){\n    case \"days\": return dur.getDayPart(); // get difference in days between two days\n    case \"ms\": return dur.getNumericValue(); // get difference in milliseconds\n    case \"timer\":  return dur.getDisplayValue(); //get difference in Days Hours Minute format\n    default : return dur; //get difference in yyyy:mm:dd hh:mm:ss format\n    }\n}\ngs.info(getDifferenceBtnDates(date1,date2, \"days\"));\ngs.info(getDifferenceBtnDates(date1,date2, \"ms\"));\ngs.info(getDifferenceBtnDates(date1,date2, \"timer\"));\ngs.info(getDifferenceBtnDates(date1,date2));\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Get Date Difference/README.md",
    "content": "This script can be used to get the difference between two dates in different formats.\nAll you have to do is to call the function and pass on the dates as 1st and 2nd parameter and the third parameter is the format in which you want the difference.\n1. Days (# in days)\n2. Milli Seconds (# in milliseonds)\n3. Timer (#days #hours #minute)\n4. Default format (yyyy:mm:dd hh:mm:ss)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Get Next Monday Date/GetNextMondayDate.js",
    "content": "function getNextMonday() {\n    var today = new GlideDate().getDayOfMonthNoTZ();\n    var dayOfWeek = new GlideDateTime().getDayOfWeek();\n\n    var daysUntilNextMonday = 8 - dayOfWeek; // 8 for Monday of next week\n    var nextMondayDate = new GlideDate();\n    nextMondayDate.addDaysLocalTime(daysUntilNextMonday);\n\n    gs.info(\"The next upcoming Monday is on \" + nextMondayDate.getDate() + \", in \" + daysUntilNextMonday + \" days.\");\n    \n    return nextMondayDate.getDate();\n}\n\ngetNextMonday();\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Get Next Monday Date/README.md",
    "content": "\n# A Date Function to get the next upcoming Monday date.\n\nThis function uses a few Glide date and Glide Date Time API's \n\n1. First we get todays date with GlideDateTime()\n\n2. Then we get the day of the month number with GlideDate().getDayOfMonthNoTZ();\n\n3. Then we use the make a calculation to set the day to the next monday\n\n4. Finally, we calcualtate the day using AddDays we return the New Date transforming it back with getDate()\n\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Get last day of a month/README.md",
    "content": "Code snippets to get the last day of a month from a date field or an GDT object.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Get last day of a month/getLastDayOfMonth.js",
    "content": "/**\n * Get last day of a month from date field\n */\nvar dateFieldValue = current.date_field;\nvar year = dateField.getFullYear();\nvar month = dateField.getMonth() + 1;\nvar daysInMonth = gs.daysInMonth(year, month);\nvar lastDayOfMonth = gs.dateGenerate(year, month, daysInMonth);\ngs.info(lastDayOfMonth);\n\n/**\n * Get last day of a month from GlideDateTime\n */\nvar gdt = new GlideDateTime();\ngdt.setDayOfMonthUTC(1000);\ngs.info(gdt.getDate());\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Set time zone and date format to output string dates/README.md",
    "content": "# Set time zone and date format to output dates.\nExamples\n```javascript\n// Set time zone and date format to output string dates.\n// Current time \"2021-10-23 16:29:08\" JST +0900\nvar gDateTime = new GlideDateTime();  \nvar calendar = Packages.java.util.Calendar.getInstance();\n//Import dates in milliseconds\ncalendar.setTimeInMillis(gDateTime.getNumericValue());\n//Java Date Object\nvar javaobjDate = calendar.getTime();\n\n// Set the date format you want to output.\nvar simpleDateFormat = new Packages.java.text.SimpleDateFormat(\"EEEE,MMMM dd,yyyy HH:mm:ss z Z\");\n// Time zone string for current user (\"Japan\")\nvar strTz = gs.getSession().getTimeZoneName();\n// Java TimeZone Object\nvar javaobjTz = Packages.java.util.TimeZone.getTimeZone(strTz);\n// Set TimeZone\nsimpleDateFormat.setTimeZone(javaobjTz);\n// Convert and date output\nvar strDate = '' + simpleDateFormat.format(javaobjDate);\n// Output \"Saturday,October 23,2021 16:29:08 JST +0900\"\ngs.info(strDate);\n```\n\n### Similar Functions: GlideDate.getByFormat()\nOutput in UTC, not in the time zone set by the user.\n\nExamples\n```javascript\n// Current time \"2021-10-23 17:03:56\" JST +0900\nvar gd = new GlideDate();\n// Time zone string for current user (\"Japan\")\nvar strTz = gs.getSession().getTimeZoneName();\nvar javaobjTz = Packages.java.util.TimeZone.getTimeZone(strTz);\ngd.setTZ(javaobjTz);\ngs.info( gd.getByFormat(\"EEEE,MMMM dd,yyyy HH:mm:ss z Z\") );\n// \"Saturday,October 23,2021 08:03:56 UTC +0000\"\n// Output in UTC, not in the time zone set by the user.\n```\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Set time zone and date format to output string dates/output_string_dates.js",
    "content": "// Set time zone and date format to output string dates.\n// Current time \"2021-10-23 16:29:08\" JST +0900\nvar gDateTime = new GlideDateTime();  \nvar calendar = Packages.java.util.Calendar.getInstance();\n//Import dates in milliseconds\ncalendar.setTimeInMillis(gDateTime.getNumericValue());\n//Java Date Object\nvar javaobjDate = calendar.getTime();\n\n// Set the date format you want to output.\nvar simpleDateFormat = new Packages.java.text.SimpleDateFormat(\"EEEE,MMMM dd,yyyy HH:mm:ss z Z\");\n// Time zone string for current user (\"Japan\")\nvar strTz = gs.getSession().getTimeZoneName();\n// Java TimeZone Object\nvar javaobjTz = Packages.java.util.TimeZone.getTimeZone(strTz);\n// Set TimeZone\nsimpleDateFormat.setTimeZone(javaobjTz);\n// Convert and date output\nvar strDate = '' + simpleDateFormat.format(javaobjDate);\n// Output \"Saturday,October 23,2021 16:29:08 JST +0900\"\ngs.info(strDate);\n\n/*\n// Similar Functions: GlideDate.getByFormat()\n// Current time \"2021-10-23 17:03:56\" JST +0900\nvar gd = new GlideDate();\n// Time zone string for current user (\"Japan\")\nvar strTz = gs.getSession().getTimeZoneName();\nvar javaobjTz = Packages.java.util.TimeZone.getTimeZone(strTz);\ngd.setTZ(javaobjTz);\ngs.info( gd.getByFormat(\"EEEE,MMMM dd,yyyy HH:mm:ss z Z\") );\n// \"Saturday,October 23,2021 08:03:56 UTC +0000\"\n// Output in UTC, not in the time zone set by the user.\n*/\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Start, End, and Duration Updates/README.md",
    "content": "# Start, End, and Duration Updates\n### Use this code to auto update associated GlideDateTime and Duration fields on a record.\n\nThis code assumes you're working on the sc_req_item and sc_task tables but can be modified to support other tables such as change.\n1. Requires only a 2 of the 3 data points to work.  In this example, the start and duration variables are the required input.\n2. Code checks for either a blank end time or if start or duration has changed. \n3. If start or duration has changed, it will calculate a new effective end date and duration accordingly.\n4. If the end date is the one changing, it will calculate a new effective duration.\n5. If all 3 data points change at the same time, only the start and duration fields will be accepted as input.  \n6. It also includes a section to update an associated task if needed.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Start, End, and Duration Updates/start_end_duration_updates.js",
    "content": "// condition\ncurrent.variables.access_end == '' || current.variables.access_start.changes() || current.variables.how_long.changes() || current.variables.access_end.changes()\n\n//function\n(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n    // calculates the end time of an task based on the start time and duration\n    if (current.variables.access_end == '' || current.variables.access_start.changes() || current.variables.how_long.changes()) {\n        if (current.variables.access_start.changes()) {\n            current.work_notes = 'Start Time Modified' + '\\n\\nNew value: ' + current.variables.access_start.getDisplayValue() + '\\nPrevious value: ' + previous.variables.access_start.getDisplayValue();\n        }\n        if (current.variables.how_long.changes()) {\n            current.work_notes = 'Duration Modified' + '\\n\\nNew value: ' + current.variables.how_long.getDisplayValue() + '\\nPrevious value: ' + previous.variables.how_long.getDisplayValue();\n        }\n        var startTime = new GlideDateTime(current.variables.access_start.getValue()); // getting start time\n        var dur = new GlideDateTime(current.variables.how_long.getValue()); // getting duration and converting to GlideDateTime\n        dur = dur.getNumericValue() / 1000; // calculating the total duration in seconds\n\n        startTime.addSeconds(dur); // add the seconds to the start time to calculate end time\n\n        current.variables.access_end = startTime.getValue(); // set the end time\n\n        gs.addInfoMessage(\"End time automatically adjusted based on start time and duration\");\n\n    } else if (current.variables.access_end.changes()) {\n        current.work_notes = 'End Time Modified'+ '\\n\\nNew value: ' + current.variables.access_end.getDisplayValue() + '\\nPrevious value: ' + previous.variables.access_end.getDisplayValue();\n        var stgdt = new GlideDateTime(current.variables.access_start); // getting start time\n        var engdt = new GlideDateTime(current.variables.access_end); // getting end time\n        var durgd = new GlideDuration();\n        durgd = GlideDateTime.subtract(stgdt, engdt); // getting duration\n\n        current.variables.how_long = durgd; // set the duration\n\n        gs.addInfoMessage(\"Duration was automatically adjusted\");\n    }\n\n  // update an associated task\n    var sc = new GlideRecord('sc_task');\n    sc.addQuery('request_item', current.sys_id);\n    sc.addActiveQuery();\n    sc.query();\n    if (sc.next()) {\n        var desc = sc.getValue('description');\n        var short_desc = sc.getValue('short_description');\n        if (short_desc.indexOf(' – Updated') < 0) {\n            sc.work_notes = 'Original Task: \\n' + short_desc + '\\n\\nOriginal Description: \\n' + desc;\n        }\n        sc.setValue('due_date', current.variables.access_start);\n        sc.setValue('short_description', 'Open access for ' + current.requested_for.name + ' at ' + current.variables.access_start.getDisplayValue() + ' – Updated');\n        sc.setValue('description', 'Please open access for ' + current.requested_for.name + ' at ' + current.variables.access_start.getDisplayValue() + '. – Updated \\n\\n See ' + current.number + ' work notes for previous task info.');\n        sc.update();\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Use timezone in Scoped App/README.md",
    "content": "The normal APIs for using Timezones modifications doesn't work in scoped app. For this you can use a undocumented API called \"GlideScheduleDateTime\". Meaning you can set the time to be e.g. 23 June 2023 15.00.00. And then you want that time to be in IST time, then you use this api to make set this and then you can save it in a normal glideDatetime and get the correct time saved in the field.\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideDateTime/Use timezone in Scoped App/script.js",
    "content": "//Code show just a simple example on how to use it.\n//here I have a timezone field on the record and a date field, then I want to set the start time to be 8AM on with that specific timezone on another time.\n//Below code could have been done in a Before Business Rule\n\nvar appOpenDateTime = new GlideDateTime(current.application_open_date);//It's a date field\n\nappOpenDateTime.addSeconds(28880);//28880 seconds is 8 hours, to get it to 8AM\nvar gsdt = new GlideScheduleDateTime(appOpenDateTime);\nvar timeZone = current.getValue('timezone');\ngsdt.convertTimeZone(timeZone, \"UTC\"); //Convert it to UTC which is the value saved in the instance.\n\n// create the date&time value and set it\nvar appStart = new GlideDateTime(gsdt);\ncurrent.setValue('appStart',appStart);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Display available choices/DisplayAvailableChoices.js",
    "content": "var grInc = new GlideRecord('incident');\ngrInc.get('number', 'INC0006924');\nvar acceptedValues = j2js(grInc.contact_type.getChoices());\ngs.debug('The accepted values for field contact_type are: ' + acceptedValues.join(', '));"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Display available choices/README.md",
    "content": "A code snippet to show how to obtain the list of accepted values for a given choice field.\nIt also uses `j2js()` to convert the output array from Java to Javascript."
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Display base table for each field/README.md",
    "content": "# Display Base Table for Each Field\n\nThis code snippet demonstrates how to identify the base table where each field originates from in ServiceNow's table inheritance hierarchy.\n\n## Functionality\n\nThe script uses `GlideRecordUtil` to retrieve all parent tables for a given table, then iterates through each field in a record to display which base table the field was defined in using the `getBaseTableName()` method from the GlideElement API.\n\n## Use Case\n\nThis is particularly useful when working with extended tables to understand:\n- Which fields are inherited from parent tables\n- Which fields are defined locally on the current table\n- The complete table inheritance structure\n\n## Example Output\n\nFor a table like `db_image`, the output shows each field name alongside its originating table:\n```\nFields          Base table\nsys_id          sys_metadata\nsys_created_on  sys_metadata\nimage           db_image\nname            db_image\n...\n```\n\n## Related Methods\n\n- `getBaseTableName()` - Returns the name of the table where the field was originally defined\n- `GlideRecordUtil.getTables()` - Returns parent tables in the inheritance hierarchy\n- `GlideRecordUtil.getFields()` - Returns an array of all field names for a GlideRecord\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Display base table for each field/displayBaseTablesForEachField.js",
    "content": "var tableName = 'db_image';\n\nvar gru = new GlideRecordUtil();\n\nvar parentTables = gru.getTables(tableName);\ngs.print(\"Parent tables: \" + parentTables);\n\nvar gr = new GlideRecord(tableName);\ngr.setlimit(1);\ngr.query();\n\nif(gr.next()){ \n\tvar fieldNames = gru.getFields(gr);\n\tgs.print('Fields\\t\\tBase table');\n    for (var i = 0; i < fieldNames.length; i++) {\n\t\tgs.print(fieldNames[i] + \"\\t\\t\" + gr[fieldNames[i]].getBaseTableName()); \n    }\n}\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Smart Field Validation and Dependent Field Derivation Using getError() and setError()/README.md",
    "content": "# Smart Field Validation and Dependent Field Derivation Using GlideElement.getError()\n\nThis project demonstrates how to use `GlideElement.setError()` and `GlideElement.getError()` \nto perform validation in one Business Rule and field derivation in another, without repeating logic.\n\n## 📘 Overview\n\nThis snippet demonstrates how to share validation state and error messages between multiple Business Rules using `GlideElement.setError()` and `GlideElement.getError()` in ServiceNow.\n\nBy propagating validation context across Business Rules, developers can:\n\n- Avoid repeated validation logic.  \n- Trigger dependent field updates only when a field passes validation.  \n- Maintain consistent and clean data flow between sequential rules.  \n\nThis technique is especially useful when different validation or derivation rules are split by purpose or owned by different teams.\n\n---\n\n## 🧠 Concept\n\nWhen one Business Rule sets an error on a field using `setError()`, the error message persists in memory for that record during the same transaction.  \nA later Business Rule (executing at a higher order) can then retrieve that message using `getError()` and make data-driven decisions.\n\n### Flow:\n1. BR #1 (`Validate Short Description`) checks text length.\n2. BR #2 (`Derive Dependent Fields`) runs only if no validation error exists.\n3. Category, Subcategory, and Impact are derived dynamically.\n\n## 🚀 Benefits\n\n- ✅ Reduces redundant validation checks\n- ✅ Improves rule execution efficiency\n- ✅ Keeps logic modular and maintainable\n- ✅ Provides better visibility and control in field validations\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Smart Field Validation and Dependent Field Derivation Using getError() and setError()/br_derive_dependent_fields.js",
    "content": "// Name: Derive Dependent Fields\n// Table: Incident\n// When: before insert or before update\n// Order: 200\n\n(function executeRule(current, previous /*null when async*/) {\n\n    // Only proceed if short_description changed or new record\n    if (!(current.operation() === 'insert' || current.short_description.changes())) {\n        return;\n    }\n\n    var errorMsg = current.short_description.getError();\n\n    if (errorMsg) {\n        gs.info('[BR:200 - Derive] Skipping field derivation due to prior error → ' + errorMsg);\n        return;\n    }\n\n    // Proceed only if no prior validation error\n    var desc = current.getValue('short_description').toLowerCase();\n\n    // Example 1: Derive category\n    if (desc.includes('server')) {\n        current.category = 'infrastructure';\n        current.subcategory = 'server issue';\n    } else if (desc.includes('email')) {\n        current.category = 'communication';\n        current.subcategory = 'email problem';\n    } else if (desc.includes('login')) {\n        current.category = 'access';\n        current.subcategory = 'authentication';\n    } else {\n        current.category = 'inquiry';\n        current.subcategory = 'general';\n    }\n\n    // Example 2: Derive impact\n    if (desc.includes('critical') || desc.includes('outage')) {\n        current.impact = 1; // High\n    } else {\n        current.impact = 3; // Low\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/Smart Field Validation and Dependent Field Derivation Using getError() and setError()/br_validate_short_description.js",
    "content": "// Name: Validate Short Description\n// Table: Incident\n// When: before insert or before update\n// Order: 100\n\n(function executeRule(current, previous /*null when async*/) {\n    var short_desc = current.getValue('short_description');\n\n    // Validate only for new records or when field changes\n    if (current.operation() === 'insert' || current.short_description.changes()) {\n        if (!short_desc || short_desc.trim().length < 40) {\n            current.short_description.setError('Short description must be at least 40 characters long.');\n            current.setAbortAction(true);\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/getDependent/README.md",
    "content": "GlideElement API provides different functions to work with the fields, attribute, values, etc.,\ngetDependent function on GlideElement API - helpful in validating the dependent field values if any for the given field. \n\nUsed subcategory field in incident table as an example so that it will print \"category\" as result.\nIf we update the field_name variable to some other field that is not dependent on another field then the method prints null as result.\n\nLink to documentation - https://developer.servicenow.com/dev.do#!/reference/api/paris/server_legacy/c_GlideElementAPI#r_GlideElement-getDependent\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideElement/getDependent/glideelement.js",
    "content": "var table_name /* String */ = \"incident\";\nvar field_name /* String */ = \"subcategory\";\n\nfieldHasDependent(table_name, field_name);\n\n/*\nMethod fieldHasDependent return the field name if available else retun null\nInput arguement :- \ntable_name - valid table name as a String \nfield_name - valid field from the table as a string\n\nResult:- \nnull - When no dependend field.\nfield_value - when dependend\n*/\n\nfunction fieldHasDependent(table_name, field_name){\n\tvar tableGr = new GlideRecord(table_name);\n\t\n\t//Get the GlideElement object for the given field\n\tvar glideElement = tableGr.getElement(field_name);\t\n\tgs.print(glideElement.getDependent());\t\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideFilter/checkRecord/README.md",
    "content": "Ever wondered how business rule works in the background. This example depicts the common scenario where we want to analyse certain records against a certain condition. This is one of the OOB class provided by servicenow.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideFilter/checkRecord/example.js",
    "content": "var cond='short_description=certificate: Fingerprint: - Certificate Expired';\nvar emalert = new GlideRecord('em_alert');\nemalert.query();\n\nvar bool = true;\n \nwhile(emalert.next())\n{\n   bool = GlideFilter.checkRecord(emalert, cond);\n   gs.info(\"Source \"+ emalert.source + \" is \" + bool);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideHTTPRequest/Retrieve table records via GlideHTTPRequest/README.md",
    "content": "GlideHTTPRequest API is used to work on Glide HTTP Request and Response.\n\nAPI Documentation Link for \"PARIS\" release : https://developer.servicenow.com/dev.do#!/reference/api/paris/server_legacy/GlideHTTPRequestAPI\n\nPlease update the variable details in the script to check it.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideHTTPRequest/Retrieve table records via GlideHTTPRequest/glidehttprequest.js",
    "content": "/*\nGlideHTTPRequest API is used to get the records from given table. \n\nNote: It prints the displayvalue of the records. as the query parameter \"sysparm_display_value\" is set to \"true\". \n*/\n\n/********************   Please update below details *******************************/\n\nvar instance_name  /* String  */  = \"myinstance.service-now.com\"; \nvar user_name /* String  */  = \"admin\"; \nvar password  /* String  */  = \"aAhZ0xaWkWO9\"; \nvar timeout   /* Integer */  = 15000; \nvar table_name /* String */ = \"incident\";\nvar record_count /* Integer */ = 1;  // Numer of records to be retuned in the response\nvar table_fields /* String */ = \"number,state\"; //Provide comma separated field values\n\n/************************************* END ****************************************/\n\ngetRecordsFromTable(user_name, password, instance_name, timeout, table_name, record_count, table_fields);\n\nfunction getRecordsFromTable(user_name, password, instance_name, timeout, table_name, record_count, table_fields ){\n\n\ttry{\n\t\t//construct TableAPI endpoint\n\t\tvar endpoint = \"https://\"+instance_name+\"/api/now/table/\"+table_name;\n\t\t\n\t\t//Get HTTPRequest object for the endpoint\n\t\tvar http_request = new GlideHTTPRequest(endpoint); \n\t\t\n\t\t//Set Basic Authentication credentails.\n\t\thttp_request.setBasicAuth(user_name, password);\n\n\t\t//Setting timeout duration in milliseconds \n\t\thttp_request.setHttpTimeout(timeout);\n\n\t\t//Set the Query Parameters\n\t\thttp_request.addParameter(\"sysparm_limit\",record_count);\n\t\thttp_request.addParameter(\"sysparm_fields\",table_fields);\n\t\thttp_request.addParameter(\"sysparm_display_value\",\"true\");\n\n\t\t//Execute the get() method on the GlideHTTPRequest object to retirve the HTTPResponse.\n\t\tvar http_response = http_request.get();\n\n\t\tif(http_response.getErrorMessage() != null){\n\t\t\t//Show ERROR in case of request failed to process.\n\t\t\tgs.print(\"ERROR : \"+http_response.getErrorMessage());\n\t\t}else{\n\t\t\t//Display the response message\n\t\t\tvar response_body = http_response.getBody();\n\t\t\tgs.print(\"Response Body is :\\n\"+response_body);\n\t\t}\n\t}catch(ex){\n\t\t// Prints any unhandled exception\n\t\tgs.print(\"Exception : \"+ex);\n\t}\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideJsonPath/Basic-Example/README.md",
    "content": "# Querying JSON with JSONPath to extract values\n\nGlideJsonPath is a class which can be used to use JSONPath in ServiceNow. It can be useful when working with JSON Payloads, especially highly nested or in general complex structures.\n\nReferences:\n- [RFC 9535: JSONPath: Query Expressions for JSON](https://datatracker.ietf.org/doc/rfc9535/)\n- [Play with JSONPath outside of ServiceNow](https://jsonpath.com/)\n- [Good Examples to start with](https://restfulapi.net/json-jsonpath/)\n- [ServiceNow API Documentation](https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideJsonPath/concept/GlideJsonPathAPI.html)"
  },
  {
    "path": "Core ServiceNow APIs/GlideJsonPath/Basic-Example/examples.js",
    "content": "// Run in background script\nvar json = { \n\t\"store\": \n\t{\n\t\t\"book\": [\n\t\t\t{ \n\t\t\t\t\"category\": \"reference\",\n\t\t\t\t\"author\": \"Nigel Rees\",\n\t\t\t\t\"title\": \"Sayings of the Century\",\n\t\t\t\t\"price\": 8.95\n\t\t\t},\n\t\t\t{ \n\t\t\t\t\"category\": \"fiction\",\n\t\t\t\t\"author\": \"Evelyn Waugh\",\n\t\t\t\t\"title\": \"Sword of Honour\",\n\t\t\t\t\"price\": 12.99\n\t\t\t},\n\t\t\t{ \n\t\t\t\t\"category\": \"fiction\",\n\t\t\t\t\"author\": \"Herman Melville\",\n\t\t\t\t\"title\": \"Moby Dick\",\n\t\t\t\t\"isbn\": \"0-553-21311-3\",\n\t\t\t\t\"price\": 8.99\n\t\t\t},\n\t\t\t{ \n\t\t\t\t\"category\": \"fiction\",\n\t\t\t\t\"author\": \"J. R. R. Tolkien\",\n\t\t\t\t\"title\": \"The Lord of the Rings\",\n\t\t\t\t\"isbn\": \"0-395-19395-8\",\n\t\t\t\t\"price\": 22.99\n\t\t\t}\n\t\t],\n\t\t\"bicycle\": {\n\t\t\t\"color\": \"red\",\n\t\t\t\"price\": 19.95\n\t\t}\n\t}\n};\n\nvar path1 = 'store.book[0].author'; // The author of the first book\nvar path2 = 'store.book[*].author'; // All authors\nvar path3 = 'store..price'; // All prices\nvar path4 = '$..book[?(@.price<10)]'; // All books cheaper than 10\nvar path5 = '$..book[?(@.isbn)]'; // All books with an ISBN number\nvar path6 = '$..*'; // All members of JSON structure\n\nvar gjp = new GlideJsonPath(JSON.stringify(json));\ngs.info('Path: ' + path1 + ' Result: ' + gjp.read(path1));\ngs.info('Path: ' + path2 + ' Result: ' + gjp.read(path2));\ngs.info('Path: ' + path3 + ' Result: ' + gjp.read(path3));\ngs.info('Path: ' + path4 + ' Result: ' + gjp.read(path4));\ngs.info('Path: ' + path5 + ' Result: ' + gjp.read(path5));\ngs.info('Path: ' + path6 + ' Result: ' + gjp.read(path6));"
  },
  {
    "path": "Core ServiceNow APIs/GlideJsonPath/Create Critical P1 Incident from Alert using GlideJsonPath/README.md",
    "content": "Create Critical P1 Incident from Alert This script provides the server-side logic for a Scripted REST API endpoint in ServiceNow. \nIt allows external monitoring tools to send alert data via a POST request, which is then used to automatically create a high-priority, P1 incident. \nOverview The API endpoint performs the following actions: Receives a JSON Payload: Accepts a POST request containing a JSON payload with alert details (severity, description, source, CI). Parses Data: Uses the GlideJsonPath API to efficiently extract the necessary alert information from the JSON body. Validates Request: Ensures that the severity is CRITICAL and the description is present. It sends an appropriate error response for invalid or incomplete data. Creates Incident: If the data is valid, it creates a new incident record in the incident table. Sets Incident Fields: Automatically populates the incident's short_description, description, source, and sets the impact, urgency, and priority to 1 - High/Critical. Associates CI: If a ci_sys_id is provided in the payload, it links the incident to the correct Configuration Item. Logs Activity: Logs the successful creation of the incident in the system log for tracking and auditing purposes. Responds to Sender: Sends a JSON response back to the external system, confirming success or failure and providing the new incident's number and sys_id. Expected JSON payload The external system should send a POST request with a JSON body structured like this: json { \"alert\": { \"severity\": \"CRITICAL\", \"description\": \"The primary database server is down. Users are unable to log in.\", \"source\": \"Dynatrace\", \"configuration_item\": \"DB_Server_01\", \"ci_sys_id\": \"a1b2c3d4e5f6g7h8i9j0k1l2m3n4o5p6\" } } Use code with caution.\n\nInstallation As a Scripted REST API Resource Create the Scripted REST API: Navigate to System Web Services > Scripted REST APIs. \nClick New and fill in the details: Name: CriticalAlertIncident API ID: critical_alert_incident Save the record.\n\nCreate the Resource: On the Resources related list of the API record, click New. \nName: PostCriticalIncident HTTP Method: POST Relative Path: / Copy and paste the provided script into the Script field. Configure Security: Ensure appropriate authentication is configured for the API, such as OAuth or Basic Auth, to secure the endpoint. Customization Change Priority/Impact: Modify the grIncident.setValue() lines to set different priority or impact levels based on the payload (e.g., if (severity == 'MAJOR') { grIncident.setValue('priority', 2); }). Add Additional Fields: Extend the script to parse and set other incident fields, such as assignment_group, caller_id, or category, based on data from the incoming payload. Enrich Incident Data: Perform a lookup on the CI to fetch additional information and add it to the incident description or other fields. Handle Different Severity Levels: Add if/else logic to handle different severity values (e.g., MAJOR, MINOR) from the source system, creating incidents with different priorities accordingly.\n\nDependencies This script requires the GlideJsonPath API, which is available in Jakarta and later releases. \nThe API endpoint must be secured with appropriate authentication to prevent unauthorized access.\n\nConsiderations\n\nSecurity: This API endpoint is a powerful integration point. \nEnsure that it is properly secured and that only trusted sources are allowed to create incidents. Error Handling: The script includes robust error handling for common failures (missing data, insertion failure) but should be extended to handle specific use cases as needed. Testing: Thoroughly test the endpoint with a variety of payloads, including valid data, missing data, and invalid data, to ensure it behaves as expected.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideJsonPath/Create Critical P1 Incident from Alert using GlideJsonPath/script.js",
    "content": "try {\n        // Get the JSON payload from the request body.\n        var requestBody = request.body.dataString;\n\n        // Use GlideJsonPath to parse the JSON payload efficiently.\n        var gjp = new GlideJsonPath(requestBody);\n\n        // Extract key information from the JSON payload.\n        var severity = gjp.read(\"$.alert.severity\");\n        var shortDescription = gjp.read(\"$.alert.description\");\n        var source = gjp.read(\"$.alert.source\");\n        var ciName = gjp.read(\"$.alert.configuration_item\");\n        var ciSysId = gjp.read(\"$.alert.ci_sys_id\");\n\n        // Validate that mandatory fields are present.\n        if (!shortDescription || severity != 'CRITICAL') {\n            response.setStatus(400); // Bad Request\n            response.setBody({\n                \"status\": \"error\",\n                \"message\": \"Missing mandatory alert information or severity is not critical.\"\n            });\n            return;\n        }\n        \n        // Use GlideRecordSecure for added security and ACL enforcement.\n        var grIncident = new GlideRecordSecure('incident');\n        grIncident.initialize();\n        \n        // Set incident field values from the JSON payload.\n        grIncident.setValue('short_description', 'INTEGRATION ALERT: [' + source + '] ' + shortDescription);\n        grIncident.setValue('description', 'A critical alert has been received from ' + source + '.\\n\\nAlert Details:\\nSeverity: ' + severity + '\\nDescription: ' + shortDescription + '\\nCI Name: ' + ciName);\n        grIncident.setValue('source', source);\n        grIncident.setValue('impact', 1); // Set Impact to '1 - High'\n        grIncident.setValue('urgency', 1); // Set Urgency to '1 - High'\n        grIncident.setValue('priority', 1); // Set Priority to '1 - Critical'\n\n        // If a CI sys_id is provided, set the Configuration Item.\n        if (ciSysId) {\n            grIncident.setValue('cmdb_ci', ciSysId);\n        }\n        \n        // Insert the new incident record and store its sys_id.\n        var newIncidentSysId = grIncident.insert();\n        \n        if (newIncidentSysId) {\n            // Get the incident number for the successful response.\n            var incNumber = grIncident.getRecord().getValue('number');\n            \n            // Log the successful incident creation.\n            gs.info('Critical P1 incident ' + incNumber + ' created from alert from ' + source);\n            \n            // Prepare the success response.\n            var responseBody = {\n                \"status\": \"success\",\n                \"message\": \"Critical incident created successfully.\",\n                \"incident_number\": incNumber,\n                \"incident_sys_id\": newIncidentSysId\n            };\n            response.setStatus(201); // Created\n            response.setBody(responseBody);\n        } else {\n            // Handle database insertion failure.\n            response.setStatus(500); // Internal Server Error\n            response.setBody({\n                \"status\": \"error\",\n                \"message\": \"Failed to create the incident record.\"\n            });\n        }\n        \n    } catch (ex) {\n        // Handle any exceptions during processing.\n        gs.error('An error occurred during critical alert incident creation: ' + ex);\n        response.setStatus(500);\n        response.setBody({\n            \"status\": \"error\",\n            \"message\": \"An internal server error occurred.\"\n        });\n    }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Add HTML Input Field in GlideModal Window/README.md",
    "content": "# Add HTML Input Field in GlideModal Window - ServiceNow\n\n## Use Case\nThis snippet demonstrates how to include HTML input fields, including rich text editors, inside a GlideModal window in ServiceNow.\n\n## Real-Life Example of Use  \nIn ServiceNow ITSM, support agents often need to add detailed notes or updates quickly without losing their workflow context. For instance, when investigating complex incidents, agents can click the \"Add Details\" button to open a modal with rich text input to document findings, attach formatted comments, or paste troubleshooting steps. This modal dialog prevents navigation away from the incident form, speeding up data entry and improving information capture quality.\n\n## Why This Use Case is Unique and Valuable (Simple)\n- Lets users enter rich text and HTML inputs right inside a popup window (GlideModal) without leaving the current page.\n- Makes data entry faster and easier by avoiding navigation away from the form.\n- Supports complex inputs like formatted text using editors such as TinyMCE.\n- Helps improve quality and detail of notes and comments on records.\n- Can be reused for different input forms or workflows in ServiceNow.\n- Works smoothly within the ServiceNow platform UI for a consistent user experience.\n\n## Steps to Implement\n1. Create a UI Page named `rich_text_modal` with appropriate input fields (string and rich text).  \n2. Create a UI Action (e.g., \"Add Details\") on the Incident table that opens the `rich_text_modal` UI Page within a GlideModal.  \n3. Open an incident record and click the \"Add Details\" button to see the modal with the HTML input fields.\n\n## Compatibility\nThis UI Page and UI Action is compatible with all standard ServiceNow instances without requiring ES2021 features.\n\n## Files\n`UI Page` , `UI Action` - are the files implementing the logic.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Add HTML Input Field in GlideModal Window/UI Action",
    "content": "function test()\n{\nvar dialog = new GlideModal('rich_text_modal'); \ndialog.setTitle('Rich Text Editor'); \ndialog.setSize('500','500');\ndialog.render();\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Add HTML Input Field in GlideModal Window/UI page",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n\n<!-- Short Description (String) -->\n<label for=\"short_description\">Short Description</label><br/>\n<input type=\"text\" id=\"short_description\" name=\"short_description\" style=\"width: 100%;\" maxlength=\"100\" /><br/><br/>\n \n<!-- Full Description (Rich Text/HTML) -->\n<label for=\"full_description\">Full Description</label><br/>\n<script src=\"/scripts/tinymce4_4_3/tinymce.full.jsx\"></script>\n\n<script>\ntinymce.init({\nmode : \"specific_textareas\",\neditor_selector : \"mce\",\nwidth: '100%',\nheight: '500px',\nmenubar: \"tools\",\ntoolbar: 'undo redo | bold italic | alignleft aligncenter alignright | bullist numlist',\ncontent_css : \"* {font: 10px arial, sans-serif;} b {font-weight:bold;} th {font-weight:bold;} strong {font-weight:bold;}\",\n});\n\n</script>\n<textarea cols=\"80\" rows=\"10\" id=\"myArea\" name=\"myArea\" class=\"mce\"></textarea>\n<br/>\n<br/>\n<div style=\"text-align: left;\">\n<button type=\"button\" id=\"submitBtn\" onclick=\"submitForm()\">Submit</button>\n</div>\n<script>\n// Example submit handler function:\nfunction submitForm() {\nalert(\"form submitted\");\n}\n</script>\n</j:jelly>\n \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Confirm Message/README.md",
    "content": "This script will be helpful to get a confirm box with the help of GlideModal, this can be leveraged in your client side scripts or UI actions. Confirm and alerts, which we get by \nusing standard Javascript window methods can be easily blocked by user's browser, but GlideModal confirm will still display and this enhances the User experience.\n\nWith the help of this script we can get a confirmation pop-up with \"Don't save\", \"Cancel\", and \"Save\" buttons.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Confirm Message/glide_confirm.js",
    "content": "var gm = new GlideModal(\"glide_confirm\", false, 600); // glide_confirm is the OOB prebuilt UI page.\ngm.setTitle(\"Conform box title\"); // The title for your confirm message box\ngm.setPreference(\"title\", \"confirm box body\"); // this will be the body for your confirm message\ngm.setPreference(\"warning\", \"false\"); // put it true if you want to display a warning sign on your confirm box\ngm.setPreference(\"onPromptSave\", function() {alert(\"You clicked on 'Save'\")});\ngm.setPreference(\"onPromptDiscard\", function() {alert(\"You clicked on 'Don't save'\")});\ngm.setPreference(\"onPromptCancel\", function() {alert(\"You clicked on 'Cancel'\")});\ngm.render();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Information Message/README.md",
    "content": "These scripts will be helpful to get an Information message with the help of GlideModal, this can be leveraged in your client side scripts or UI actions.\nThere are two types of info messages which we can be leveraged through GlideModal.\n1. It will display a simple pop-up with an info icon and an \"OK\" button that stretches with width of the modal window.\n2. It will displat a simple pop-up without an icon.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Information Message/glide_info.js",
    "content": "var gm = new GlideModal(\"glide_info\", true, 600); // glide_info is the OOB ui page.\ngm.setTitle(\"Info box\"); //title of your info box\ngm.setPreference(\"title\", \"Welcome to ServicNow using GlideModal info box\"); //Message shown in the info box\ngm.setPreference(\"onPromptComplete\", function() {alert(\"You clicked on 'Ok'\")});\ngm.render();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideModal/Information Message/glide_warn.js",
    "content": "var gm = new GlideModal(\"glide_warn\", true, 600); //glide_warn is the OOB UI Page\ngm.setTitle(\"Info message without info icon\"); //title of the box\ngm.setPreference(\"title\", \"Info message text\"); //message you want to display\ngm.setPreference(\"onPromptComplete\", function() {alert(\"You clicked on 'Ok'\")});\ngm.render();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Basic Wrappers/README.md",
    "content": "# GlideQuery\n\nGlideQuery is a wrapper for the GlideRecord and GlideAggregate APIs. The wrapper offers several advantages, including a single point of entry, fail-fast with friendly error messaging, native JavaScript syntax, and more. Here are some functions that implement the basic GlideQuery capabilities to help you get started.\n\n[Product Documentation](https://docs.servicenow.com/bundle/tokyo-application-development/page/app-store/dev_portal/API_reference/GlideQuery/concept/GlideQueryGlobalAPI.html)\n\n[ServiceNow Community Blog Entries](https://developer.servicenow.com/blog.do?p=/tags/glidequery/) \n[Streyda Article](https://www.streyda.eu/post/gliderecordorglidequery)\n\n\n## Get Records\n```javascript\nfunction getRecords( tableName, query, fields ){\n\t/* Given a table name, encoded query, and array of field names,\n\t * Gather the request field attributes of the queried records,\n\t * Build an array of JSON objects and return the array.\n\t */\n\t \t\n\treturn new global.GlideQuery.parse( tableName, query )\n\t.select( fields )\n\t.reduce( function( arr, rec ){\n\t\tarr.push( rec );\n\t\treturn arr;\n\t}, [] );\n\t\n}\n```\n\n## Insert Records\n```javascript\nfunction insertRecords( strTableName, arrValues, boolDisableWorkflow ){\n\t/* Given a table name, array of objects, where\n\t * each object contains the data for a new record,\n\t * Insert the new objects into the table as new records.\n\t * Optionally, disable workflow\n\t */\n\t \t\n\tif( boolDisableWorkflow === undefined || boolDisableWorkflow == 'false' ){\n\t\tboolDisableWorkflow = false;\n\t}\n\n\ttry{\n\t\tvar gqRecords = new global.GlideQuery( strTableName )\n\t\t.disableWorkflow( boolDisableWorkflow )\n\n\t\tarrValues.forEach( function( record ){\n\t\t\tgqRecords.insert( record );\n\t\t} );\n\t\t\n\t}\n\tcatch( e ){\n\t\n\t\tgs.error( \n\t\t\t'An error occurred trying to insert records into ' + strTableName + \n\t\t\t'\\nValues: \\n' + JSON.stringify( arrValues, null, 2 ) + \n\t\t\t'\\n\\nError: ' + e.name + ': ' + e.message \n\t\t);\n\t\t\n\t}\n\t\n}\n\n```\n\n## Update Records\n```javascript\nfunction updateRecords( strTableName, strEncodedQuery, objValues, boolDisableWorkflow ){\n\t/* Given a table name, an encoded query, and an object of record values,\n\t * Update all of the filtered records with the new values.\n\t * Optionally, disable workflow\n\t */\n\t \t\n\tif( boolDisableWorkflow === undefined || boolDisableWorkflow == 'false' ){\n\t\tboolDisableWorkflow = false;\n\t}\n\n\ttry{\n\n\t\treturn new global.GlideQuery.parse( strTableName, strEncodedQuery )\n\t\t.disableWorkflow( boolDisableWorkflow )\n\t\t.updateMultiple( objValues );\n\t\n\t}\n\tcatch( e ){\n\t\n\t\tgs.error( \n\t\t\t'An error occurred trying to update records in ' + strTableName + \n\t\t\t'\\nQuery: ' + strEncodedQuery + \n\t\t\t'\\nValues: \\n' + JSON.stringify( objValues, null, 2 ) + \n\t\t\t'\\n\\nError: ' + e.name + ': ' + e.message \n\t\t);\n\t\t\n\t}\n\t\n}\n\n```\n\n## Delete Records\n```javascript\nfunction deleteRecords( strTableName, strEncodedQuery, boolDisableWorkflow ){\n\t/* Given a table name and an encoded query,\n\t * Delete all of the filtered records.\n\t * Optionally, disable workflow\n\t */\n\t \t\n\tif( boolDisableWorkflow === undefined || boolDisableWorkflow == 'false' ){\n\t\tboolDisableWorkflow = false;\n\t}\n\n\ttry{\n\n\t\treturn new global.GlideQuery.parse( strTableName, strEncodedQuery )\n\t\t.disableWorkflow( boolDisableWorkflow )\n\t\t.deleteMultiple();\n\t\n\t}\n\tcatch( e ){\n\t\n\t\tgs.error( \n\t\t\t'An error occurred trying to delete records from ' + strTableName + \n\t\t\t'\\nQuery: ' + strEncodedQuery + \n\t\t\t'\\n\\nError: ' + e.name + ': ' + e.message \n\t\t);\n\t\t\n\t}\n\t\n}\n\n```"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Basic Wrappers/delete_records.js",
    "content": "function deleteRecords( strTableName, strEncodedQuery, boolDisableWorkflow ){\n\t/* Given a table name and an encoded query,\n\t * Delete all of the filtered records.\n\t * Optionally, disable workflow\n\t */\n\t \t\n\tif( boolDisableWorkflow === undefined || boolDisableWorkflow == 'false' ){\n\t\tboolDisableWorkflow = false;\n\t}\n\n\ttry{\n\n\t\treturn new global.GlideQuery.parse( strTableName, strEncodedQuery )\n\t\t.disableWorkflow( boolDisableWorkflow )\n\t\t.deleteMultiple();\n\t\n\t}\n\tcatch( e ){\n\t\n\t\tgs.error( \n\t\t\t'An error occurred trying to delete records from ' + strTableName + \n\t\t\t'\\nQuery: ' + strEncodedQuery + \n\t\t\t'\\n\\nError: ' + e.name + ': ' + e.message \n\t\t);\n\t\t\n\t}\n\t\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Basic Wrappers/get_records.js",
    "content": "function getRecords( tableName, query, fields ){\n\t/* Given a table name, encoded query, and array of field names,\n\t * Gather the request field attributes of the queried records,\n\t * Build an array of JSON objects and return the array.\n\t */\n\t \t\n\treturn new global.GlideQuery.parse( tableName, query )\n\t.select( fields )\n\t.reduce( function( arr, rec ){\n\t\tarr.push( rec );\n\t\treturn arr;\n\t}, [] );\n\t\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Basic Wrappers/insert_records.js",
    "content": "function insertRecords( strTableName, arrValues, boolDisableWorkflow ){\n\t/* Given a table name, array of objects, where\n\t * each object contains the data for a new record,\n\t * Insert the new objects into the table as new records.\n\t * Optionally, disable workflow\n\t */\n\t \t\n\tif( boolDisableWorkflow === undefined || boolDisableWorkflow == 'false' ){\n\t\tboolDisableWorkflow = false;\n\t}\n\n\ttry{\n\t\tvar gqRecords = new global.GlideQuery( strTableName )\n\t\t.disableWorkflow( boolDisableWorkflow )\n\n\t\tarrValues.forEach( function( record ){\n\t\t\tgqRecords.insert( record );\n\t\t} );\n\t\t\n\t}\n\tcatch( e ){\n\t\n\t\tgs.error( \n\t\t\t'An error occurred trying to insert records into ' + strTableName + \n\t\t\t'\\nValues: \\n' + JSON.stringify( arrValues, null, 2 ) + \n\t\t\t'\\n\\nError: ' + e.name + ': ' + e.message \n\t\t);\n\t\t\n\t}\n\t\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Basic Wrappers/update_records.js",
    "content": "function updateRecords( strTableName, strEncodedQuery, objValues, boolDisableWorkflow ){\n\t/* Given a table name, an encoded query, and an object of record values,\n\t * Update all of the filtered records with the new values.\n\t * Optionally, disable workflow\n\t */\n\t \t\n\tif( boolDisableWorkflow === undefined || boolDisableWorkflow == 'false' ){\n\t\tboolDisableWorkflow = false;\n\t}\n\n\ttry{\n\n\t\treturn new global.GlideQuery.parse( strTableName, strEncodedQuery )\n\t\t.disableWorkflow( boolDisableWorkflow )\n\t\t.updateMultiple( objValues );\n\t\n\t}\n\tcatch( e ){\n\t\n\t\tgs.error( \n\t\t\t'An error occurred trying to update records in ' + strTableName + \n\t\t\t'\\nQuery: ' + strEncodedQuery + \n\t\t\t'\\nValues: \\n' + JSON.stringify( objValues, null, 2 ) + \n\t\t\t'\\n\\nError: ' + e.name + ': ' + e.message \n\t\t);\n\t\t\n\t}\n\t\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Conditional Field Selection/README.md",
    "content": "# Conditional Field Selection with GlideQuery\r\n\r\nThis snippet demonstrates how to dynamically select different sets of fields based on conditions using GlideQuery. This pattern is useful when you need to optimize queries by selecting only the fields you actually need based on runtime conditions, or when building flexible APIs that return different data sets based on user permissions or preferences.\r\n\r\n## Use Cases\r\n\r\n- **Permission-based field selection**: Select different fields based on user roles or permissions\r\n- **Performance optimization**: Only fetch expensive fields when needed\r\n- **API flexibility**: Return different data sets based on request parameters\r\n- **Conditional aggregations**: Include summary fields only when specific conditions are met\r\n\r\n## Key Benefits\r\n\r\n- **Reduced data transfer**: Only fetch the fields you need\r\n- **Performance optimization**: Avoid expensive field calculations when unnecessary\r\n- **Security**: Dynamically exclude sensitive fields based on permissions\r\n- **Maintainable code**: Centralized logic for field selection patterns\r\n\r\n## Examples Included\r\n\r\n1. **Role-based field selection**: Different fields for different user roles\r\n2. **Performance-optimized queries**: Conditional inclusion of expensive fields\r\n3. **Dynamic field arrays**: Building field lists programmatically\r\n4. **Chained conditional selection**: Multiple condition-based selections"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Conditional Field Selection/conditional_field_selection.js",
    "content": "// Conditional Field Selection with GlideQuery\r\n// Demonstrates dynamically selecting different fields based on runtime conditions\r\n\r\n/**\r\n * Example 1: Role-based Field Selection\r\n * Select different incident fields based on user's role\r\n */\r\nfunction getIncidentsByRole(userRole, assignedTo) {\r\n    // Define base fields that everyone can see\r\n    let baseFields = ['number', 'short_description', 'state', 'priority', 'sys_created_on'];\r\n\r\n    // Define additional fields based on role\r\n    let additionalFields = [];\r\n\r\n    if (userRole === 'admin' || userRole === 'security_admin') {\r\n        additionalFields = ['caller_id', 'assigned_to', 'assignment_group', 'work_notes', 'comments'];\r\n    } else if (userRole === 'itil') {\r\n        additionalFields = ['caller_id', 'assigned_to', 'assignment_group'];\r\n    } else if (userRole === 'agent') {\r\n        additionalFields = ['assigned_to', 'assignment_group'];\r\n    }\r\n\r\n    // Combine base and additional fields\r\n    let fieldsToSelect = baseFields.concat(additionalFields);\r\n\r\n    return new GlideQuery('incident')\r\n        .where('assigned_to', assignedTo)\r\n        .where('state', '!=', 7) // Not closed\r\n        .select(fieldsToSelect)\r\n        .orderByDesc('sys_created_on')\r\n        .toArray(50);\r\n}\r\n\r\n/**\r\n * Example 2: Performance-optimized Field Selection\r\n * Only include expensive fields when specifically requested\r\n */\r\nfunction getTasksWithOptionalFields(options) {\r\n    options = options || {};\r\n\r\n    // Always include these lightweight fields\r\n    let fields = ['sys_id', 'number', 'short_description', 'state'];\r\n\r\n    // Conditionally add more expensive fields\r\n    if (options.includeUserDetails) {\r\n        fields.push('caller_id.name', 'caller_id.email', 'assigned_to.name');\r\n    }\r\n\r\n    if (options.includeTimeTracking) {\r\n        fields.push('work_start', 'work_end', 'business_duration');\r\n    }\r\n\r\n    if (options.includeApprovalInfo) {\r\n        fields.push('approval', 'approval_history');\r\n    }\r\n\r\n    if (options.includeRelatedRecords) {\r\n        fields.push('parent.number', 'caused_by.number');\r\n    }\r\n\r\n    let query = new GlideQuery('task')\r\n        .where('active', true)\r\n        .select(fields);\r\n\r\n    if (options.assignmentGroup) {\r\n        query.where('assignment_group', options.assignmentGroup);\r\n    }\r\n\r\n    return query.toArray(100);\r\n}\r\n\r\n/**\r\n * Example 3: Dynamic Field Array Building\r\n * Build field selection based on table structure and permissions\r\n */\r\nfunction getDynamicFieldSelection(tableName, userPermissions, includeMetadata) {\r\n    let fields = [];\r\n\r\n    // Always include sys_id\r\n    fields.push('sys_id');\r\n\r\n    // Add fields based on table type\r\n    if (tableName === 'incident' || tableName === 'sc_request') {\r\n        fields.push('number', 'short_description', 'state', 'priority');\r\n\r\n        if (userPermissions.canViewCaller) {\r\n            fields.push('caller_id');\r\n        }\r\n\r\n        if (userPermissions.canViewAssignment) {\r\n            fields.push('assigned_to', 'assignment_group');\r\n        }\r\n    } else if (tableName === 'cmdb_ci') {\r\n        fields.push('name', 'operational_status', 'install_status');\r\n\r\n        if (userPermissions.canViewConfiguration) {\r\n            fields.push('ip_address', 'fqdn', 'serial_number');\r\n        }\r\n    }\r\n\r\n    // Add metadata fields if requested\r\n    if (includeMetadata) {\r\n        fields.push('sys_created_on', 'sys_created_by', 'sys_updated_on', 'sys_updated_by');\r\n    }\r\n\r\n    return new GlideQuery(tableName)\r\n        .select(fields)\r\n        .limit(100)\r\n        .toArray();\r\n}\r\n\r\n/**\r\n * Example 4: Chained Conditional Selection with Method Chaining\r\n * Demonstrate building complex queries with multiple conditions\r\n */\r\nfunction getConditionalIncidentData(filters) {\r\n    let query = new GlideQuery('incident');\r\n\r\n    // Build base field list\r\n    let fields = ['sys_id', 'number', 'short_description', 'state'];\r\n\r\n    // Apply filters and modify field selection accordingly\r\n    if (filters.priority && filters.priority.length > 0) {\r\n        query.where('priority', 'IN', filters.priority);\r\n        fields.push('priority'); // Include priority field when filtering by it\r\n    }\r\n\r\n    if (filters.assignmentGroup) {\r\n        query.where('assignment_group', filters.assignmentGroup);\r\n        fields.push('assignment_group', 'assigned_to'); // Include assignment fields\r\n    }\r\n\r\n    if (filters.dateRange) {\r\n        query.where('sys_created_on', '>=', filters.dateRange.start)\r\n             .where('sys_created_on', '<=', filters.dateRange.end);\r\n        fields.push('sys_created_on'); // Include date when filtering by it\r\n    }\r\n\r\n    if (filters.includeComments) {\r\n        fields.push('comments', 'work_notes');\r\n    }\r\n\r\n    if (filters.includeResolution) {\r\n        fields.push('close_code', 'close_notes', 'resolved_by');\r\n    }\r\n\r\n    return query.select(fields)\r\n                .orderByDesc('sys_created_on')\r\n                .toArray(filters.limit || 50);\r\n}\r\n\r\n/**\r\n * Example 5: Security-conscious Field Selection\r\n * Exclude sensitive fields based on user context\r\n */\r\nfunction getSecureUserData(requestingUser, targetUserId) {\r\n    let baseFields = ['sys_id', 'name', 'user_name', 'active'];\r\n\r\n    // Check if requesting user can see additional details\r\n    if (gs.hasRole('user_admin') || requestingUser === targetUserId) {\r\n        // Full access - include all standard fields\r\n        return new GlideQuery('sys_user')\r\n            .where('sys_id', targetUserId)\r\n            .select(['sys_id', 'name', 'user_name', 'email', 'phone', 'department', 'title', 'manager', 'active'])\r\n            .toArray(1);\r\n    } else if (gs.hasRole('hr_admin')) {\r\n        // HR access - include HR-relevant fields but not IT details\r\n        return new GlideQuery('sys_user')\r\n            .where('sys_id', targetUserId)\r\n            .select(['sys_id', 'name', 'user_name', 'department', 'title', 'manager', 'active'])\r\n            .toArray(1);\r\n    } else {\r\n        // Limited access - only public information\r\n        return new GlideQuery('sys_user')\r\n            .where('sys_id', targetUserId)\r\n            .select(baseFields)\r\n            .toArray(1);\r\n    }\r\n}\r\n\r\n// Usage Examples:\r\n\r\n// Role-based selection\r\nvar adminIncidents = getIncidentsByRole('admin', gs.getUserID());\r\n\r\n// Performance-optimized query\r\nvar tasksWithDetails = getTasksWithOptionalFields({\r\n    includeUserDetails: true,\r\n    includeTimeTracking: false,\r\n    assignmentGroup: 'hardware'\r\n});\r\n\r\n// Dynamic field building\r\nvar dynamicData = getDynamicFieldSelection('incident', {\r\n    canViewCaller: true,\r\n    canViewAssignment: false\r\n}, true);\r\n\r\n// Complex conditional query\r\nvar filteredIncidents = getConditionalIncidentData({\r\n    priority: [1, 2],\r\n    assignmentGroup: 'network',\r\n    includeComments: true,\r\n    limit: 25\r\n});"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Field Default/GlideQueryFieldDefault.js",
    "content": "javascript:var gq = new global.GlideQuery('sys_user').get(gs.getUserID(), [ 'phone']);gq._value.phone;\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Field Default/README.md",
    "content": "# Use GlideQuery to get a default value for a field\n\nUse GlideQuery to get a field value when specifying a default in the dictionary record\n\nSubstitute the field name for the value you want (i.e., phone, first_name etc).\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/FlatMap to Nest New Queries/README.md",
    "content": "# Using FlatMap to Nest Queries\n\n`flatMap` returns a Stream to a parent stream, that cause the parent stream to return the result of the nested Stream.  It is extremely useful for using the results of a query to create and return the results of a new query based on the original's results.\n\nThe provided example is slightly contrived, but exhibits this behavior of using the results of a query on one table to create a query on a second table, and then view the output.\n\nIt finds uses the `sys_user` table to find the `sys_id` for username = david.miller, then queries the incident table for incidents where the `caller_id` is David Miller's sys_id (from the first query).  The output is the result of the inner query."
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/FlatMap to Nest New Queries/getIncidentInfoWithFlatMap.js",
    "content": "(function () {\n\n\tvar userQuery = new GlideQuery('sys_user')\n\t\t// Find david.miller's sys_id\n\t\t.where('user_name', 'david.miller')\n\t\t.select('sys_id')\n\t\t.flatMap(function (user) {\n\t\t\treturn new GlideQuery('incident')\n\t\t\t\t// Use david.miller's sys_id in a new query on a new table\n\t\t\t\t.where('caller_id', user.sys_id)\n\t\t\t\t.select([\n\t\t\t\t\t'number',\n\t\t\t\t\t'caller_id.user_name',\n\t\t\t\t\t'state',\n\t\t\t\t\t'assigned_to',\n\t\t\t\t\t'short_description',\n\t\t\t\t\t'priority',\n\t\t\t\t]);\n\t\t})\n\t\t.toArray(10);\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Get Delegates/GlideQueryGetDelegates.js",
    "content": "/**\n * Returns the active delegates for a given user, returning only the types required\n * @example\n * getDelegatesForType(gs.getUserID(), [\"approvals\"]);  // return only those flagged as approvals\n * getDelegatesForType(gs.getUserID(), [\"approvals\", \"notifications\"]); // return flagged as both approvals and notifications\n * @param {string} userId sys_id of the user to get delegates for\n * @param {Array} delegateTypes types of delegate to return, can be a combination of \"approvals\", \"notifications\", \"assignments\", \"invitations\".  Query is AND, so all specified must be true\n * @returns {JSON} Array of delegates in the format\n * [\n *    {\n *       \"name\": \"delegate_name\",\n *       \"sys_id\" : \"sys_id of delegate\",\n *       \"starts\" : \"start date/time\",\n *       \"ends\" : \"end date/time\",\n *    }\n * ]\n */\nfunction getDelegatesForType(userId, delegateTypes) {\n    delegateTypes = delegateTypes || [];  // if no delegate types passed then assume empty error\n    delegateTypes = !Array.isArray(delegateTypes) ? [ delegateTypes ] : delegateTypes;  // if delegateTypes is not an array then force to be one.\n    \n    // start initial GlideQuery, for the passed in user and active delegates\n    var gqDelegate = new global.GlideQuery('sys_user_delegate')\n        .where('user', userId)\n        .where('starts', '<=', 'javascript:gs.daysAgo(0)')\n        .where('ends', '>=', 'javascript:gs.daysAgo(0)');\n\n    // for each of the delegate types required, add a where clause to the GlideQuery\n    delegateTypes.forEach(function (_type) {\n        gqDelegate = gqDelegate.where(_type, true);\n    });\n\n    // finish off the GlideQuery, selecting the delegate info\n    gqDelegate = gqDelegate.select(['delegate', 'delegate$DISPLAY', 'starts', 'ends'])\n        .map(function (_delegate) {\n            // we don't want fields called $DISPLAY, so map to a new usable object\n            return {\n                \"name\": _delegate.delegate$DISPLAY,\n                \"sys_id\": _delegate.delegate,\n                \"starts\": _delegate.starts,\n                \"ends\": _delegate.ends\n            }\n        })\n        .reduce(function (arr, e) { arr.push(e); return arr; }, []); // return delegates as an array\n\n    return gqDelegate;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Get Delegates/README.md",
    "content": "# Get Delegates for user based on delegate type\n\nExample snippet using GlideQuery to get the active delegates for a user based on the different types available;\n\n- approvals\n- notifications\n- assignments\n- invitations\n\nBuilds an initial GlideQuery and then adds in the optional queries.\n\nExample call,\n\n```\ngetDelegatesForType(gs.getUserID(), [ \"approvals\", \"assignments\" ]);\n```\n\nMight return an object similar to the following\n\n```json\n[\n   {\n      \"name\": \"Mary Cruse\",\n      \"sys_id\": \"46d77b47a9fe1981007c0b3faff3edf8\",\n      \"starts\": \"2023-10-02 14:03:24\",\n      \"ends\": \"2100-01-01 23:59:59\"\n   },\n   {\n      \"name\": \"Fred Kunde\",\n      \"sys_id\": \"75826bf03710200044e0bfc8bcbe5d2b\",\n      \"starts\": \"2023-10-01 14:03:01\",\n      \"ends\": \"2100-01-01 23:59:59\"\n   }\n]\n```\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Get User's Roles from User Name/README.md",
    "content": "# Get User's Roles from User Name\n\nGlideQuery's can greatly simplify extracting information from queries, especially when dot walking.\n\nThis code has been updated to simplify the function to return an array of roles for the given user.  Shows the use of GlideQuery and .reduce() in place of .toArray().\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Get User's Roles from User Name/getUserRoles.js",
    "content": "function getUserRoles(userName) {\n\n    var gqRoles = new global.GlideQuery('sys_user_has_role')\n        .where('user.user_name', userName)\n        .select('role$DISPLAY')\n        // use .reduce() instead of .toArray() as it allows us to strip out only whats needed, and doesn't need to know the number of entries.\n        .reduce(function (arr, e) {\n            arr.push(e.role$DISPLAY);\n            return arr;\n        }, []);\n\n    return gqRoles;\n};\n\ngs.info(getUserRoles('bow.ruggeri'));\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Nested WHERE orWHERE GlideQueries/README.md",
    "content": "# WHERE Clauses with Nested GlideQueries\n\nCreating a query with multiple groupings of AND and OR statements would be impossible to decipher,\nso `GlideQuery` forbids them.\n\nAn example of this would be the following, where the query system would be unable to tell if this\nshould be \n\n`WHERE (caller_id is david.miller AND state is 1) OR (caller_id is beth.anglin)` \n\nversus\n\n`WHERE (caller_id is david.miller) AND (state is 1 OR caller_id is beth.anglin)` \n\n```javascript\nsecondQuery = new GlideQuery('task')\n\t.where('caller_id.user_name', 'david.miller')\n\t.where('state', 1)\n\t.orWhere('caller_id.user_name', 'beth.anglin')\n\t.select(['sys_id'])\n\t.toArray(10);\n```\n\nInstead, using the equivalent nested GlideQuery in the JavaScript file shows the proper way of nesting\nOR WHERE queries.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Nested WHERE orWHERE GlideQueries/nestedWhereQueries.js",
    "content": "(function nestedWhereQueries() {\n\t// Both of these queries will show the equivalent results.\n\t// They will both query for incidents WHERE the caller is beth.anglin OR (the caller is david.miller\n\t// AND the state is 1).\n\n\t// 1: This example demonstrates nesting the GlideQuery directly\n\tvar nestedQuery = new GlideQuery('incident')\n\t\t.where('caller_id.user_name', 'beth.anglin')\n\t\t.orWhere(new GlideQuery().where('caller_id.user_name', 'david.miller').where('state', 1))\n\t\t.select([\n\t\t\t'number',\n\t\t\t'caller_id.user_name',\n\t\t\t'state',\n\t\t\t'assigned_to',\n\t\t\t'short_description',\n\t\t\t'priority',\n\t\t])\n\t\t.toArray(10);\n\n\tgs.log(JSON.stringify(nestedQuery, null, 2));\n\n\t// 2: This example shows creating a query, and then nesting it in a second query\n\tvar query = new GlideQuery('incident')\n\t\t.where('caller_id.user_name', 'david.miller')\n\t\t.where('state', 1);\n\n\tvar secondQuery = new GlideQuery('incident')\n\t\t.where('caller_id.user_name', 'beth.anglin')\n\t\t.orWhere(query)\n\t\t.select([\n\t\t\t'number',\n\t\t\t'caller_id.user_name',\n\t\t\t'state',\n\t\t\t'assigned_to',\n\t\t\t'short_description',\n\t\t\t'priority',\n\t\t])\n\t\t.toArray(10);\n\n\tgs.log(JSON.stringify(secondQuery, null, 2));\n\n\t/* \n\tBoth Output:\n\n\t\t*** Script: [\n\t{\n\t\t\"number\": \"INC0000601\",\n\t\t\"caller_id\": {\n\t\t\"user_name\": \"beth.anglin\"\n\t\t},\n\t\t\"state\": 7,\n\t\t\"assigned_to\": null,\n\t\t\"short_description\": \"The USB port on my PC stopped working\",\n\t\t\"priority\": 5,\n\t\t\"sys_id\": \"9e7f9864532023004247ddeeff7b121f\"\n\t},\n\t{\n\t\t\"number\": \"INC0000049\",\n\t\t\"caller_id\": {\n\t\t\"user_name\": \"beth.anglin\"\n\t\t},\n\t\t\"state\": 2,\n\t\t\"assigned_to\": \"9ee1b13dc6112271007f9d0efdb69cd0\",\n\t\t\"short_description\": \"Network storage unavailable\",\n\t\t\"priority\": 2,\n\t\t\"sys_id\": \"ef4225a40a0a0b5700d0b8a790747812\"\n\t},\n\t{\n\t\t\"number\": \"INC0009001\",\n\t\t\"caller_id\": {\n\t\t\"user_name\": \"David.Miller\"\n\t\t},\n\t\t\"state\": 1,\n\t\t\"assigned_to\": null,\n\t\t\"short_description\": \"Unable to post content on a Wiki page\",\n\t\t\"priority\": 3,\n\t\t\"sys_id\": \"a623cdb073a023002728660c4cf6a768\"\n\t},\n\t{\n\t\t\"number\": \"INC0009005\",\n\t\t\"caller_id\": {\n\t\t\"user_name\": \"David.Miller\"\n\t\t},\n\t\t\"state\": 1,\n\t\t\"assigned_to\": null,\n\t\t\"short_description\": \"Email server is down.\",\n\t\t\"priority\": 1,\n\t\t\"sys_id\": \"ed92e8d173d023002728660c4cf6a7bc\"\n\t},\n\t{\n\t\t\"number\": \"INC0007001\",\n\t\t\"caller_id\": {\n\t\t\"user_name\": \"David.Miller\"\n\t\t},\n\t\t\"state\": 1,\n\t\t\"assigned_to\": null,\n\t\t\"short_description\": \"Employee payroll application server is down.\",\n\t\t\"priority\": 1,\n\t\t\"sys_id\": \"f12ca184735123002728660c4cf6a7ef\"\n\t},\n\t{\n\t\t\"number\": \"INC0007002\",\n\t\t\"caller_id\": {\n\t\t\"user_name\": \"David.Miller\"\n\t\t},\n\t\t\"state\": 1,\n\t\t\"assigned_to\": null,\n\t\t\"short_description\": \"Need access to the common drive.\",\n\t\t\"priority\": 4,\n\t\t\"sys_id\": \"ff4c21c4735123002728660c4cf6a758\"\n\t}\n\t]\n\t*/\n})();\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Remote Table/README.md",
    "content": "This script is used to combine 2 internal tables:sc_req_item and sc_item_produced_record from a Report Table Definition => Script tab. \nWe can create a report on the created remote table (Pie chart). \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideQuery/Remote Table/remotetabldef.js",
    "content": "(function executeQuery(v_table, v_query) {\n    var months = 12; //This value should be set in a system property. \n    var startDate = gs.monthsAgo(months);\n\n    addUsage('sc_req_item', 'cat_item.name'); //You might want to group by sys_id instead of name\n    addUsage('sc_item_produced_record', 'producer.name'); //You might want to group by sys_id instead of name\n\n    function addUsage(table, groupBy) {\n        new GlideQuery(table)\n            .where('sys_created_on', '>', startDate)\n            .aggregate('count')\n            .groupBy(groupBy)\n            .select()\n            .forEach(function(result) {\n                v_table.addRow({\n                    u_count: result.count,\n                    u_name: result.group[groupBy],\n                    u_type: table\n                });\n\n            });\n\n    }\n})(v_table, v_query);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/ACL enforcement using GlideRecord/README.md",
    "content": "Using GlideRecordSecure to query data with built in access checks is as simple as that! With this class and associated API, you can have confidence that your data is, well, secure!\n\nWhen utilizing this class and associated API within scripts, the same rules as GlideRecord apply. On the “server side” (like in a Business Rule), the GlideRecordSecure API can only be run from scripts within global or scoped applications. On the “client side” (like in a Client Script), the GlideRecord API can only be run from scripts within global applications.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/ACL enforcement using GlideRecord/glideRecordSecure.js",
    "content": "var grSecure = new GlideRecordSecure('<table_name>');\ngrSecure.addEncodedQuery('<query>');\ngrSecure.query();\nwhile(grSecure.next())\n{\n<action>\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Add n number of users to n number of groups using server scripts/AddUserstoGroups.js",
    "content": "var users = ['user1', 'user2', 'user3', 'user4', 'user5']; // Add Users Array here users can be of any number, and a list of users arrays will be passed\n\nvar groups = ['group1', 'group2', 'group3', 'group4', 'group5']; // Add Groups Array here groups can be of any number, and a list of groups arrays will be passed\n\nfor (var i = 0; i < groups.length; i++) { // Visit all the groups in the list of array\n    var groupSysId = groups[i];\n\n    for (var j = 0; j < users.length; j++) { // Visit all the users in the list of array\n        var userSysId = users[j];\n\n        var grMember = new GlideRecord('sys_user_grmember'); // Query for Group Membership table\n        grMember.initialize();\n        grMember.group = groupSysId;\n        grMember.user = userSysId;\n        grMember.insert(); // Users will be inserted into the Group Membership table\n\n        gs.info('Added User ' + userSysId + 'to group ' + groupSysId); // Info Message displaying after each users group membership creation\n    }\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Add n number of users to n number of groups using server scripts/README.md",
    "content": "This server-side script will add any number of Users to any number of groups. It is required to be used in Server-Side scripts.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Archiving Old Incident Records to Improve Performance/readme.md",
    "content": "## Purpose\nThis document explains how to archive old incident records from the `incident` table to an archive table `ar_incident` to improve performance, while preserving historical data for reporting and audit purposes.\n## Solution Overview\nUse **ServiceNow Archive Rules** to automatically move incidents to an archive table based on specific conditions:\n- Incidents that are **closed**.\n- Incidents that are **inactive** (`active = false`).\n- Incidents that were closed **150 days ago or earlier**.\nThe records are moved to the archive table `ar_incident`, which preserves all necessary fields for historical reference.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Archiving Old Incident Records to Improve Performance/script.js",
    "content": "var gr = new GlideRecord('incident');\ngr.addQuery('state', 7); // Closed\ngr.addQuery('active', false);\ngr.addQuery('closed_at', '<=', gs.daysAgo(150));\ngr.query();\nwhile (gr.next()) {\n    var ar = new GlideRecord('ar_incident'); //ar_incident is the new table for storing archive data\n    ar.initialize();\n    ar.short_description = gr.short_description;\n    ar.description = gr.description;\n    // Copy other necessary fields\n    ar.insert();\n    gr.deleteRecord(); // deleting from incident table if record in inserted in the archived table\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/CheckDuplicate-Server/readme.md",
    "content": "Scan all Servers (cmdb_ci_server). For each one, check if there is another CI in cmdb_ci_computer with the same name but not a server (sys_class_name != cmdb_ci_server).\n\nIf found, log the server name and the duplicate CI’s class; keep a running duplicate count; finally log the total.\n\n*******Descriton****\n1. var gr = new GlideRecord(\"cmdb_ci_server\");\n2. Creates a record set for Server CIs.\n\n\ngr.addEncodedQuery(\"sys_class_name=cmdb_ci_server\");\n3. Redundant: you’re already targeting the cmdb_ci_server table which is a class table. This filter doesn’t harm, but it’s unnecessary.\n\n\nwhile (gr.next()) { ... }\n4. Loops through each server CI.\n\n\n5.Inside loop:\n\nQuery cmdb_ci_computer for records with the same name but where sys_class_name != cmdb_ci_server.\n6. If found, log the duplicate and increment dupCount.\n\n\n\n7. Finally logs total dupCount.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/CheckDuplicate-Server/script.js",
    "content": "var dupCount = 0;\nvar gr = new GlideRecord(\"cmdb_ci_server\");\n//gr.addQuery(\"name\", \"value\");\ngr.addEncodedQuery(\"sys_class_name=cmdb_ci_server\");\ngr.query();\nwhile (gr.next()) {\n   var dup = new GlideRecord(\"cmdb_ci_computer\");\n\tdup.addQuery(\"name\", gr.name);\n\tdup.addQuery(\"sys_class_name\", \"!=\", \"cmdb_ci_server\");\n\tdup.query();\n\tif (dup.next()) {\n\t\tgs.log(\"\\t\" + gr.name + \"\\t\" + dup.sys_class_name);\n\t\tdupCount++;\n\t}\n\n}\ngs.log(\"dup count=\" + dupCount);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Choose Window for better performance/README.md",
    "content": "chooseWindow method is used to paginate the records while using Glide Record on any table.\nThis method allows you to paginate through records, fetching only a window (subset) of records, which can be useful for performance when dealing with large datasets. \nsummary : Paginate large result sets by selecting a window of records."
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Choose Window for better performance/script.js",
    "content": "var gr = new GlideRecord('incident');\ngr.chooseWindow(0, 10); // Fetch the first 10 records\ngr.query();\nwhile (gr.next()) {\n  gs.info(gr.number);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Compare_2_records/README.md",
    "content": "# Compare Two Records Using GlideRecord (Global Scope)\n\nThis snippet compares two records from the same table in ServiceNow field-by-field using the **GlideRecord API**.  \nIt’s useful for debugging, verifying data after imports, or checking differences between two similar records.\n\n---\n\n## Working \nThe script:\n1. Retrieves two records using their `sys_id`.\n2. Includes or Excludes the system fields.\n2. Iterates over all fields in the record.\n3. Logs any fields where the values differ.\n\n---\n\n## Scope\nThis script is designed to run in the Global scope.\nIf used in a scoped application, ensure that the target table is accessible from that scope (cross-scope access must be allowed).\n\n## Usage\nRun this script in a **Background Script** or **Fix Script**:\n\n```js\ncompareRecords('incident', 'sys_id_1_here', 'sys_id_2_here', false);\n```\n---\n## Example Output\n```js\nshort_description: \"Printer not working\" vs \"Printer offline\"\nstate: \"In Progress\" vs \"Resolved\"\npriority: \"2\" vs \"3\"\nComparison complete.\n```\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Compare_2_records/compareRecords.js",
    "content": "/**\n * Compare two records in a ServiceNow table field-by-field.\n * Logs all field differences between the two records, including display values.\n * \n * Parameters: \n * @param {string} table - Table name\n * @param {string} sys_id1 - sys_id of first record\n * @param {string} sys_id2 - sys_id of second record\n * @param {boolean} includeSystemFields - true to compare system fields, false to skip them\n * \n * Usage:\n * compareRecords('incident', 'sys_id_1_here', 'sys_id_2_here', true/false);\n */\n\nfunction compareRecords(table, sys_id1, sys_id2, includeSystemFields) {\n    var rec1 = new GlideRecord(table);\n    var rec2 = new GlideRecord(table);\n\n    if (!rec1.get(sys_id1) || !rec2.get(sys_id2)) {\n        gs.error('One or both sys_ids are invalid for table: ' + table);\n        return;\n    }\n\n    var fields = rec1.getFields();\n    gs.info('Comparing records in table: ' + table);\n\n    for (var i = 0; i < fields.size(); i++) {\n        var field = fields.get(i);\n        var fieldName = field.getName();\n        \n        if( !includeSystemFields && fieldName.startsWith('sys_') ) {\n            continue; \n        }\n\n        var val1 = rec1.getValue(fieldName);\n        var val2 = rec2.getValue(fieldName);\n\n        var disp1 = rec1.getDisplayValue(fieldName);\n        var disp2 = rec2.getDisplayValue(fieldName);\n\n        if (val1 != val2) {\n            gs.info(\n                fieldName + ': Backend -> \"' + val1 + '\" vs \"' + val2 + '\", ' +\n                'Display -> \"' + disp1 + '\" vs \"' + disp2 + '\"'\n            );\n        }\n    }\n\n    gs.info('Comparison complete.');\n}\n\n// Example call\ncompareRecords('incident', 'sys_id_1_here', 'sys_id_2_here', false);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Conditional Batch Update/README.md",
    "content": "# GlideRecord Conditional Batch Update\n\n## Description\nThis snippet updates multiple records in a ServiceNow table based on a GlideRecord encoded query.  \nIt logs all updated records and provides a safe, controlled way to perform batch updates.\n\n## Prerequisites\n- Server-side context (Background Script, Script Include, Business Rule)\n- Access to the table\n- Knowledge of GlideRecord and encoded queries\n\n## Note\n- Works in Global Scope\n- Server-side execution only\n- Logs updated records for verification\n- Can be used for maintenance, bulk updates, or automated scripts\n\n## Usage\n```javascript\n// Update all active low-priority incidents to priority=2 and state=2\nbatchUpdate('incident', 'active=true^priority=5', {priority: 2, state: 2});\n```\n\n## Sample Output\n```\nUpdated record: abc123\nUpdated record: def456\nUpdated record: ghi789\nBatch update completed. Total records updated: 3\n```"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Conditional Batch Update/batchUpdate.js",
    "content": "/**\n * Update multiple records in a table based on an encoded query with field-level updates.\n * Logs all updated records for verification.\n *\n * @param {string} table - Name of the table\n * @param {string} encodedQuery - GlideRecord encoded query to select records\n * @param {object} fieldUpdates - Key-value pairs of fields to update\n */\nfunction batchUpdate(table, encodedQuery, fieldUpdates) {\n    if (!table || !encodedQuery || !fieldUpdates || typeof fieldUpdates !== 'object') {\n        gs.error('Table, encodedQuery, and fieldUpdates (object) are required.');\n        return;\n    }\n\n    var gr = new GlideRecord(table);\n    gr.addEncodedQuery(encodedQuery);\n    gr.query();\n\n    var count = 0;\n    while (gr.next()) {\n        for (var field in fieldUpdates) {\n            if (gr.isValidField(field)) {\n                gr.setValue(field, fieldUpdates[field]);\n            } else {\n                gs.warn('Invalid field: ' + field + ' in table ' + table);\n            }\n        }\n\n        gr.update();\n        gs.info('Updated record: ' + gr.getValue('sys_id'));\n        count++;\n    }\n\n    gs.info('Batch update completed. Total records updated: ' + count);\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Count Records By Column/README.md",
    "content": "# Count records in a table by column\nScript to count records in a table, grouped by a certain column and output as CSV for further data analysis.\nCan be useful where further data analysis is needed for example during data or domain migration projects.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Count Records By Column/script.js",
    "content": "// Count records in a table, grouped by a certain column and output as CSV for further data analysis\n\n// Table to count records in\nvar count_table = 'sys_attachment';\n// Column to group the count by\nvar count_column = 'table_name';\n// (Optional) Limit results by query\nvar encoded_query = 'table_nameLIKEsys';\n\ncount_gr = new GlideAggregate(count_table);\ncount_gr.addAggregate('COUNT', count_column);\ncount_gr.orderByAggregate('COUNT', count_column);\ncount_gr.groupBy(count_column);\nif (encoded_query != '')\n    count_gr.addEncodedQuery(encoded_query);\ncount_gr.query();\nvar csv_output = \"\\n\" + count_column + \",count\\n\";\nwhile (count_gr.next()) {\n    var colDispValue = count_gr.getDisplayValue(count_column);\n    if (colDispValue == \"\") { colDispValue = \"(empty)\"; }\n    csv_output += colDispValue + \",\" + count_gr.getAggregate('COUNT', count_column) + \"\\n\";\n}\n\ngs.info(csv_output);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Display list of records based on Users Location/README.md",
    "content": "Get the list of records based on the User's Location while getting the list of records\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Display list of records based on Users Location/listOfRecordsBasedOnLocation.js",
    "content": "// Business Rule to display the list of records based on the user's location\n\nWhen to Run - Before Query\nTable - Any table in which you want to perform this script\nScripts - \n(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n    var current_user = gs.getUserID();\n\n    var usr = new GlideRecord('sys_user'); // Query User Table\n    usr.addQuery('sys_id', current_user);\n    usr.query();\n    if (usr.next()) {\n        if (usr.location != '') {\n        current.addQuery('location=' + usr.location); // Querying the user's location with current record location \n    }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Fetch active incidents assigned to a specific group/README.md",
    "content": "This script basically built to pull active incidents for a particular group in order to act quickly.\nDemo script retrieves all active incidents assigned to the \"Network Support\" group.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Fetch active incidents assigned to a specific group/code.js",
    "content": "\nvar gr = new GlideRecord('incident');\n    gr.addQuery('active', true);\n    gr.addQuery('assignment_group.name', 'Network Support');\n    gr.query();\n\n    while (gr.next()) {\n        gs.info('Incident Number: ' + gr.getValue('number'));\n    }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Fetch groups that have no members in them/README.md",
    "content": "This is script is useful in listing out the groups that do not have any members. You can use it before setting any group record/s as inactive. \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Fetch groups that have no members in them/fetchEmptyGroups.js",
    "content": "var arr=[]; \n\nvar gm = new GlideAggregate('sys_user_grmember');\ngm.addAggregate('COUNT','group');\ngm.query();\nwhile(gm.next())\n{\nvar gn=gm.getValue('group');\narr.push(gn);     // get the groups with one or more members \n}\n\nvar str=arr.join(',');\nvar grp = new GlideRecord('sys_user_group');\ngrp.addActiveQuery();\ngrp.addQuery('sys_id','NOT IN',str);     // skip the groups that have members \ngrp.query();\nwhile(grp.next())\n{\ngs.print(grp.getDisplayValue('name'));  // prints out the groups that have no members\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Field Level Audit/README.md",
    "content": "# GlideRecord Field-Level Audit\n\n## Description\nThis snippet compares two GlideRecord objects field by field and logs all differences.  \nIt is useful for debugging, auditing updates, or validating changes in Business Rules, Script Includes, or Background Scripts.\n\n## Prerequisites\n- Server-side context (Background Script, Business Rule, Script Include)\n- Two GlideRecord objects representing the original and updated records\n- Access to the table(s) involved\n\n## Note\n- Works in Global Scope\n- Server-side execution only\n- Logs all fields with differences to system logs\n- Does not modify any records\n## Usage\n```javascript\n// Load original record\nvar oldRec = new GlideRecord('incident');\noldRec.get('sys_id_here');\n\n// Load updated record\nvar newRec = new GlideRecord('incident');\nnewRec.get('sys_id_here');\n\n// Compare and log differences\nfieldLevelAudit(oldRec, newRec);\n```\n\n## Output\n```\nField changed: priority | Old: 5 | New: 2\nField changed: state    | Old: 1 | New: 3\nField changed: short_description | Old: 'Old description' | New: 'New description'\n```"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Field Level Audit/fieldLevelAudit.js",
    "content": "/**\n * Compare two GlideRecord objects field by field and log differences.\n *\n * @param {GlideRecord} grOld - Original record before changes\n * @param {GlideRecord} grNew - Updated record to compare against\n */\nfunction fieldLevelAudit(grOld, grNew) {\n    if (!grOld || !grNew) {\n        gs.error('Both old and new GlideRecord objects are required.');\n        return;\n    }\n\n    var fields = grOld.getFields();\n    fields.forEach(function(f) {\n        var name = f.getName();\n        var oldValue = grOld.getValue(name);\n        var newValue = grNew.getValue(name);\n\n        if (oldValue != newValue) {\n            gs.info('Field changed: ' + name + \n                    ' | Old: ' + oldValue + \n                    ' | New: ' + newValue);\n        }\n    });\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Find Date Overlapping/README.md",
    "content": "Logic to check if new dates are falling between already created record's dates.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Find Date Overlapping/isSimilarDates.js",
    "content": "// Logic to check if new dates are falling between already created record's dates.\n\ncheckIteSimilarDates: function(dep,arr,trvlr) { // passing new (departure Date , arrival date , user's sys_id)\n\t\tdep.toString();\n\t\tarr.toString();\n\t\ttrvlr.toString();\n\t\tvar newstart = new GlideDateTime();\n\t\tnewstart.setDisplayValue(dep);\n\t\tvar newend=new GlideDateTime();\n\t\tnewend.setDisplayValue(arr);\n\t\t\n\t\tgs.info(\"Dep Date \"+dep+\" Arr \"+arr+\" trvlr \"+trvlr);\n\t\tvar tr= new GlideRecord('x_adsr_travel_expe_travel_request');\n\t\ttr.addEncodedQuery('u_traveler='+trvlr);\n\t\ttr.query();\n\t\twhile(tr.next()){\n\t\t\tvar itin= new GlideRecord('x_adsr_travel_expe_itenary');\n\t\t\titin.addEncodedQuery('u_travelrequest.sys_idSTARTSWITH'+tr.sys_id);\n\t\t\titin.query();\n\t\t\twhile(itin.next()){\n\t\t\t\t//gs.info('@@true Condition!!');\n\t\t\t\tvar st=new GlideDateTime();\n\t\t\t\tvar en= new GlideDateTime();\n\t\t\t\tst.setDisplayValue(itin.u_departuredatetime.toString());\n\t\t\t\ten.setDisplayValue(itin.u_arrivaldatetime.toString());\n\t\t\t\t\n\t\t\t\tif((st<newstart && newstart<en) || ( st < newend && newend < en ) ||(newstart<en && en<=newstart)||\n(newstart < st && st<newend)){        // Logic to check if new dates are falling between already created record's dates.\n\t\t\t\t\tgs.addInfoMessage('Similar travel request exists'+tr.getValue('number'));\n\t\t\t\t\treturn true;\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\n\t\t\t}\n\t\t\n\t\t\t\n\t}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Find No Of Days/README.md",
    "content": "This code will let you find the date difference between the provided dates: Helps in calculating No of days, payment processing , etc.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Find No Of Days/findNoOfDays.js",
    "content": "function findNoOfDays(start, end) {\n\n            var date1 = new GlideDateTime();\n            date1.setDisplayValue(start);\n            var date2 = new GlideDateTime();\n            date2.setDisplayValue(end);\n\n            var date = GlideDateTime.subtract(date1, date2);\n\n            var total = date.getDisplayValue();\n            total = date.getDayPart();\n\n            return parseInt(total);\n        }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get All Groups without Manager/README.md",
    "content": "The GlideRecord query is used to get the groups names having manager field Empty.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get All Groups without Manager/getGroupsWithoutManager.js",
    "content": "(function () {\n    var groups = [];\n    var groupGr = new GlideRecord('sys_user_group'); \n    groupGr.addActiveQuery(); \n    groupGr.addNullQuery('manager');\n    groupGr.query();\n    while (groupGr.next()) {\n        groups.push(groupGr.getDisplayValue()); // used getDisplayValue() method to get the name as a string instead of using groupGr.name\n    }\n    gs.info(\"Groups without manager are : \" + groups);\n\n})(); \n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Contains role of a role/Get contains role of a role.js",
    "content": "var gr = new GlideRecord('sys_user_role_contains');\ngr.addQuery('role.name', '<role_name>'); //replace <role_name> with the name of a role to which you need to get all the conatins role\ngr.query();\nwhile(gr.next()){\n  gs.info(gr.contains.name.toString());\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Contains role of a role/README.md",
    "content": "The script in GlideRecord > Get Contains role of a role >Get contains role of a role.js file will get all the roles contains in a role.\n\nThe <role_name>' needs to be replaced with the name of a role to which you need to get all the contains role\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Record Fields in JSON/README.md",
    "content": "<h2>Get Record Fields in JSON</h2>\n\n<h3>Description</h3>\n<p>This code fetches all field values from a specified record in ServiceNow and returns them in a JSON format. This is ideal for exporting data or logging record details in a structured format. By simply passing in a table name and a <code>sys_id</code>, it retrieves and formats the fields, allowing for easy data handling.</p>\n\n<h3>Usage</h3>\n<p>Call the function and provide:</p>\n<ul>\n    <li><strong>tableName</strong> (String): The name of the table where the record is stored.</li>\n    <li><strong>sysId</strong> (String): The <code>sys_id</code> of the record to retrieve.</li>\n</ul>\n\n\n\n<h3>Example</h3>\n<p>In this example, we fetch all fields from a record in the <code>incident</code> table with a specified <code>sys_id</code> and log the result to the console.</p>\n<pre><code>\nvar recordData = grFetchRecordFields('incident', 'sys_id_value');\ngs.info('Record Data: ' + recordData);\n</code></pre>\n\n<h4>Output</h4>\n<p>If the record with the specified <code>sys_id</code> exists, the output will be a JSON string containing all field names and their values. For example:</p>\n<pre><code>{\n    \"number\": \"INC0010001\",\n    \"short_description\": \"Sample incident description\",\n    \"priority\": \"3\",\n    \"state\": \"New\",\n    \"assigned_to\": \"System Administrator\",\n    ...\n}\n</code></pre>\n\n<h3>Benefits</h3>\n<ul>\n    <li><strong>Simplifies data retrieval</strong>: Quickly fetch all field values from a record without manually specifying each field.</li>\n    <li><strong>Easy JSON output</strong>: Ideal for logging or API responses where JSON format is needed.</li>\n    <li><strong>Reduces errors</strong>: Avoids repetitive code and minimizes the risk of missing fields during retrieval.</li>\n</ul>\n\n<h3>Notes</h3>\n<ul>\n    <li>Ensure <code>tableName</code> and <code>sysId</code> are valid to prevent runtime errors.</li>\n    <li>Use this with caution on tables with many fields to avoid performance issues.</li>\n</ul>\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Record Fields in JSON/script.js",
    "content": "//# Fetch all fields of a record and return as JSON\nfunction grFetchRecordFields(tableName, sysId) {\n    var result = {};\n    var gr = new GlideRecord(tableName);\n    if (gr.get(sysId)) {\n        var fields = gr.getFields();\n        for (var i = 0; i < fields.size(); i++) {\n            var fieldName = fields.get(i).getName();\n            result[fieldName] = gr.getValue(fieldName);\n        }\n    }\n    return JSON.stringify(result);\n}\n\n// Example Usage:\nvar recordData = grFetchRecordFields('incident', 'sys_id_value');\ngs.info('Record Data: ' + recordData);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Reference Record/README.md",
    "content": "# Get Reference Record with GlideRecord\n\nThis folder contains examples demonstrating how to retrieve and work with reference records using `getRefRecord()` in ServiceNow server-side scripting.\n\n## Overview\n\n`getRefRecord()` is used to retrieve the full GlideRecord object of a reference field. This allows access to additional fields from the referenced record, such as `name`, `email`, or other attributes beyond the display value.\n\nBecause `getRefRecord()` does not throw an error when the reference field is empty or invalid, it is important to use `isValidRecord()` to verify that the reference was successfully retrieved before accessing its fields.\n\n## Script Descriptions\n\n- get_assignment_group_from_incident.js retrieves the assignment group from an incident record and prints its name if the group exists.\n- get_requested_by_user.js retrieves a change request by `sys_id`, then accesses the `requested_by` user record. If valid, it prints the user's username and email.\n\n## Best Practices\n\n- Always use `isValidRecord()` after calling `getRefRecord()` to ensure the reference is valid.\n- Use `getRefRecord()` when you need to access fields from a referenced record, not just its display value.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Reference Record/get_assignment_group_from_incident.js",
    "content": "function getAssignmentGroup(grIncident) {\n  var grGroup = grIncident.assignment_group.getRefRecord();\n  if (grGroup.isValidRecord()) {\n    gs.print(grGroup.getValue(\"name\"));\n  }\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Reference Record/get_requested_by_user.js",
    "content": "var changerecord = new GlideRecord('change_request');\nchangerecord.get('sys_id', 'a9e9c33dc61122760072455df62663d2' );\n\nvar requestedBy = changerecord.requested_by.getRefRecord();\n\nif (requestedBy.isValidRecord()) {\n\n\tgs.print(\"User Name: \" + requestedBy.user_name);\n\tgs.print(\"User Email: \" + requestedBy.email);\n}\nelse {\n\tgs.print(\"Caller record wasn't found\");\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Variables from RITM/README.md",
    "content": "# Get RITM Variables in JSON format\n\nThis method returns all the variables for a given RITM (along with MRVS if requested) as a JSON object.\n\nFor example;\n\n```javascript\n// Get a sample RITM\nvar requestItem = new GlideRecord(\"sc_req_item\");\nrequestItem.get(\"797b7e8e4706b51001612c44846d4341\")\n\nvar json = getVariablesJSON(requestItem, true);\ngs.info(JSON.stringify(json, '', 3))\n```\nReturns JSON object similar to the following:\n\n```json\n{\n   \"virtual_machine\": [\n      {\n         \"vm_number\": \"1\",\n         \"name\": \"Windows VM\"\n      },\n      {\n         \"vm_number\": \"2\",\n         \"name\": \"Linux VM\"\n      }\n   ],\n   \"disks\": [\n      {\n         \"disk_vm_number\": \"1\",\n         \"disk_size\": \"100Gb\"\n      },\n      {\n         \"disk_vm_number\": \"2\",\n         \"disk_size\": \"500Gb\"\n      },\n      {\n         \"disk_vm_number\": \"2\",\n         \"disk_size\": \"1Tb\"\n      }\n   ],\n   \"requested_for\": \"ee826bf03710200044e0bfc8bcbe5dd4\",\n   \"required_by\": \"2023-11-04\"\n}\n```\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get Variables from RITM/getVariablesJSON.js",
    "content": "/*\n * getVariablesJSON\n * Return all variables from the provided RITM as a JSON object\n * Optional second parameter indicates if MRVS should be included (default true)\n */\nfunction getVariablesJSON(ritm, includeMRVS) {\n    includeMRVS = includeMRVS != undefined ? includeMRVS : true; // set to true if not provided\n\n    var ritmVariables = ritm.variables;\n    var variablesJSON = {};\n\n    for (variableName in ritmVariables) {\n        if (ritmVariables[variableName].isMultiRow() && includeMRVS) {\n            variablesJSON[variableName] = JSON.parse(ritmVariables[variableName]);\n        } else if (!ritmVariables[variableName].isMultiRow()) {\n            variablesJSON[variableName] = ritmVariables[variableName].toString();\n        }\n    }\n    return variablesJSON;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get all task records with at least on active child task/Get all task records with at least on active child task.md",
    "content": "# Glide record to get all the task records which have at least one active child task"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get all task records with at least on active child task/code.js",
    "content": "var sysIds = [];\n\nvar glideRecordTaskTable = new GlideRecord('task');\nvar glideRecordJoin = glideRecordTaskTable.addJoinQuery('task', 'sys_id', 'parent');\nglideRecordJoin.addCondition('active', true);\nglideRecordTaskTable.query();\nwhile(glideRecordTaskTable.next()){\n    sysIds.push(glideRecordTaskTable.getValue('sys_id'));\n}"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get all user's group based on username/README.md",
    "content": "Get all user's group based on username\n\nJust add replace the **userid** with the correct username in script\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get all user's group based on username/script.js",
    "content": "var groups= [];\n\nvar Usergr = new GlideRecord(\"sys_user\");\nUsergr.addActiveQuery();\nUsergr.addQuery(\"user_name\",\"<user_id>\");  //Add username\nUsergr.query();\nif (Usergr.next()) {\nvar usrid = Usergr.getUniqueValue(); \n}\n\nvar GroupRelationGR = new GlideRecord('sys_user_grmember');\nGroupRelationGR.addEncodedQuery('user=' +usrid);\nGroupRelationGR.query();\nwhile(GroupRelationGR.next()){\ngroups.push(GroupRelationGR.getDisplayValue('group'));\t\n}\n\ngs.info('User is part of this groups= ' + groups);\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get all users whose email is empty/README.md",
    "content": "# Get all users without email\n\nUse the script in script.js file to get the list of all users in sys_user table who do not have an email.\nThis GlideRecord script can be used in multiple places. For example in background scripts.\n\n### Did some optimization in the code\n1. Used different variable name instead of gr to reference a GlideRecord object.\n2. Used addActiveQuery() method to filter out just the active records.\n3. Used getDisplayValue() method to push string values in the array instead of using dot notation.\n4. Used self executing function to wrap the code in a function for reducing variable scoping issues.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get all users whose email is empty/script.js",
    "content": "(function () {\n    var users = [];\n    var userGr = new GlideRecord('sys_user'); // used different variable name instead of gr.\n    userGr.addActiveQuery(); // used to filter more on the records fetched.\n    userGr.addNullQuery('email');\n    userGr.query();\n    while (userGr.next()) {\n        users.push(userGr.getDisplayValue()); // used getDisplayValue() method to get the name as a string instead of using gr.name\n    }\n    gs.info(\"Users without email are : \" + users);\n\n})(); // Used a self executing function to wrap the code with a function for reducing variable scoping issues\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get field from GlideRecord/README.md",
    "content": "The code snippet provided is a JavaScript function that retrieves and logs the field names from a specified GlideRecord. The function getFields takes a GlideRecord instance as a parameter and returns an array containing the names of all the fields in the specified record.\n\nFunctionality\n  getFields(gr: GlideRecord): Array\n  Purpose:\n    - Returns an array of all the fields in the specified GlideRecord.\n  Parameters:\n    - gr (GlideRecord): A GlideRecord instance positioned to a valid record.\n  Returns:\n    - An array of strings representing the field names in the specified GlideRecord.\n  Note:\n    - If there is a field name which is the same as the table name, the getFields() method does not return the value of the field.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get field from GlideRecord/getField.js",
    "content": "/**\n * getFields - Returns an array of all the fields in the specified GlideRecord.\n * \n * Note: If there is a field name that is the same as the table name, the getFields() method does not return the value of the field.\n * \n * @function\n * @param {GlideRecord} gr - GlideRecord instance positioned to a valid record.\n * @returns {Array} - Field names for the specified GlideRecord.\n**/\n\nvar queryString = \"<Encoded query to filter the record>\";\nvar now_GR = new GlideRecord('<table_name>');\nnow_GR.addEncodedQuery(queryString);\nnow_GR.query();\nnow_GR.next();\n\nvar gRU = new GlideRecordUtil();\nvar fieldList = gRU.getFields(now_GR);\ngs.info(fieldList); // Output: Array of field names for the specified GlideRecord.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get link for the Record/README.md",
    "content": "getLink() generates a direct URL to the record, useful for embedding links to records in notifications or logging them.\nSummary: Generate a URL link to the record."
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get link for the Record/script.js",
    "content": "var gr = new GlideRecord('incident');\nif (gr.get('sys_id_of_record')) {\n  gs.info('Incident link: ' + gr.getLink());\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get-task-containing-sensitive-data/README.md",
    "content": "This script is used to get all task records based on sensitive data entered into this task based records. To make it simple to add the criteria for GDPR or sentive data i \nhave created a property and used it in this line : getProperty.addQuery('name', 'nn.criticalDataPhrases');\n\nExample: \nProperty name : criteria.gdpr\nValue: BSN,Burgerservicenummer,voornaam,achternaam,geslacht,gender,Geboortedatum,Birth,adres,woonplaats,straatnaam,huisnummer,postcode,telefoonnummer,mobiel,hypotheeknummer,IBAN,Rekeningnummer,Rekeningnr,Rek. nr.,Verzekeringsnummer,verzekeringsnr,wachtwoord,gebruikersnaam,username,password,pwd\n\nOutput:\nTask sys_ids which contains GDPR data.\n\nUsage:\nIn scripted reports, in script includes,etc.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Get-task-containing-sensitive-data/script.js",
    "content": "        var queryString;\n        var getPropertyValue = new GlideRecord('sys_properties');\n        getPropertyValue.addQuery('name', '<name of your property which contains parameters of sensitive data mainnly GDPR>'); \"Example : BSN,Burgerservicenummer,voornaam,achternaam,geslacht,gender,Geboortedatum,Birth,adres,woonplaats,straatnaam,huisnummer,postcode,telefoonnummer,mobiel,hypotheeknummer,IBAN,Rekeningnummer,Rekeningnr,Rek. nr.,Verzekeringsnummer,verzekeringsnr,wachtwoord,gebruikersnaam,username,password,pwd\"\n        getPropertyValue.query();\n        if (getPropertyValue.next()) {\n            queryString = getPropertyValue.value.toString();\n        }\n\n        var queryStringArray = queryString.split(',');\n        var arrayTasks = [];\n\n        var query = '';\n        for (var i = 0; i < queryStringArray.length; i++) {\n            if (i == 0) {\n                query = 'short_descriptionLIKE' + queryStringArray[0] + '^ORwork_notesLIKE' + queryStringArray[0] + '^ORdescriptionLIKE' + queryStringArray[0];\n            } else {\n                query = query + '^ORshort_descriptionLIKE' + queryStringArray[i] + '^ORwork_notesLIKE' + queryStringArray[i] + '^ORdescriptionLIKE' + queryStringArray[i];\n            }\n        }\n\n        grTask = new GlideRecord('task');\n        grTask.addEncodedQuery(query);\n        grTask.query();\n        while (grTask.next()) {\n            arrayTasks.push(grTask.sys_id.toString());\n        }\n\n        return arrayTasks;\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Gets the display value according to the specified language/README.md",
    "content": "Get the display value according to the specified language.\n\n(Install Language Plugin)\n\n```javascript\nvar gr = new GlideRecord(\"incident\");\ngr.setLimit(1);\ngr.query();\ngr.next();\nvar user = gs.getUser();\nvar lang = user.getPreference(\"user.language\");\n\n// Japanese\nuser.setPreference(\"user.language\", 'ja');\nvar outputJA = '' + gr.state.getLabel() + ' = ' + gr.state.getDisplayValue();\n// English\nuser.setPreference(\"user.language\", 'en');\nvar outputEN = '' + gr.state.getLabel() + ' = ' + gr.state.getDisplayValue();\ngs.info(outputJA + ' / ' + outputEN);\n\nuser.setPreference(\"user.language\", lang);\n```\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Gets the display value according to the specified language/specified_language.js",
    "content": "//Get the display value according to the specified language.\n//(Install Language Plugin)\nvar gr = new GlideRecord(\"incident\");\ngr.setLimit(1);\ngr.query();\ngr.next();\nvar user = gs.getUser();\nvar lang = user.getPreference(\"user.language\");\n\n// Japanese\nuser.setPreference(\"user.language\", 'ja');\nvar outputJA = '' + gr.state.getLabel() + ' = ' + gr.state.getDisplayValue();\n// English\nuser.setPreference(\"user.language\", 'en');\nvar outputEN = '' + gr.state.getLabel() + ' = ' + gr.state.getDisplayValue();\ngs.info(outputJA + ' / ' + outputEN);\n\nuser.setPreference(\"user.language\", lang);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/GlideRecord to Object/README.md",
    "content": "This script takes in a GlideRecord object and returns an object. Use the following background script to test the function.\n\nfunction _grToObject(recordToPackage) {\n  var packageToSend = {};\n  for (var property in recordToPackage) {\n      try {\n          packageToSend[property] = recordToPackage[property].getDisplayValue();\n      } catch (err) {}\n  }\n  return packageToSend;\n}\n\nvar incSysID = ''; // Update w/ Incident sysId in target instance\n\nvar grInc = new GlideRecord('incident');\nif(grInc.get(incSysID)){\n    gs.info(JSON.stringify(_grToObject(grInc)));\n} else {\n    gs.info('Invalid Incident sysId');\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/GlideRecord to Object/_grToObject.js",
    "content": "/**\n *  Transforms a GlideRecord object to an object\n *  @return {object} packageToSend - The transformed object\n *  @param {object} recordToPackage - A GlideRecord object\n **/\nfunction _grToObject(recordToPackage) {\n  var packageToSend = {};\n  for (var property in recordToPackage) {\n      try {\n          packageToSend[property] = recordToPackage[property].getDisplayValue();\n      } catch (err) {}\n  }\n  return packageToSend;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/GlideRecord with Performance Enhancement Condtions/README.md",
    "content": "A sample GlideRecord code with conditions to enhance performance.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/GlideRecord with Performance Enhancement Condtions/script.js",
    "content": "var TableGr = new GlideRecord('Table Name');\nTableGr.addQuery('due_date', '')// this will be useful if we are making batches, run it only where due date is blank.\nTableGr.setWorkflow('false'); // to ensure no BRs trigger for this update, will increase performance also.\nTableGr.autoSysFields(false); //if you dont want system fields to be update, ie. sys_updated_on, sys_updated_by etc\nTableGr.setLimit(1000); // its better to do it in batches to ensure the query doesnt time out or break the system\nTableGr.query();\nwhile(gr.next()){\n  TableGr.update();\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/LEFT Join/README.md",
    "content": "# LEFT Join\n\nThis script can be used if you want to filter the results of a table based on values from a related table.\n\nThis is very useful in before query Business Rules. You can use this to filter the results based on a related table\n\n### Examples\n\n1. Users that like Tennis and Movies\n2. Users called Patrik or that like Movies\n3. Incidents with Priority 1 - Critical or SLA In Progress\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/LEFT Join/example.js",
    "content": "/*\nTable A - Users(u_user)                 Table B - Likes(u_likes)\nID(sys_id):     Name(u_name):           User(u_user):   Activity(u_activity):\n...b65a         Patrik                  Maria           Movies\n...b6ba         Albert                  Patrik          Climbing\n...b6df         Maria                   Patrik          Code\n...b6c9         Darwin                  (empty)         Rugby\n...b662         Elizabeth               Darwin          Tennis\n                                        Maria           Tennis\n        \n                                    \nDatabase View\n    Name: u_user_left_join_likes\n    Label: User LEFT JOIN Likes\n    View Tables:\n        - 1\n            Table: u_user\n            Order: 100\n            Variable prefix: usr\n            Where clause: \n            Left join: false\n        - 2\n            Table: u_likes\n            Order: 200\n            Variable prefix: lks\n            Where clause: usr_sys_id=lks_u_user\n            Left join: true\n*/\n\n//Example 1 - Users that like Tennis and Movies\nvar grTableA = new GlideRecord('u_user');\nvar grJoin = grTableA.addJoinQuery('u_user_left_join_likes','sys_id','usr_sys_id');\ngrJoin.addCondition('lks_u_activity','Tennis');\ngrJoin.addOrCondition('lks_u_activity','Movies');\ngrTableA.query();\nwhile(grTableA.next()) {\n\tgs.info('Name: ' + grTableA.getValue('u_name'));\n}\n\n/*\nOutput:\n\nName: Maria\nName: Darwin\n*/\n\n//Example 2 - Users called Patrik or that like Movies\nvar grTableA = new GlideRecord('u_user');\nvar grJoin = grTableA.addJoinQuery('u_user_left_join_likes','sys_id','usr_sys_id');\ngrJoin.addCondition('lks_u_activity','Movies');\ngrJoin.addOrCondition('usr_u_name','Patrik');\ngrTableA.query();\nwhile(grTableA.next()) {\n\tgs.info('Name: ' + grTableA.getValue('u_name'));\n}\n\n/*\nOutput:\n\nName: Patrik\nName: Maria\n*/\n\n//Example 3 - Incidents with Priority 1 - Critical or SLA In Progress\nvar grIncident = new GlideRecord('incident');\nvar grJoin = grIncident.addJoinQuery('incident_sla','sys_id','inc_sys_id');\ngrJoin.addCondition('taskslatable_stage','in_progress');\ngrJoin.addOrCondition('inc_priority','1');\ngrIncident.query();\nwhile(grIncident.next()) {\n\tgs.info('Number: ' + grIncident.getValue('number'));\n}\n\n/*\nNote this wont work on the OOB incident_sla database view. You need to create a new one or make the following modifications:\n\nTable Views:\n    - 1\n        Table: incident\n        Order: 100\n        Variable prefix: inc\n        Where clause: \n        Left join: false\n    - 2\n        Table: task_sla\n        Order: 200\n        Variable prefix: taskslatable\n        Where clause: inc_sys_id=taskslatable_task\n        Left join: true\n*/"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/LEFT Join/script.js",
    "content": "/*\nFilter result from Table A based on conditions of a related table (Table B)\n\nIMPORTANT: Need a Database view to get this to work\n*/\n\nvar grTableA = new GlideRecord('<Table A>');\nvar grJoin = grTableA.addJoinQuery('<Database View>','<Table A ID>','<Database View Table A prefix plus ID>');\ngrJoin.addCondition('<Database View table prefix plus field>','<value>')\ngrTableA.query();\nwhile(grTableA.next()) {\n\tgs.info('Number: ' + grTableA.getValue('<Table A field>'));\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/List of Child Incidents/README.md",
    "content": "This code will help to show list of Incidents whih are child of a particular incident.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/List of Child Incidents/listofchildIncident.js",
    "content": "var parentID = '9c573169c611228700193229fff72400'; // This should be stored in sys_property instead of hardcoding the sys_id. \nvar inc = new GlideRecord('incident');\ninc.addEncodedQuery('parent_incident='+parentID); // sys_id of parent Incident\ninc.query();\n\nif(inc.next()){\ngs.info('List of Child Incidents of:'+inc.getDisplayValue('parent_incident'));\ngs.info(inc.getValue('number'));\n}\n\nwhile(inc.next())\n{\n\tgs.info(inc.getValue('number'));\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Multi Row Variable Set(MRVS)/InsertMRVSRecords.js",
    "content": "// Record Producer script to insert MRVS rows as records in the desired table:\n\n\nvar Ite = JSON.parse(producer.itinerary); // Enter MRVS NAME as producer.mrvs_name;\n    if (Ite.length > 0) {\n        for (j = 0; j < Ite.length; j++) {\n            try {\n                var gr_itr = new GlideRecord('itinerary_Table'); // Glide the required table \n\n                gr_itr.addQuery('u_number', producer.itinerary.getRow(j).itinerary_number);\n                gr_itr.query();\n                if (gr_itr.next()) {\n                    // Update Functionality here\n                } else {\n\n                    gr_itr.initialize();\n                    // Lets assume we have these variables \n                    gr_itr.setValue('u_mode_of_transport', producer.itinerary.getRow(j).mode_of_transport); //Write actual variable names: producer.mrvs_name.variable_Name.\n                    gr_itr.setValue('u_flightnumber', producer.itinerary.getRow(j).FlightNumber);\n                    gr_itr.setValue('u_origin_1', producer.itinerary.getRow(j).origin_1);\n                    gr_itr.setValue('u_origin_2', producer.itinerary.getRow(j).origin_2);\n                    gr_itr.setValue('u_arrivaldatetime', producer.itinerary.getRow(j).Adt);\n                    gr_itr.setValue('u_class', producer.itinerary.getRow(j).tr_class);\n                    gr_itr.setValue('u_carrier', producer.itinerary.getRow(j).Carrier);\n                    //gr_itr.setValue('u_status', producer.itinerary.getRow(j).date_To);\n                    gr_itr.setValue('u_destination_1', producer.itinerary.getRow(j).destination_1);\n                    gr_itr.setValue('u_destination_2', producer.itinerary.getRow(j).destination_2);\n                    gr_itr.setValue('u_missiondestination', producer.itinerary.getRow(j).mission_destination);\n                    gr_itr.setValue('u_departuredatetime', producer.itinerary.getRow(j).DepartureDatetime);\n\t\t\t\t\tgr_itr.setValue('u_no_match_found_origin', producer.itinerary.getRow(j).no_match_found_origin);\n\t\t\t\t\tgr_itr.setValue('u_no_match_found_dest', producer.itinerary.getRow(j).no_match_found_dest);\n\t\t\t\t\tgr_itr.setValue('u_origin_missing_city', producer.itinerary.getRow(j).OriginMissingCity);\n\t\t\t\t\tgr_itr.setValue('u_destination_missing_city', producer.itinerary.getRow(j).DestinationMissingCity);\n\n                    gr_itr.setValue('u_official', producer.itinerary.getRow(j).official);\n\n                    gr_itr.setValue('u_travelrequest', current.sys_id);\n\n                    gr_itr.insert(); // At Last simply insert the records.\n                }\n\n            } catch (e) {\n                   gs.addErrorMessage('Error in Itr' + e);\n            }\n        }\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Multi Row Variable Set(MRVS)/README.md",
    "content": "This piece of code will let you insert MRVS Records without quering the Question Answers out of the box table!\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Performance Optimization Techniques/README.md",
    "content": "# GlideRecord Performance Optimization Techniques\n\nThis collection provides advanced techniques for optimizing GlideRecord queries and database operations in ServiceNow.\n\n## Overview\n\nPerformance optimization is crucial for maintaining responsive ServiceNow applications, especially when dealing with large datasets or complex queries. These snippets demonstrate best practices for efficient GlideRecord usage.\n\n## Key Performance Principles\n\n- **Minimize Database Roundtrips**: Use efficient query patterns\n- **Proper Indexing**: Leverage indexed fields in queries\n- **Batch Operations**: Process multiple records efficiently\n- **Query Optimization**: Use appropriate query methods\n- **Memory Management**: Handle large datasets responsibly\n\n## Snippets Included\n\n1. **optimized_batch_processing.js** - Efficient batch processing techniques\n2. **indexed_field_queries.js** - Leveraging database indexes\n3. **chunked_data_processing.js** - Processing large datasets in chunks\n4. **query_performance_comparison.js** - Performance comparison examples\n5. **memory_efficient_operations.js** - Memory-conscious GlideRecord usage\n\n## Performance Monitoring\n\nAlways measure performance using:\n- `gs.log()` with timestamps for execution time\n- Database query metrics in Developer Tools\n- Performance Analytics for production monitoring\n\n## Best Practices Summary\n\n1. Always query on indexed fields when possible\n2. Use `setLimit()` for large result sets\n3. Avoid using `getRowCount()` on large tables\n4. Use `chooseWindow()` for pagination\n5. Consider using `GlideAggregate` for statistical queries\n6. Implement proper error handling and logging\n\n## Related Documentation\n\n- [ServiceNow GlideRecord API Documentation](https://developer.servicenow.com/dev.do#!/reference/api/tokyo/server/no-namespace/c_GlideRecordScopedAPI)\n- [Query Performance Best Practices](https://docs.servicenow.com/bundle/tokyo-platform-administration/page/administer/managing-data/concept/query-performance.html)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Performance Optimization Techniques/indexed_field_queries.js",
    "content": "/**\n * Indexed Field Query Optimization\n * \n * This snippet demonstrates how to leverage database indexes for optimal query performance\n * in ServiceNow GlideRecord operations.\n * \n * Use Case: High-performance queries on large tables\n * Performance Benefits: Faster query execution, reduced database load\n * \n * @author ServiceNow Community\n * @version 1.0\n */\n\n// Method 1: Using Indexed Fields for Optimal Performance\nfunction queryWithIndexedFields() {\n    var gr = new GlideRecord('incident');\n    \n    // GOOD: Query on indexed fields (state, priority, assignment_group are typically indexed)\n    gr.addQuery('state', 'IN', '2,3'); // Work in Progress, On Hold\n    gr.addQuery('priority', '<=', '3'); // High priority and above\n    gr.addQuery('assignment_group', '!=', ''); // Has assignment group\n    \n    // GOOD: Use sys_created_on for date ranges (indexed timestamp)\n    gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(7));\n    gr.addQuery('sys_created_on', '<=', gs.daysAgoEnd(1));\n    \n    // GOOD: Order by indexed field\n    gr.orderBy('sys_created_on');\n    \n    gr.query();\n    \n    var results = [];\n    while (gr.next()) {\n        results.push({\n            number: gr.getDisplayValue('number'),\n            state: gr.getDisplayValue('state'),\n            priority: gr.getDisplayValue('priority')\n        });\n    }\n    \n    return results;\n}\n\n// Method 2: Avoiding Non-Indexed Field Queries\nfunction avoidNonIndexedQueries() {\n    // BAD EXAMPLE - Don't do this on large tables\n    function badQueryExample() {\n        var gr = new GlideRecord('incident');\n        \n        // BAD: Contains query on non-indexed text field\n        gr.addQuery('description', 'CONTAINS', 'network issue');\n        \n        // BAD: Complex wildcard search\n        gr.addQuery('short_description', 'STARTSWITH', 'Unable');\n        \n        gr.query();\n        return gr.getRowCount(); // Also bad on large tables\n    }\n    \n    // GOOD ALTERNATIVE - Use indexed fields and full-text search\n    function goodQueryAlternative() {\n        var gr = new GlideRecord('incident');\n        \n        // GOOD: Use category/subcategory (often indexed)\n        gr.addQuery('category', 'network');\n        \n        // GOOD: Use indexed fields first to narrow results\n        gr.addQuery('state', 'IN', '1,2,3');\n        gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(30));\n        \n        // Then filter on text if needed (smaller result set)\n        gr.addQuery('short_description', 'CONTAINS', 'unable');\n        \n        gr.query();\n        \n        var results = [];\n        while (gr.next()) {\n            results.push(gr.getUniqueValue());\n        }\n        \n        return results;\n    }\n    \n    return goodQueryAlternative();\n}\n\n// Method 3: Compound Index Optimization\nfunction optimizeCompoundIndexes() {\n    var gr = new GlideRecord('task');\n    \n    // GOOD: Query fields in order that matches compound indexes\n    // Many tables have compound indexes on (table, state, assigned_to)\n    gr.addQuery('state', '!=', '7'); // Not closed\n    gr.addQuery('assigned_to', '!=', ''); // Has assignee\n    \n    // Add additional filters after indexed ones\n    gr.addQuery('priority', '<=', '3');\n    \n    // Order by indexed field for better performance\n    gr.orderBy('sys_updated_on');\n    \n    gr.query();\n    \n    return gr.getRowCount();\n}\n\n// Method 4: Reference Field Optimization\nfunction optimizeReferenceQueries() {\n    // GOOD: Query reference fields by sys_id (indexed)\n    function queryByReferenceId() {\n        var groupSysId = getAssignmentGroupSysId('Hardware');\n        \n        var gr = new GlideRecord('incident');\n        gr.addQuery('assignment_group', groupSysId); // Uses index\n        gr.addQuery('state', '!=', '7'); // Not closed\n        gr.query();\n        \n        return gr.getRowCount();\n    }\n    \n    // LESS OPTIMAL: Query by reference field display value\n    function queryByDisplayValue() {\n        var gr = new GlideRecord('incident');\n        gr.addQuery('assignment_group.name', 'Hardware'); // Less efficient\n        gr.addQuery('state', '!=', '7');\n        gr.query();\n        \n        return gr.getRowCount();\n    }\n    \n    // BEST: Combine both approaches\n    function optimizedReferenceQuery() {\n        // First get the sys_id using indexed query\n        var groupGR = new GlideRecord('sys_user_group');\n        groupGR.addQuery('name', 'Hardware');\n        groupGR.query();\n        \n        if (groupGR.next()) {\n            var groupSysId = groupGR.getUniqueValue();\n            \n            // Then use sys_id in main query (indexed)\n            var gr = new GlideRecord('incident');\n            gr.addQuery('assignment_group', groupSysId);\n            gr.addQuery('state', '!=', '7');\n            gr.query();\n            \n            return gr.getRowCount();\n        }\n        \n        return 0;\n    }\n    \n    return optimizedReferenceQuery();\n}\n\n// Method 5: Date Range Optimization\nfunction optimizeDateRangeQueries() {\n    // GOOD: Use built-in date functions with indexed timestamps\n    function efficientDateQuery() {\n        var gr = new GlideRecord('incident');\n        \n        // Use sys_created_on (indexed) with built-in functions\n        gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(30));\n        gr.addQuery('sys_created_on', '<=', gs.daysAgoEnd(1));\n        \n        // Add other indexed filters\n        gr.addQuery('state', '!=', '7');\n        \n        gr.query();\n        return gr.getRowCount();\n    }\n    \n    // LESS OPTIMAL: Complex date calculations\n    function lessOptimalDateQuery() {\n        var gr = new GlideRecord('incident');\n        \n        // Complex date calculation (harder to optimize)\n        var thirtyDaysAgo = new GlideDateTime();\n        thirtyDaysAgo.addDaysUTC(-30);\n        \n        gr.addQuery('sys_created_on', '>=', thirtyDaysAgo);\n        gr.query();\n        \n        return gr.getRowCount();\n    }\n    \n    return efficientDateQuery();\n}\n\n// Method 6: Query Performance Analysis\nfunction analyzeQueryPerformance() {\n    var queries = [\n        {\n            name: 'Indexed Query',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', '2');\n                gr.addQuery('priority', '1');\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'Non-Indexed Query',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('description', 'CONTAINS', 'test');\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        }\n    ];\n    \n    queries.forEach(function(queryTest) {\n        var startTime = new Date().getTime();\n        var result = queryTest.query();\n        var endTime = new Date().getTime();\n        var executionTime = endTime - startTime;\n        \n        gs.log(queryTest.name + ':');\n        gs.log('  Execution time: ' + executionTime + 'ms');\n        gs.log('  Result count: ' + result);\n        gs.log('  Performance rating: ' + (executionTime < 100 ? 'Good' : executionTime < 500 ? 'Fair' : 'Poor'));\n    });\n}\n\n// Helper function\nfunction getAssignmentGroupSysId(groupName) {\n    var gr = new GlideRecord('sys_user_group');\n    gr.addQuery('name', groupName);\n    gr.query();\n    \n    if (gr.next()) {\n        return gr.getUniqueValue();\n    }\n    \n    return '';\n}\n\n// Method 7: Index-Aware Pagination\nfunction indexAwarePagination(pageSize, pageNumber) {\n    pageSize = pageSize || 50;\n    pageNumber = pageNumber || 0;\n    \n    var gr = new GlideRecord('incident');\n    \n    // Use indexed fields for filtering\n    gr.addQuery('state', 'IN', '1,2,3');\n    gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(90));\n    \n    // Order by indexed field for consistent pagination\n    gr.orderBy('sys_created_on');\n    gr.orderByDesc('sys_id'); // Secondary sort for tie-breaking\n    \n    // Use chooseWindow for efficient pagination\n    gr.chooseWindow(pageNumber * pageSize, (pageNumber + 1) * pageSize);\n    \n    gr.query();\n    \n    var results = [];\n    while (gr.next()) {\n        results.push({\n            sys_id: gr.getUniqueValue(),\n            number: gr.getDisplayValue('number'),\n            short_description: gr.getDisplayValue('short_description'),\n            state: gr.getDisplayValue('state')\n        });\n    }\n    \n    return {\n        data: results,\n        page: pageNumber,\n        pageSize: pageSize,\n        hasMore: results.length === pageSize\n    };\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Performance Optimization Techniques/optimized_batch_processing.js",
    "content": "/**\n * Optimized Batch Processing with GlideRecord\n * \n * This snippet demonstrates efficient techniques for processing large numbers of records\n * while maintaining good performance and avoiding timeout issues.\n * \n * Use Case: Bulk updates, data migration, or mass record processing\n * Performance Benefits: Reduced memory usage, better transaction management, timeout prevention\n * \n * @author ServiceNow Community\n * @version 1.0\n */\n\n// Method 1: Chunked Processing with Limit\nfunction processRecordsInChunks() {\n    var tableName = 'incident';\n    var chunkSize = 500; // Adjust based on your needs and system performance\n    var processedCount = 0;\n    var totalProcessed = 0;\n    \n    // Log start time for performance monitoring\n    var startTime = new Date().getTime();\n    gs.log('Starting batch processing at: ' + new Date());\n    \n    do {\n        var gr = new GlideRecord(tableName);\n        \n        // Use indexed fields for better performance\n        gr.addQuery('state', 'IN', '1,2,3'); // Open states\n        gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(30)); // Last 30 days\n        \n        // Set limit for this chunk\n        gr.setLimit(chunkSize);\n        gr.orderBy('sys_created_on'); // Consistent ordering for pagination\n        \n        // Skip already processed records\n        gr.chooseWindow(totalProcessed, totalProcessed + chunkSize);\n        \n        gr.query();\n        \n        processedCount = 0;\n        \n        while (gr.next()) {\n            try {\n                // Your processing logic here\n                updateIncidentPriority(gr);\n                processedCount++;\n                totalProcessed++;\n                \n                // Log progress every 100 records\n                if (processedCount % 100 === 0) {\n                    gs.log('Processed ' + totalProcessed + ' records so far...');\n                }\n                \n            } catch (error) {\n                gs.error('Error processing record ' + gr.getUniqueValue() + ': ' + error.message);\n                // Continue processing other records\n            }\n        }\n        \n        // Yield execution to prevent timeout (if in scheduled job)\n        gs.sleep(100); // Brief pause between chunks\n        \n    } while (processedCount === chunkSize); // Continue if we got a full chunk\n    \n    // Log completion statistics\n    var endTime = new Date().getTime();\n    var executionTime = (endTime - startTime) / 1000;\n    \n    gs.log('Batch processing completed:');\n    gs.log('- Total records processed: ' + totalProcessed);\n    gs.log('- Execution time: ' + executionTime + ' seconds');\n    gs.log('- Average records per second: ' + (totalProcessed / executionTime).toFixed(2));\n}\n\n// Method 2: Optimized Bulk Update with Batch Commits\nfunction optimizedBulkUpdate() {\n    var gr = new GlideRecord('task');\n    \n    // Use compound query with indexed fields\n    gr.addQuery('state', 'IN', '1,2');\n    gr.addQuery('priority', '4');\n    gr.addQuery('sys_updated_on', '<', gs.daysAgoStart(7));\n    \n    // Set reasonable limit to prevent memory issues\n    gr.setLimit(1000);\n    gr.query();\n    \n    var updateCount = 0;\n    var batchSize = 50;\n    \n    // Start transaction for batch processing\n    var transaction = new GlideTransaction();\n    \n    try {\n        while (gr.next()) {\n            // Update the record\n            gr.priority = '3'; // Increase priority\n            gr.comments = 'Priority auto-updated due to age';\n            gr.update();\n            \n            updateCount++;\n            \n            // Commit in batches to manage transaction size\n            if (updateCount % batchSize === 0) {\n                transaction.commit();\n                transaction = new GlideTransaction(); // Start new transaction\n                gs.log('Committed batch of ' + batchSize + ' updates. Total: ' + updateCount);\n            }\n        }\n        \n        // Commit remaining records\n        if (updateCount % batchSize !== 0) {\n            transaction.commit();\n        }\n        \n        gs.log('Bulk update completed. Total records updated: ' + updateCount);\n        \n    } catch (error) {\n        transaction.rollback();\n        gs.error('Bulk update failed and rolled back: ' + error.message);\n        throw error;\n    }\n}\n\n// Method 3: Memory-Efficient Large Dataset Processing\nfunction processLargeDatasetEfficiently() {\n    var tableName = 'cmdb_ci';\n    var processedTotal = 0;\n    var hasMoreRecords = true;\n    var lastSysId = '';\n    \n    while (hasMoreRecords) {\n        var gr = new GlideRecord(tableName);\n        \n        // Use sys_id for cursor-based pagination (most efficient)\n        if (lastSysId) {\n            gr.addQuery('sys_id', '>', lastSysId);\n        }\n        \n        // Add your business logic filters\n        gr.addQuery('install_status', 'IN', '1,6'); // Installed or Reserved\n        \n        gr.orderBy('sys_id'); // Consistent ordering\n        gr.setLimit(200); // Smaller chunks for large tables\n        gr.query();\n        \n        var currentBatchCount = 0;\n        \n        while (gr.next()) {\n            try {\n                // Your processing logic\n                processConfigurationItem(gr);\n                \n                currentBatchCount++;\n                processedTotal++;\n                lastSysId = gr.getUniqueValue();\n                \n            } catch (error) {\n                gs.error('Error processing CI ' + gr.getUniqueValue() + ': ' + error.message);\n            }\n        }\n        \n        // Check if we have more records to process\n        hasMoreRecords = (currentBatchCount === 200);\n        \n        gs.log('Processed batch: ' + currentBatchCount + ' records. Total: ' + processedTotal);\n        \n        // Small delay to prevent overwhelming the system\n        gs.sleep(50);\n    }\n    \n    gs.log('Large dataset processing completed. Total records: ' + processedTotal);\n}\n\n// Helper function example\nfunction updateIncidentPriority(incidentGR) {\n    // Example business logic\n    if (incidentGR.getValue('business_impact') == '1' && incidentGR.getValue('urgency') == '1') {\n        incidentGR.priority = '1'; // Critical\n        incidentGR.update();\n    }\n}\n\nfunction processConfigurationItem(ciGR) {\n    // Example CI processing logic\n    ciGR.last_discovered = new GlideDateTime();\n    ciGR.update();\n}\n\n// Method 4: Performance Monitoring Wrapper\nfunction monitoredBatchOperation(operationName, operationFunction) {\n    var startTime = new Date().getTime();\n    var memoryBefore = gs.getProperty('glide.script.heap.size', 'Unknown');\n    \n    gs.log('Starting operation: ' + operationName);\n    gs.log('Memory before: ' + memoryBefore);\n    \n    try {\n        var result = operationFunction();\n        \n        var endTime = new Date().getTime();\n        var executionTime = (endTime - startTime) / 1000;\n        var memoryAfter = gs.getProperty('glide.script.heap.size', 'Unknown');\n        \n        gs.log('Operation completed: ' + operationName);\n        gs.log('Execution time: ' + executionTime + ' seconds');\n        gs.log('Memory after: ' + memoryAfter);\n        \n        return result;\n        \n    } catch (error) {\n        var endTime = new Date().getTime();\n        var executionTime = (endTime - startTime) / 1000;\n        \n        gs.error('Operation failed: ' + operationName);\n        gs.error('Execution time before failure: ' + executionTime + ' seconds');\n        gs.error('Error details: ' + error.message);\n        \n        throw error;\n    }\n}\n\n// Example usage of performance monitoring\nfunction exampleMonitoredOperation() {\n    monitoredBatchOperation('Incident Priority Update', function() {\n        processRecordsInChunks();\n        return 'Success';\n    });\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Performance Optimization Techniques/query_performance_comparison.js",
    "content": "/**\n * Query Performance Comparison and Analysis\n * \n * This snippet provides tools to compare different query approaches and measure their performance\n * to help developers choose the most efficient methods.\n * \n * Use Case: Performance testing, query optimization, development best practices\n * Performance Benefits: Data-driven optimization decisions, performance monitoring\n * \n * @author ServiceNow Community\n * @version 1.0\n */\n\n// Performance Testing Framework\nvar QueryPerformanceTester = Class.create();\nQueryPerformanceTester.prototype = {\n    \n    initialize: function(tableName) {\n        this.tableName = tableName || 'incident';\n        this.results = [];\n    },\n    \n    // Method to test different query approaches\n    testQuery: function(testName, queryFunction, iterations) {\n        iterations = iterations || 1;\n        var times = [];\n        var results = [];\n        \n        gs.log('Starting performance test: ' + testName);\n        \n        for (var i = 0; i < iterations; i++) {\n            var startTime = new Date().getTime();\n            \n            try {\n                var result = queryFunction();\n                var endTime = new Date().getTime();\n                var executionTime = endTime - startTime;\n                \n                times.push(executionTime);\n                results.push(result);\n                \n            } catch (error) {\n                gs.error('Error in test \"' + testName + '\", iteration ' + (i + 1) + ': ' + error.message);\n                return null;\n            }\n        }\n        \n        var avgTime = times.reduce(function(a, b) { return a + b; }) / times.length;\n        var minTime = Math.min.apply(null, times);\n        var maxTime = Math.max.apply(null, times);\n        \n        var testResult = {\n            name: testName,\n            iterations: iterations,\n            averageTime: avgTime,\n            minimumTime: minTime,\n            maximumTime: maxTime,\n            resultCount: results[0] || 0,\n            allTimes: times\n        };\n        \n        this.results.push(testResult);\n        \n        gs.log('Test completed: ' + testName);\n        gs.log('Average time: ' + avgTime + 'ms');\n        gs.log('Result count: ' + (results[0] || 0));\n        \n        return testResult;\n    },\n    \n    // Compare multiple query approaches\n    compareQueries: function(queryTests, iterations) {\n        iterations = iterations || 3;\n        \n        gs.log('Starting query performance comparison with ' + iterations + ' iterations each');\n        \n        var self = this;\n        queryTests.forEach(function(test) {\n            self.testQuery(test.name, test.query, iterations);\n        });\n        \n        this.printComparison();\n        return this.results;\n    },\n    \n    // Print comparison results\n    printComparison: function() {\n        if (this.results.length === 0) {\n            gs.log('No test results to compare');\n            return;\n        }\n        \n        gs.log('\\n=== Query Performance Comparison Results ===');\n        \n        // Sort by average time\n        var sortedResults = this.results.slice().sort(function(a, b) {\n            return a.averageTime - b.averageTime;\n        });\n        \n        sortedResults.forEach(function(result, index) {\n            var rank = index + 1;\n            var performance = rank === 1 ? 'BEST' : rank === sortedResults.length ? 'WORST' : 'GOOD';\n            \n            gs.log('\\nRank #' + rank + ' (' + performance + '): ' + result.name);\n            gs.log('  Average Time: ' + result.averageTime.toFixed(2) + 'ms');\n            gs.log('  Min/Max Time: ' + result.minimumTime + 'ms / ' + result.maximumTime + 'ms');\n            gs.log('  Result Count: ' + result.resultCount);\n            \n            if (index > 0) {\n                var percentSlower = ((result.averageTime - sortedResults[0].averageTime) / sortedResults[0].averageTime * 100);\n                gs.log('  Performance: ' + percentSlower.toFixed(1) + '% slower than best');\n            }\n        });\n        \n        gs.log('\\n=== Recommendations ===');\n        gs.log('Use \"' + sortedResults[0].name + '\" for best performance');\n        if (sortedResults.length > 1) {\n            gs.log('Avoid \"' + sortedResults[sortedResults.length - 1].name + '\" if possible');\n        }\n    }\n};\n\n// Example 1: Comparing Different Query Approaches\nfunction compareBasicQueryMethods() {\n    var tester = new QueryPerformanceTester('incident');\n    \n    var queryTests = [\n        {\n            name: 'Indexed Field Query (Optimal)',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', '2'); // Work in Progress (indexed)\n                gr.addQuery('priority', '1'); // Critical (indexed)\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'Multiple OR Conditions',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', 'IN', '1,2,3'); // Multiple states\n                gr.addQuery('priority', 'IN', '1,2'); // High priorities\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'Text Search (Non-Indexed)',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('short_description', 'CONTAINS', 'network'); // Text search\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'Reference Field Display Value',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('caller_id.name', 'CONTAINS', 'John'); // Reference display value\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        }\n    ];\n    \n    return tester.compareQueries(queryTests, 3);\n}\n\n// Example 2: Date Query Performance Comparison\nfunction compareDateQueryMethods() {\n    var tester = new QueryPerformanceTester('incident');\n    \n    var queryTests = [\n        {\n            name: 'Built-in Date Functions (Optimal)',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(30));\n                gr.addQuery('sys_created_on', '<=', gs.daysAgoEnd(1));\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'GlideDateTime Calculations',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                var thirtyDaysAgo = new GlideDateTime();\n                thirtyDaysAgo.addDaysUTC(-30);\n                var oneDayAgo = new GlideDateTime();\n                oneDayAgo.addDaysUTC(-1);\n                \n                gr.addQuery('sys_created_on', '>=', thirtyDaysAgo);\n                gr.addQuery('sys_created_on', '<=', oneDayAgo);\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'String Date Comparison',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                var today = new Date();\n                var thirtyDaysAgo = new Date(today.getTime() - (30 * 24 * 60 * 60 * 1000));\n                \n                gr.addQuery('sys_created_on', '>=', thirtyDaysAgo.toISOString());\n                gr.setLimit(100);\n                gr.query();\n                return gr.getRowCount();\n            }\n        }\n    ];\n    \n    return tester.compareQueries(queryTests, 5);\n}\n\n// Example 3: Pagination Method Comparison\nfunction comparePaginationMethods() {\n    var tester = new QueryPerformanceTester('incident');\n    var pageSize = 50;\n    var pageNumber = 2;\n    \n    var queryTests = [\n        {\n            name: 'chooseWindow() Method (Optimal)',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', '!=', '7');\n                gr.orderBy('sys_created_on');\n                gr.chooseWindow(pageNumber * pageSize, (pageNumber + 1) * pageSize);\n                gr.query();\n                \n                var count = 0;\n                while (gr.next()) { count++; }\n                return count;\n            }\n        },\n        {\n            name: 'setLimit() with Offset Simulation',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', '!=', '7');\n                gr.orderBy('sys_created_on');\n                gr.setLimit(pageSize * (pageNumber + 1));\n                gr.query();\n                \n                var count = 0;\n                var skip = pageSize * pageNumber;\n                while (gr.next()) {\n                    if (skip > 0) {\n                        skip--;\n                        continue;\n                    }\n                    count++;\n                }\n                return count;\n            }\n        }\n    ];\n    \n    return tester.compareQueries(queryTests, 3);\n}\n\n// Example 4: Aggregate vs Manual Counting\nfunction compareCountingMethods() {\n    var tester = new QueryPerformanceTester('incident');\n    \n    var queryTests = [\n        {\n            name: 'GlideAggregate COUNT (Optimal)',\n            query: function() {\n                var ga = new GlideAggregate('incident');\n                ga.addQuery('state', '!=', '7');\n                ga.addAggregate('COUNT');\n                ga.query();\n                \n                if (ga.next()) {\n                    return parseInt(ga.getAggregate('COUNT'));\n                }\n                return 0;\n            }\n        },\n        {\n            name: 'GlideRecord getRowCount()',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', '!=', '7');\n                gr.query();\n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'Manual Counting with Loop',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                gr.addQuery('state', '!=', '7');\n                gr.query();\n                \n                var count = 0;\n                while (gr.next()) { count++; }\n                return count;\n            }\n        }\n    ];\n    \n    return tester.compareQueries(queryTests, 3);\n}\n\n// Example 5: Complex Query Optimization\nfunction compareComplexQueries() {\n    var tester = new QueryPerformanceTester('incident');\n    \n    var queryTests = [\n        {\n            name: 'Optimized Complex Query',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                \n                // Start with most selective indexed fields\n                gr.addQuery('state', 'IN', '1,2,3');\n                gr.addQuery('priority', '<=', '3');\n                gr.addQuery('sys_created_on', '>=', gs.daysAgoStart(30));\n                \n                // Add less selective filters after\n                gr.addQuery('assignment_group', '!=', '');\n                \n                gr.orderBy('sys_created_on');\n                gr.setLimit(100);\n                gr.query();\n                \n                return gr.getRowCount();\n            }\n        },\n        {\n            name: 'Non-Optimized Complex Query',\n            query: function() {\n                var gr = new GlideRecord('incident');\n                \n                // Start with less selective fields\n                gr.addQuery('short_description', 'CONTAINS', 'issue');\n                gr.addQuery('assignment_group', '!=', '');\n                gr.addQuery('state', 'IN', '1,2,3');\n                gr.addQuery('priority', '<=', '3');\n                \n                gr.setLimit(100);\n                gr.query();\n                \n                return gr.getRowCount();\n            }\n        }\n    ];\n    \n    return tester.compareQueries(queryTests, 3);\n}\n\n// Comprehensive Performance Analysis\nfunction runCompletePerformanceAnalysis() {\n    gs.log('=== Starting Comprehensive Query Performance Analysis ===');\n    \n    var allResults = [];\n    \n    gs.log('\\n1. Basic Query Methods Comparison:');\n    allResults.push(compareBasicQueryMethods());\n    \n    gs.log('\\n2. Date Query Methods Comparison:');\n    allResults.push(compareDateQueryMethods());\n    \n    gs.log('\\n3. Pagination Methods Comparison:');\n    allResults.push(comparePaginationMethods());\n    \n    gs.log('\\n4. Counting Methods Comparison:');\n    allResults.push(compareCountingMethods());\n    \n    gs.log('\\n5. Complex Query Optimization:');\n    allResults.push(compareComplexQueries());\n    \n    gs.log('\\n=== Performance Analysis Complete ===');\n    \n    return {\n        basicQueries: allResults[0],\n        dateQueries: allResults[1],\n        pagination: allResults[2],\n        counting: allResults[3],\n        complexQueries: allResults[4]\n    };\n}\n\n// Quick Performance Check for Development\nfunction quickPerformanceCheck(tableName, testQuery) {\n    var startTime = new Date().getTime();\n    \n    var gr = new GlideRecord(tableName);\n    testQuery(gr); // Apply query function\n    gr.query();\n    \n    var count = 0;\n    while (gr.next()) { count++; }\n    \n    var endTime = new Date().getTime();\n    var executionTime = endTime - startTime;\n    \n    var performance = {\n        executionTime: executionTime,\n        resultCount: count,\n        performance: executionTime < 100 ? 'Excellent' : \n                    executionTime < 500 ? 'Good' : \n                    executionTime < 1000 ? 'Fair' : 'Poor'\n    };\n    \n    gs.log('Quick Performance Check Results:');\n    gs.log('- Execution Time: ' + executionTime + 'ms');\n    gs.log('- Result Count: ' + count);\n    gs.log('- Performance Rating: ' + performance.performance);\n    \n    return performance;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Populate the type of device on any record/README.md",
    "content": "This script will return and populate the type of device from which incident or issue is raised.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Populate the type of device on any record/typeOfDevice.js",
    "content": "// Business rule that runs Before Insert on any record to get the type of device the user is raising the incidents or issues\n(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n    var device = GlideMobileExtensions.getDeviceType(); \n\n    if (device == 'doctype')\n    {\n        current.u_type_of_device = 'Laptop / Desktop'; // I have added my custom field, it can be added for the required field\n    }\n   if (device == 'm')\n    {\n        current.u_type_of_device = 'Mobile'; // I have added my custom field, it can be added for the required field\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Record Activity Collector/ActivityCollector.js",
    "content": "var ActivityStreamCollector = Class.create();\nActivityStreamCollector.prototype = {\n    initialize: function(tableName, recordSysId) {\n        this.tableName = tableName;\n        this.recordSysId = recordSysId;\n\n        this.arrayUtil = new global.ArrayUtil();\n        this.users = {};\n    },\n\n    /**\n     * This function collects the activity elements of a record and gives back the result as a JSON array\n     */\n    getActivityEntries: function() {\n\n        var historyArray = [];\n\n        var hw = new sn_hw.HistoryWalker(this.tableName, this.recordSysId);\n        // Get the first (original element)\n        hw.walkTo(0);\n        // This record is used for collecting the initial field values\n        var currentRec = hw.getWalkedRecord();\n\n        // As a first step we need to know which audited fields are shown on the record's form  \n        var activityFields = gs.getProperty('glide.ui.' + this.tableName + '_activity.fields', \"\");\n        // This definition is a comma separated list, which we need to convert as an array\n        activityFields = activityFields.split(\",\");\n\n        // Collect the initial data\n        var initialHistoryData = this._getFirstHistory(currentRec, activityFields);\n\n        // Walking through the history data\n        while (hw.walkForward()) {\n            var nextRec = hw.getWalkedRecord();\n\n            // Get all field changes\n            var fieldChanges = this._compareTwoRecords(currentRec, nextRec, activityFields);\n            if (fieldChanges.fields.length > 0)\n                historyArray.push(fieldChanges);\n\n            currentRec = hw.getWalkedRecordCopy();\n        }\n\n        // Get Comments and Work Notes\n        historyArray = this.arrayUtil.concat(historyArray, this._getNotesComments());\n\n        // Get attachments\n        historyArray = this.arrayUtil.concat(historyArray, this._getAttachments());\n\n        // Sort the element by date desc.\n        historyArray.sort(function(elem1, elem2) {\n            var date1 = new GlideDateTime(elem1.date);\n            var date2 = new GlideDateTime(elem2.date);\n            if (date1 > date2) {\n                return -1;\n            } else if (date1 < date2) {\n                return 1;\n            } else {\n                return 0;\n            }\n        });\n\n        // Add initial data to the end of array\n        // It is handled as a last step, because if the date of this activity is the same with the first action, de order is not correct.\n        historyArray.push(initialHistoryData);\n\n        var historyDataArray = [];\n        for (var idx in historyArray) {\n            var currentActivity = historyArray[idx];\n\n            var activityValue;\n\n            if (currentActivity.type == 'field_changes') {\n                var fields = [];\n\n                for (var idxElem in currentActivity.fields) {\n                    var currentElem = currentActivity.fields[idxElem];\n                    fields.push({\n                        label: currentElem.label,\n                        oldValue: currentElem.oldValue,\n                        newValue: currentElem.newValue\n                    });\n                }\n\n                activityValue = fields;\n            } else if (currentActivity.type == 'attachment') {\n                activityValue = currentActivity.fileName;\n            } else {\n                activityValue = currentActivity.text;\n            }\n\n            historyDataArray.push({\n                createdBy: currentActivity.userName,\n                createdOn: currentActivity.date,\n                type: currentActivity.type,\n                value: activityValue\n            });\n        }\n        return historyDataArray;\n    },\n\n\n    /**\n     * Get all attachments, which are related to the current record\n     */\n    _getAttachments: function() {\n        var attachments = [];\n        var saGr = new GlideRecord('sys_attachment');\n        saGr.addQuery(\"table_name\", this.tableName);\n        saGr.addQuery(\"table_sys_id\", this.recordSysId);\n        saGr.query();\n        while (saGr.next()) {\n            attachments.push({\n                date: saGr.getDisplayValue(\"sys_created_on\"),\n                userName: this._getUserDisplayName(saGr.getValue(\"sys_created_by\")),\n                type: \"attachment\",\n                fileName: saGr.getValue(\"file_name\")\n            });\n        }\n        return attachments;\n    },\n\n    /**\n     * This function collects and gives back the audited fields which are shown on the activity stream\n     */\n    _getFirstHistory: function(currGr, activityFields) {\n        var fieldDataArray = [];\n\n        for (var idxItem in activityFields) {\n            var item = activityFields[idxItem];\n\n            try {\n\n                // In case of Work notes and Comments we can skip the process\n                if (item == \"work_notes\" || item == \"comments\")\n                    continue;\n\n                var currentRecElem = currGr.getElement(item);\n                // In case of no value it can be skipped\n                if (gs.nil(currGr.getDisplayValue(item)))\n                    continue;\n\n                // Add the object to the array\n                fieldDataArray.push({\n                    name: item,\n                    label: currentRecElem.getLabel(),\n                    oldValue: \"\",\n                    newValue: currGr.getDisplayValue(item),\n                });\n            } catch (err) {} // In order to handle some unexpected cases...\n        }\n\n        return {\n            date: currGr.getDisplayValue(\"sys_created_on\"),\n            userName: this._getUserDisplayName(currGr.getValue(\"sys_created_by\")),\n            type: \"field_changes\",\n            fields: fieldDataArray\n        };\n    },\n\n    /**\n     * This function compares two records and gets all audited fields, where the content is different\n     */\n    _compareTwoRecords: function(currGr, nextGr, activityFields) {\n\n        var fieldDataArray = [];\n\n        for (var idxItem in activityFields) {\n            var item = activityFields[idxItem];\n            var currentRecElem = currGr.getElement(item);\n            var nextRecElem = nextGr.getElement(item);\n\n            if (currentRecElem == nextRecElem)\n                continue;\n\n            fieldDataArray.push({\n                name: item,\n                label: currentRecElem.getLabel(),\n                oldValue: currGr.getDisplayValue(item),\n                newValue: nextGr.getDisplayValue(item),\n            });\n        }\n\n        return {\n            date: nextGr.getDisplayValue(\"sys_updated_on\"),\n            userName: this._getUserDisplayName(nextGr.getValue(\"sys_created_by\")),\n            type: \"field_changes\",\n            fields: fieldDataArray\n        };\n    },\n\n    /**\n     * Get Work notes, Comments from a record\n     */\n    _getNotesComments: function() {\n\n        var noteArray = [];\n\n        var journalGr = new GlideRecord(\"sys_journal_field\");\n        journalGr.addQuery(\"name\", this.tableName);\n        journalGr.addQuery(\"element_id\", this.recordSysId);\n        journalGr.query();\n        while (journalGr.next()) {\n            noteArray.push({\n                date: journalGr.getDisplayValue(\"sys_created_on\"),\n                userName: this._getUserDisplayName(journalGr.getValue(\"sys_created_by\")),\n                type: journalGr.getDisplayValue(\"element\"),\n                text: journalGr.getValue(\"value\"),\n            });\n        }\n\n        return noteArray;\n    },\n\n    /**\n     * Get user display name\n     */\n    _getUserDisplayName: function(userName) {\n\n        // A bit caching... \n        if (this.users[userName])\n            return this.users[userName];\n\n        var userGr = new GlideRecord(\"sys_user\");\n        if (userGr.get(\"user_name\", userName)) {\n            this.users[userName] = userGr.getDisplayValue(\"name\");\n            return this.users[userName];\n        } else\n            return \"\";\n    },\n\n    type: 'ActivityStreamCollector'\n};\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Record Activity Collector/README.md",
    "content": "It might be a requirement when we need to collect the activities from a ticket, and report it to any purposes. There is an OOTB available Processor, called NGListHistoryDirectProcessor, but it executes a Java code, and call it from the back-end can be complex. So this solution makes the activity collection simple.\n\nThe feature can be used easily:\n\n``` Javascript\nvar tableName = \"incident\";\nvar recordSysId = \"57af7aec73d423002728660c4cf6a71c\";\nvar collector = new global.ActivityStreamCollector(tableName, recordSysId);\nvar activityObj = collector.getActivityEntries();\ngs.info(JSON.stringify(activityObj));\n```\n\nThe result will be a JSON array, which contains the history of record like it can be seen on the form:\n\n``` JSON\n[\n  {\n    \"createdBy\": \"System Administrator\",\n    \"createdOn\": \"2023-10-17 17:28:14\",\n    \"type\": \"field_changes\",\n    \"value\": [\n      {\n        \"label\": \"Incident state\",\n        \"oldValue\": \"New\",\n        \"newValue\": \"In Progress\"\n      }\n    ]\n  },\n  {\n    \"createdBy\": \"System Administrator\",\n    \"createdOn\": \"2023-10-17 17:05:18\",\n    \"type\": \"comments\",\n    \"value\": \"Test comment\"\n  },\n  {\n    \"createdBy\": \"System Administrator\",\n    \"createdOn\": \"2023-09-14 21:38:02\",\n    \"type\": \"attachment\",\n    \"value\": \"test.pdf\"\n  },\n  {\n    \"createdBy\": \"System Administrator\",\n    \"createdOn\": \"2018-12-13 08:30:24\",\n    \"type\": \"work_notes\",\n    \"value\": \"Changed the priority of the Incident\"\n  },\n  {\n    \"createdBy\": \"System Administrator\",\n    \"createdOn\": \"2018-08-30 10:06:52\",\n    \"type\": \"field_changes\",\n    \"value\": [\n      {\n        \"label\": \"Incident state\",\n        \"oldValue\": \"\",\n        \"newValue\": \"New\"\n      },\n      {\n        \"label\": \"Impact\",\n        \"oldValue\": \"\",\n        \"newValue\": \"3 - Low\"\n      },\n      {\n        \"label\": \"Priority\",\n        \"oldValue\": \"\",\n        \"newValue\": \"4 - Low\"\n      },\n      {\n        \"label\": \"Opened by\",\n        \"oldValue\": \"\",\n        \"newValue\": \"System Administrator\"\n      },\n      {\n        \"label\": \"Caller\",\n        \"oldValue\": \"\",\n        \"newValue\": \"David Miller\"\n      }\n    ]\n  }\n]\n```\n\nAnd finally the image below represents how the activity information looks like on the form:\n![activities](https://github.com/manrick/code-snippets/assets/29863465/345256f6-7e18-49b8-acc1-9e5d4c68131e)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Safe Bulk Delete/README.md",
    "content": "# GlideRecord Bulk Delete with Safety Checks\n\n## Description\nThis snippet allows you to safely delete multiple records from a ServiceNow table based on an encoded query.\nIt logs all records that match the query so you can review them before actually deleting anything.  \nHelps prevent accidental mass deletion of important data.\n\n## Note \n- Works in Global Scope by default\n- Can be executed in Background Scripts or Script Includes\n- **ALWAYS REVIEW LOGS BEFORE ENABLING DELETION**\n## Prerequisites\n- Server-side context (Background Script, Business Rule, Script Include)\n- Access to the target table\n- Basic understanding of GlideRecord and Encoded Queries\n\n## Usage\n```javascript\n// Logs all active low-priority incidents that would be deleted\nsafeDelete('incident', 'active=true^priority=5');\n\n// To perform actual deletion, uncomment gr.deleteRecord() inside the function\n```\n\n## Output\n```\nRecords matching query: 3\nRecord sys_id: 12345abcdef would be deleted.\nRecord sys_id: 23456bcdef would be deleted.\nRecord sys_id: 34567cdefg would be deleted.\nBulk delete preview complete. Verify logs before enabling deletion.\n```\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Safe Bulk Delete/safeDelete.js",
    "content": "/**\n * Safely delete multiple records from a ServiceNow table.\n * Logs all affected records before deletion to prevent accidental data loss.\n * Uncomment gr.deleteRecord() to perform the actual deletion.\n *\n * @param {string} table - The table name\n * @param {string} encodedQuery - GlideRecord encoded query for filtering records\n */\nfunction safeDelete(table, encodedQuery) {\n    if (!table || !encodedQuery) {\n        gs.error('Both table name and encoded query are required.');\n        return;\n    }\n\n    var gr = new GlideRecord(table);\n    gr.addEncodedQuery(encodedQuery);\n    gr.query();\n\n    var count = gr.getRowCount();\n    gs.info('Records matching query: ' + count);\n\n    if (count === 0) {\n        gs.info('No records found. Nothing to delete.');\n        return;\n    }\n\n    while (gr.next()) {\n        gs.info('Record sys_id: ' + gr.getValue('sys_id') + ' would be deleted.');\n        // gr.deleteRecord(); // Uncomment this line to actually delete\n    }\n\n    gs.info('Bulk delete preview complete. Verify logs before enabling deletion.');\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Set Template/README.md",
    "content": "This function is useful in applying template on tables when creating new record using scripts.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Set Template/setTemplate.js",
    "content": "var rec = useTemplate('incident','4d094f142f6bf01020a42ea62799b6cd'); // pass 2 parameters - 1) Name of table on which templateis to be applied 2) sys id of the template\ngs.info(rec);              // returns the sys id of newly created record with applied template values\n\nfunction useTemplate(tblName,template_sysid){ \n    var tmp = new GlideRecord(tblName);      \n    tmp.initialize();\n    var gtemp= new GlideTemplate.get(template_sysid).apply(tmp);       // apply template to new record\n    return tmp.insert();  \n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Unique Record/README.md",
    "content": "## Unique Record\n\nCreate a before insert Business rule to check is any existing record already available in a table , then abort the process to create a new record.\n\n**Use Case**\n\nThis is script to used to validate and ensure not to create create duplicate entries/records in any given table based on the mention criteria in the script, in our example we have **cmdb_ci_server** and using **serial_number** as a primary field to validate if there is any exisitng record on the same serial number\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Unique Record/uniquerecord.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n/*\nReplace table_name with the table name on which you're writing a BR, ex:cmdb_ci_server\nReplace unique_field with the field which you're taking as a coalese field, ex: serial_number\n*/\nvar uniqueRecord = new GlideRecord(\"<table_name>\");\nuniqueRecord.addQuery(\"<unique_field>\", current.<unique_field_name>);\nuniqueRecord.setLimit(1);\nuniqueRecord.query();\nif (uniqueRecord.next()) {\n  uniqueRecord.addErrorMessage(\"Entry for this <field_name> already exists\");\n  current.setAbortAction(true);\n}\n})(current, previous);\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/UpdateMultiple/README.md",
    "content": "Update Multiple is such a useful tool. It has a few restrictions so make sure you are able to use it for your use case (not Journals)\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/UpdateMultiple/script.js",
    "content": "var quickChange = new GlideRecord('yourtablename'); \nquickChange.addQuery('field_to_filter_on', 'data_to_be_changed'); // adjust query to find the offending records\nquickChange.setValue('field_to_change', 'change_you_want'); // setvalue is your friend, but make sure you can use it on the field\nquickChange.setWorkflow(false); //Optional to decide if you want other workflows to be invoked as a result\nquickChange.updateMultiple(); //notice the lack of query or checking or anything this puppy just goes"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Watch_List_functions/README.md",
    "content": "This code snippet is to help you remove a specific element/sys_id from a List or an array using javascript. This code can be run via any server side scripting.\nFor eg:\n1. Background scripting\n2. Fix script\n3. BR\n4. ScriptInclude etc.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/Watch_List_functions/removeSpecificUser.js",
    "content": "//You can run this code from background script/fix script/explore UI.\n\nvar sysId = '32 bit guid'; //pass the 32 bit sys_id of the user you want to remove.\n\nvar inc = new GlideRecord('incident');\ninc.get('number','INC0010112');// pass the correct number to get the record.\n\nvar arr = inc.watch_list.split(','); //split the watch list values and put them in an array.\nvar idx = arr.indexOf(sysId); //get the index of sys_id of user you want to remove from watch list.\narr.splice(idx,1);           // Remove the specific user from array. idx is the index from which you want to remove and '1' is the number of elements you want to remove.\ninc.watch_list = arr.join(','); // join the array and set the values back to watch list.\ninc.setWorkflow(false); //if you don't want any BR to run for this update.\ninc.update(); // update the record\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/comments on gr.update/README.md",
    "content": "Comments on gr.update via an optional parameter of why or from where it is being update.\n\nReference of documentation link: https://developer.servicenow.com/dev.do#!/reference/api/utah/server/no-namespace/c_GlideRecordScopedAPI#r_ScopedGlideRecordUpdate_String?navFilter=update\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/comments on gr.update/code.js",
    "content": "var now_GR = new GlideRecord('incident');\nnow_GR.get('99ebb4156fa831005be8883e6b3ee4b9');\nnow_GR.setValue('short_description', 'Update the short description');\nnow_GR.update(\"updated from BR\" ); // This comment gets added to the activity log of incident for audit purposes.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/findDuplicate/GlideAggregateScript.js",
    "content": "var q = new GlideAggregate('alm_asset');\nq.addAggregate('COUNT', 'asset_tag');\nq.groupBy('asset_tag');\nq.addHaving('COUNT', '>', '1'); \n//q.addQuery('sys_updated_by','')\nq.query();\nvar listOfDupes = new Array();\nwhile (q.next()) {\ngs.info(q.getRowCount());\n\tlistOfDupes.push(q.getValue('asset_tag'));        \n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/findDuplicate/README.md",
    "content": "This sripts is used to find Duplicate Records in the 'asset' table you can change table as per your wish.\nDuplicates are counted on 'asset_tag'.\nIf count is greater than 2 than we add it to array.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/getEncodedQuery/README.md",
    "content": "1.In any GlideRecord query retrieve query using getEncodedQuery()\n\n2.Apply this encoded query to create/update records (you can apply this query to other tables if query is appropriate).\n\n![image](https://user-images.githubusercontent.com/42912180/195796801-4758afd0-4c35-4405-9836-560c6041dd4a.png)\n\n\n\n\nBefore Running the script :\n![image](https://user-images.githubusercontent.com/42912180/195796529-cad998a9-c97e-40d7-b2b0-7bb48aaecf9a.png)\n\n\nAfter Running the script:\n![image](https://user-images.githubusercontent.com/42912180/195796411-dcb20d99-2bd7-4ef6-88bc-846f46006122.png)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/getEncodedQuery/code.js",
    "content": "gr.query();\nvar outPutQuery = gr.getEncodedQuery();  //this gives query applied above as encoded query\nif(!gr.next()){ //check if record exist or not\n//create record\ngr.initialize();\ngr.applyEncodedQuery(outPutQuery); //set field values as “outPutQuery” encoded query\ngr.insert();\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/isValidGlideRecord/README.md",
    "content": "# Description\n\nMany developers are building functions which expect any GlideRecord reference as a function parameter but within these functions they do no check whether the passed value really represents a valid GlideRecord. However to test a function parameter for a valid GlideRecord reference is pretty tricky and extensive. Therefore I built a small helper function `isValidGlideRecord` which will do the job. As a second optional parameter you can specify a table name which will be considered for an additional validation.\n\n# Usage\n\n```\nvar grIncident = new GlideRecord('incident');\n\ngrIncident.get('12345678901234567890123456789012');\n\ngs.info(isValidGlideRecord(grIncident, 'incident'));\n```\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideRecord/isValidGlideRecord/isValidGlideRecord.js",
    "content": "/*\n * Tests whether passed value in parameter `grRecordInstance` represents a valid GlideRecord instance.\n * \n * @param {Object} grRecordInstance\n *   Reference to an instance of a GlideRecord object.\n * @param {Object} [strTableName]\n *   If specified an additional check of the GlideRecord against the given table name is performed.\n * @returns {Object}\n *   Name of the table for which the GlideRecord was instantiated.\n * @throws TypeError\n *   In case value of parameter `strTableName` is not a string or parameter count is 0. \n */\nfunction isValidGlideRecord(grRecordInstance, strTableName) {\n\t\n\tif (arguments.length === 0) {\n\t\tthrow new TypeError('At least one parameter value is expected!');\n\t}\n\t\n\tif (arguments.length > 1 && (typeof strTableName !== 'string' || strTableName.length === 0)) {\n\t\tthrow new TypeError('Value of parameter \"strTableName\" does not represent a valid string!');\t\t\n\t}\n\t\n\tvar _strTableName = String(strTableName || '').trim();\n\t\n\tvar _isValid = \n\t\tgrRecordInstance &&\n\t\ttypeof grRecordInstance === 'object' &&\n\t\tgrRecordInstance instanceof GlideRecord &&\n\t\t!grRecordInstance.isNewRecord() &&\n\t\tgrRecordInstance.isValidRecord();\n\t\n\t\n\tif (_strTableName.length > 0) {\n\t\t_isValid = _isValid && grRecordInstance.getTableName() === strTableName;\n\t}\n\n\treturn _isValid;\n};\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Impersonate/README.md",
    "content": "GlideSystem (referred as \"gs\") provides a way to find information about the current session.\nUsing the getSession() method of the scoped GlideSystem API.\n\nThis gives the example on how to impersonate a user using the impersonate method in the code.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Impersonate/impersonate.js",
    "content": "gs.info(\"Current User: \" + gs.getUserName())\n\nvar me = gs.getUserID();\n\nvar ableTuter = \"62826bf03710200044e0bfc8bcbe5df1\" //user id of the user to be impersonated.\n\ngs.getSession().impersonate(ableTuter);\n\ngs.info(\"Current User: \" + gs.getUserName())\n\nvar incGr = new GlideRecord('incident');\nincGr.initialize();\nincGr.short_description = \"Creating an incident using script impersonation\";\nincGr.caller = gs.getUserID();\nincGr.insert();\n\ngs.getSession().impersonate(me);"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Session/README.md",
    "content": "GlideSystem (referred as \"gs\") is used to get the current user session which we are using to set the custom key and value which are used in client side scripts.\n\nExample client script used to retrieve it\n\n```\nfunction onLoad(){\n var value = g_user.getClientData(\"custom_key\");\n console.log(\"Client data \"+value);\n}\n```\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Session/session.js",
    "content": "//Get user session object.\nvar session = gs.getSession();\n\n//Set value for custom key\nsession.putClientData(\"custom_key\",\"custom_value\");\n\n//Print the value for the key supplied\ngs.print(session.getClientData(\"custom_key\"));\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Table/README.md",
    "content": "# GlideSystem Table Methods\n\n### tableExists(String tableName)\n  * Determines if a database table exists.\n  * [tableExists(String tableName) code snippet](tableExists.js)\n  * [tableExists(String tableName) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-tableExists_S)\n\n### truncateTable(String tableName)\n  * Deletes all records in a table. ***UNDOCUMENTED"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Table/tableExists.js",
    "content": "var table = gs.tableExists('cmdb_ci'); //Name of the table\ngs.info(table); //Will return a boolean\n\n//Output Example:\n//*** Script: true\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Table/truncateTable.js",
    "content": ""
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Trigger Event/README.md",
    "content": "This shows the example of triggering the event using the gs.eventQueue() or gs.eventQueueScheduled()\n\neventQueue() - This method inserts an specified event in an event queue\n \n - Event name. Enclose the event name in quotes.\n - GlideRecord object, typically current but can be any GlideRecord object from the event's table.\n - Any value that resolves to a string. This is known as parm1 (Parameter 1). Can be a string, variable that resolves to a string, or method that resolves to a string.\n - Any value that resolves to a string. This is known as parm2 (Parameter 2). Can be a string, variable that resolves to a string, or method that resolves to a string.\n - (Optional) Name of the queue to manage the event.\n\n\neventQueueScheduled(String name, Object instance, String parm1, String parm2, Object expiration)\n\n - Event name. Enclose the event name in quotes.\n - GlideRecord object, typically current but can be any GlideRecord object from the event's table.\n - Any value that resolves to a string. This is known as parm1 (Parameter 1). Can be a string, variable that resolves to a string, or method that resolves to a string.\n - Any value that resolves to a string. This is known as parm2 (Parameter 2). Can be a string, variable that resolves to a string, or method that resolves to a string.\n - (Optional) GlideDateTime object or a date/time type element that specifies the date and time to process the event.\n\n Using eventQueue() we can specify our own event queue - which makes the event processing faster as we specify a dedictaed queue for the same. But if we are using the eventQueueScheduled this is not possible as we have to provide the time when the event should trigger."
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Trigger Event/eventQueue.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    /**\n     * eventQueue() - \n     *   This method inserts an specified event in an event queue\n     * Event name. Enclose the event name in quotes.\n     * GlideRecord object, typically current but can be any GlideRecord object from the event's table.\n     * Any value that resolves to a string. This is known as parm1 (Parameter 1). Can be a string, variable that resolves to a string, or method that resolves to a string.\n     * Any value that resolves to a string. This is known as parm2 (Parameter 2). Can be a string, variable that resolves to a string, or method that resolves to a string.\n     * (Optional) Name of the queue to manage the event.\n     */\n\tgs.eventQueue(\"task_assigned\",current,gs.getUserName(),gs.getUserDisplayName());\n\n})(current, previous);"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/Trigger Event/eventQueueScheduled.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    /**\n     * eventQueue() - \n     *   This method inserts an specified event in an event queue\n     * Event name. Enclose the event name in quotes.\n     * GlideRecord object, typically current but can be any GlideRecord object from the event's table.\n     * Any value that resolves to a string. This is known as parm1 (Parameter 1). Can be a string, variable that resolves to a string, or method that resolves to a string.\n     * Any value that resolves to a string. This is known as parm2 (Parameter 2). Can be a string, variable that resolves to a string, or method that resolves to a string.\n     * (Optional) GlideDateTime object or a date/time type element that specifies the date and time to process the event.\n     */\n    var processTime = new GlideDateTime();\n    processTime.add(5*60*60); //trigger event after 5 mins\n\n\tgs.eventQueueScheduled(\"task_assigned\",current,gs.getUserName(),gs.getUserDisplayName(),processTime);\n\n})(current, previous);"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/User/README.md",
    "content": "# GlideSystem User Methods\n\n### userID\n* Description: Returns the sys_id of the user associated with this session.\n* [userID Code Snippet](userID.js)\n* [userID API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-userID)"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/User/userID.js",
    "content": "var id = gs.userID(); //Returns the sys_id of the user associated with this session\ngs.info(id);\n\n//Example Output: 98b9c2ee1b356664a496154de4febcb\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/User Display Name/README.md",
    "content": "# GlideSystem - getUserDisplayName()\n\n\nThis is used to return the name field of the current user.\n\nIt returns Display name of user and not sysid or username\n\n*Example* : It returns \"John David\" instead of \"jdavid\"\n\n`script.js` file contains the following Use case : Display info message with current user display name on a form with \"Display business rule\"\n\n**Sample output**:\n\n![image](https://user-images.githubusercontent.com/36041130/138115452-6519d2a9-6bda-4eb8-8c50-670669e8466a.png)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/User Display Name/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    var user = gs.getUserDisplayName();\n    gs.addInfoMessage(\"Welcome \" + user); //This adds info message on top of the form\n\n})(current, previous);\n\n\n//output will be Display name of current logged in user\n//Example output : Welcome Abel Tuter\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/README.md",
    "content": "# GlideSystem date-time Methods\n\n### beginningOfLastMonth\n  \n  * [beginningOfLastMonth code snippet](beginningOfLastMonth.js)\n  * Description: Returns the date and time for the beginning of last month in GMT.\n  * [beginningOfLastMonth() API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server/no-namespace/c_GlideSystemScopedAPI#SGSYS-beginningOfLastMonth?navFilter=glidesystem)\n\n### minutesAgoEnd(Number minutes)\n  * Description: Returns a date and time for the end of the minute a certain number of minutes ago.\n  * [minutesAgoEnd(Number minutes) code snippet](minutesAgoEnd.js)\n  * [minutesAgoEnd(Number minutes) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-minutesAgoEnd_N)\n\n### minutesAgoStart(Number minutes)\n  * Description: Returns a date and time for the start of the minute a certain number of minutes ago.\n  * [minutesAgoStart(Number minutes) code snippet](minutesAgoStart.js)\n  * [minutesAgoStart(Number minutes) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-minutesAgoStart_N)\n\n### monthsAgo(Number months)\n  * Description: Returns a date and time for a certain number of months ago.\n  * [monthsAgo(Number Months) code snippet](monthsAgo.js)\n  * [monthsAgo(Number Months) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-monthsAgo_N)\n\n### monthsAgoEnd(Number months)\n  * Description: Returns a date and time for the last day of the month a certain number of months ago\n  * [monthsAgoEnd(Number Months) code snippet](monthsAgoEnd.js)\n  * [monthsAgoEnd(Number Months) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-monthsAgoEnd_N)\n\n### monthsAgoStart(Number months)\n  * Description: Returns a date and time for the start of the month a certain number of months ago.\n  * [monthsAgoStart(Number Months) code snippet](monthsAgoStart.js)\n  * [monthsAgoStart(Number Months) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-monthsAgoStart_N)\n\n### quartersAgo(Number quarters)\n  * Description: Returns a date and time for a certain number of quarters ago.\n  * [quartersAgo(Number quarters) code snippet](quartersAgo.js)\n  * [quartersAgo(Number quarters) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-quartersAgo_N)\n\n### quartersAgoEnd(Number quarters)\n  * Description: Returns a date and time for the last day of the quarter, for a specified number of quarters ago.\n  * [quartersAgoEnd(Number quarters) code snippet](quartersAgoEnd.js)\n  * [quartersAgoEnd(Number quarters) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-quartersAgoEnd_N)\n\n### quartersAgoStart(Number quarters)\n  * Description: Returns a date and time for the last day of the quarter, for a specified number of quarters ago.\n  * [quartersAgoStart(Number quarters) code snippet](quartersAgoStart.js)\n  * [quartersAgoStart(Number quarters) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-quartersAgoStart_N)\n\n### yearsAgo(Number years)\n  * Description: Gets a date and time for a certain number of years ago.\n  * [yearsAgo(Number years) code snippet](yearsAgo.js)\n  * [yearsAgo(Number years) API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-yearsAgo_N)\n\n### yesterday()\n  * Description: Returns yesterday's time (24 hours ago).\n  * [yesterday() code snippet](yesterday.js)\n  * [yesterday() API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-yesterday)\n\n### setDisplayValueInternalWithAlternates\n  * [setDisplayValueInternalWithAlternates code snippet](setDisplayValueInternalWithAlternates.js)\n  * Description: Sets a date and time value using the internal format (yyyy-MM-dd HH:mm:ss) and the current user's time zone. This method attempts to parse incomplete date and time values.\n  * [setDisplayValueInternalWithAlternates() API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GDT-setDispValInternalAlt_S?navFilter=setDisplayValueInternalWithAlternates)"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/beginningOfLastMonth.js",
    "content": "// get the current date and time in the logged in user's time zone\ngs.info(new GlideDateTime().getDisplayValue());\n// show the beginning of last month's date and time in the logged in user's time zone\ngs.info(gs.beginningOfLastMonth());\n\n// output\n// x_scope_app: 2021-10-01 15:50:40\n// x_scope_app: 2021-09-01 05:00:00\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/minutesAgoEnd.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar minAgo = gs.minutesAgoEnd(5); //Returns a date and time for the end of the minutes (in brackets) a certain number of minutes ago.\ngs.info('End of 5 Minutes Ago: ' + minAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 14:31:23\n//*** Script: End of 5 Minutes Ago: 2021-10-04 18:26:59\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/minutesAgoStart.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar minAgo = gs.minutesAgoStart(5); //Returns a date and time for the start of the minutes (in brackets) a certain number of minutes ago.\ngs.info('Start of 5 Minutes Ago: ' + minAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 14:34:45\n//*** Script: Start of 5 Minutes Ago: 2021-10-04 18:29:00\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/monthsAgo.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar monthsAgo = gs.monthsAgo(5); //Returns a date and time for a certain number of months (in brackets) ago.\ngs.info('5 Months Ago: ' + monthsAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 14:47:23\n//*** Script: 5 Months Ago: 2021-05-04 18:47:23\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/monthsAgoEnd.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar monthsAgo = gs.monthsAgoEnd(5); //Returns a date and time for the last day of the month a certain number of months (in bracket) ago\ngs.info('End of 5 Months Ago: ' + monthsAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 14:53:40\n//*** Script: End of 5 Months Ago: 2021-05-31 23:59:59\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/monthsAgoStart.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar monthsAgo = gs.monthsAgoStart(5); //Returns a date and time for the start of the month a certain number of months (in bracket) ago.\ngs.info('Start of 5 Months Ago: ' + monthsAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 15:00:53\n//*** Script: Start of 5 Months Ago: 2021-05-01 00:00:00\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/quartersAgo.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar quartersAgo = gs.quartersAgo(2); //Returns a date and time for a certain number of quarters ago\ngs.info('2 Quarters Ago: ' + quartersAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 15:41:32\n//*** Script: 2 Quarters Ago: 2021-04-04 15:41:32\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/quartersAgoEnd.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar quartersAgo = gs.quartersAgoEnd(2); //Returns a date and time for the last day of the quarter, for a specified number of quarters ago\ngs.info('End of 2 Quarters Ago: ' + quartersAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 15:48:46\n//*** Script: End of 2 Quarters Ago: 2021-06-30 23:59:59\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/quartersAgoStart.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar quartersAgo = gs.quartersAgoStart(2); //Returns a date and time for the first day of the quarter, for a specified number of quarters ago\ngs.info('Start of 2 Quarters Ago: ' + quartersAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-04 15:53:02\n//*** Script: Start of 2 Quarters Ago: 2021-04-01 00:00:00\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/setDisplayValueInternalWithAlternates.js",
    "content": "var gdt = new GlideDateTime();\n\n//get the current datetime in the logged in user's time zone\ngs.print(gdt.getDisplayValue());\n\n//parse the incomplete date time values to current user's date and GMT time\ngdt.setDisplayValueInternalWithAlternates(\"12:00:00 10-25\");\ngs.print(gdt.getValue());\n\n//Output\n//*** Script: 2021-10-07 03:47:32\n//*** Script: 2021-10-07 08:47:32\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/yearsAgo.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar yearsAgo = gs.yearsAgo(1); //Gets a date and time for a certain number of years ago.\ngs.info('1 Year Ago: ' + yearsAgo);\n\n//Output Example:\n//*** Script: Now: 2021-10-05 08:54:28\n//*** Script: 1 Year Ago: 2020-10-05 12:54:28\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/date-time/yesterday.js",
    "content": "var now = gs.nowDateTime();\ngs.info('Now: ' + now);\nvar yesterdayDateTime = gs.yesterday(); //Returns yesterday's time (24 hours ago).\ngs.info('24 Hours Ago: ' + yesterdayDateTime);\n\n//Output Example:\n//*** Script: Now: 2021-10-05 08:59:50\n//*** Script: 24 Hours Ago: 2021-10-04 08:59:50\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/hasRoleExactly/README.md",
    "content": "# Server-side version of the hasRoleExactly\n\nDetermines whether the current user has the specified role.\n\nThis feature only exists on the client-side but can be useful on the server-side also.\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/hasRoleExactly/script.js",
    "content": "function hasRoleExactly(role) {\n    // Java String to JavaScript String\n    var roles = gs.getSession().getRoles() + '';\n    var roleArr = roles.split(',');\n    return roleArr.indexOf(role) !== -1;\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/workflowFlush/README.md",
    "content": "### workflowFlush() syntax\n#### Description: \n* Deletes any open scheduled job records in the Schedule (sys_trigger) table for the specified GlideRecord. \n* For reference, the below code is a part of OOB 'incident events' Business Rule\n\n* [workflowFlush() API doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_GlideSystemAPI#r_GS-workflowFlush_O?navFilter=workflowFlush)\n* [workflowFlush code snippet](workflowFlush.js)\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideSystem/workflowFlush/workflowFlush.js",
    "content": "//For reference, the below code is a part of OOB 'incident events' Business Rule\n\nif (current.active.changesTo(false)) {\n  gs.eventQueue(\"incident.inactive\", current, current.incident_state, previous.incident_state);\n  gs.workflowFlush(current);   //Deletes any open scheduled job records in the Schedule (sys_trigger) table for the specified GlideRecord.\n}\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideTableDescriptor/getFirstTableName()/GlideTableDescriptor.js",
    "content": "var td = GlideTableDescriptor.get(dictionaryGR.getValue(\"name\"));\nvar ed = td.getElementDescriptor(dictionaryGR.getValue(\"element\"));  \ngs.print(ed.getFirstTableName()); //using “getFirstTableName()” to find out where it was descended from.  \n\n\n"
  },
  {
    "path": "Core ServiceNow APIs/GlideTableDescriptor/getFirstTableName()/README.md",
    "content": "## GlideTableDescriptor\n\nWhen a table is extended, that it creates all new sys_dictionary records for all the fields on the base table instead with just one single sys_dictionary entry. These entries are referred to as “cloned descendant elements” and you need to edit the “first element” in order to modify them. \nYou can also programatically interact with them using a “glideElementDescriptor() method on the related “GlideTableDescriptor” object. Here we using “getFirstTableName()” to find out where it was descended from.  \n\n\n\n"
  },
  {
    "path": "Integration/Attachments/Attachment to Base64/README.md",
    "content": "# Base64 file encoder\n\nConvert attachment to Base64"
  },
  {
    "path": "Integration/Attachments/Attachment to Base64/script.js",
    "content": "function getAttachmentBase64(attachmentID) {\n    var attachmentGR = new GlideRecord('sys_attachment');\n    if (attachmentGR.get(attachmentID)) {\n        var bytesContent = new GlideSysAttachment().getBytes(attachmentGR);\n        var base64ImageStr = GlideStringUtil.base64Encode(bytesContent);\n        var contentType = attachmentGR.getValue('content_type');\n        return 'data:' + contentType + ';base64,' + base64ImageStr;\n    }\n}"
  },
  {
    "path": "Integration/Attachments/Attachment to base64 in scope/README.md",
    "content": "Encode attachment to base64 for scoped applications. Uses the GlideSysAttachment API. \nPlace of use: creating a spoke/integration where you need to send an attachment via REST.\n"
  },
  {
    "path": "Integration/Attachments/Attachment to base64 in scope/attachmentToBase64Scope.js",
    "content": "/***** \n * Function to parse an attachment to base64 in scope as it uses GlideSysAttachment API\n * Used in for exmpale API calls\n * Input to the function are both strings for table name and record sysId\n * ******/\nfunction parseAttachmentToBase64(tableName, recordId) {\n\nvar attachmentAPI = new GlideSysAttachment();\nvar attachmentGR = attachmentAPI.getAttachments(tableName, recordId);\n    if(attachmentGR.next()){\n        var base64Attachment = attachmentAPI.getContentBase64(attachmentGR);\n        return base64Attachment;\n    }\n    else{\n        return \"no attachment found\";\n        }\n\n    }"
  },
  {
    "path": "Integration/Attachments/Base 64 to Attachment/README.md",
    "content": "This could be used to convert base 64 content into an attachment in ServiceNow. This is mainly used for integrating attachments from external systems to read and convert the base 64 content into an attachment.\n\nSample Usage - Attaching and xls file on a incident record.\n\nvar attachment = convertBase64ToAttachment(\"0M8R4KGxGuEAAAAAAAAAAAAAAAAAAAAAOwADAP7/CQAGAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAEAAABwAAAAEAAAD+////AAAAAAgAAAD///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////9SAG8AbwB0ACAARQBuAHQAcgB5AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFgAFAf//////////AQAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAACgTKHLiKDGAQEAAABACgAAAAAAAFcAbwByAGsAYgBvAG8AawAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAASAAIB////////////////AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABgKAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAAD///////////////8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAP///////////////wAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAkIEAAABgUA0xDMB0EAAAAGAAAA4QACALAEwQACAAAA4gAAAFwAcAALAABCb3cgUnVnZ2VyaSAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgQgACALAEYQECAAAAPQECAAAAnAACAA4AGQACAAAAEgACAAAAEwACAAAArwECAAAAvAECAAAAPQASAGgBDgFcOr4jOAAAAAAAAQBYAkAAAgAAAI0AAgAAACIAAgAAAA4AAgABALcBAgAAANoAAgAAADEAGgDIAAAA/3+QAQAAAAAAAAUBQQByAGkAYQBsADEAGgDIAAAA/3+QAQAAAAAAAAUBQQByAGkAYQBsADEAGgDIAAAA/3+QAQAAAAAAAAUBQQByAGkAYQBsADEAGgDIAAAA/3+QAQAAAAAAAAUBQQByAGkAYQBsADEAGgDIAAAA/3+8AgAAAAAAAAUBQQByAGkAYQBsAB4EHAAFABcAACIkIiMsIyMwXyk7XCgiJCIjLCMjMFwpHgQhAAYAHAAAIiQiIywjIzBfKTtbUmVkXVwoIiQiIywjIzBcKR4EIgAHAB0AACIkIiMsIyMwLjAwXyk7XCgiJCIjLCMjMC4wMFwpHgQnAAgAIgAAIiQiIywjIzAuMDBfKTtbUmVkXVwoIiQiIywjIzAuMDBcKR4ENwAqADIAAF8oIiQiKiAjLCMjMF8pO18oIiQiKiBcKCMsIyMwXCk7XygiJCIqICItIl8pO18oQF8pHgQuACkAKQAAXygqICMsIyMwXyk7XygqIFwoIywjIzBcKTtfKCogIi0iXyk7XyhAXykeBD8ALAA6AABfKCIkIiogIywjIzAuMDBfKTtfKCIkIiogXCgjLCMjMC4wMFwpO18oIiQiKiAiLSI/P18pO18oQF8pHgQ2ACsAMQAAXygqICMsIyMwLjAwXyk7XygqIFwoIywjIzAuMDBcKTtfKCogIi0iPz9fKTtfKEBfKR4EGACkABMAAHl5eXkvbW0vZGQgaGg6bW06c3PgABQAAAAAAPX/IAAAAAAAAAAAAAAAwCDgABQAAQAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAQAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAgAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAgAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAPX/IAAA9AAAAAAAAAAAwCDgABQAAAAAAAEAIAAAAAAAAAAAAAAAwCDgABQAAQArAPX/IAAA+AAAAAAAAAAAwCDgABQAAQApAPX/IAAA+AAAAAAAAAAAwCDgABQAAQAsAPX/IAAA+AAAAAAAAAAAwCDgABQAAQAqAPX/IAAA+AAAAAAAAAAAwCDgABQAAQAJAPX/IAAA+AAAAAAAAAAAwCDgABQAAACkAAEAAAAAAAAACAQIBAAAwCDgABQAAAAAAAEACAAAEAAACAQIBAAAwCDgABQABQAAAAEAIAAACAAACAQIBAAAwCCTAgQAEIAD/5MCBAARgAb/kwIEABKABP+TAgQAE4AH/5MCBAAAgAD/kwIEABSABf9gAQIAAACFAA4AxwYAAAAABgBQYWdlIDGMAAQAAQABAPwA1wAcAAAAEgAAAAYAAE51bWJlcggAAENhdGVnb3J5CAAAUHJpb3JpdHkOAABJbmNpZGVudCBzdGF0ZQoAAEVzY2FsYXRpb24RAABTaG9ydCBkZXNjcmlwdGlvbgsAAEFzc2lnbmVkIHRvCAAASU5DMTAwMTYOAABJbnF1aXJ5IC8gSGVscAcAADQgLSBMb3cGAABBY3RpdmUGAABOb3JtYWwPAABLZXlib2FyZCBzdGlua3MAAAAIAABJTkMxMDAxNwMAAEJvdwgAAElOQzEwMDE5BAAAQm93K/8AGgAIANYFAAAMAAAAQAYAAHYAAACTBgAAyQAAAAoAAAAJCBAAAAYQALsNzAfBAAAABgAAAA0AAgABAAwAAgBkAA8AAgABABEAAgAAABAACAD8qfHSTWJQP18AAgABACoAAgAAACsAAgAAAIIAAgABAIAACAAAAAAAAAAAACUCBAAAAP8AgQACAMEEGwACAAAAGgACAAAAFAAAABUAAACDAAIAAACEAAIAAAChACIAAQBkAAEAAQABAAIALAEsAQAAAAAAAOA/AAAAAAAA4D8AAFUAAgAIAH0ADAAAAAAAAAoPAAYAAAB9AAwAAQABAAAKDwAGAAAAfQAMAAIAAgAACg8ABgAAAH0ADAADAAMAAA4PAAYAAAB9AAwABAAEAAAKDwAGAAAAfQAMAAUABQAAEQ8ABgAAAH0ADAAGAAYAAAsPAAYAAAAAAg4AAAAAAAMAAAAAAAcAAAD9AAoAAAAAABcAAAAAAP0ACgAAAAEAFwABAAAA/QAKAAAAAgAXAAIAAAD9AAoAAAADABcAAwAAAP0ACgAAAAQAFwAEAAAA/QAKAAAABQAXAAUAAAD9AAoAAAAGABcABgAAAP0ACgABAAAAFgAHAAAA/QAKAAEAAQAWAAgAAAD9AAoAAQACABYACQAAAP0ACgABAAMAFgAKAAAA/QAKAAEABAAWAAsAAAD9AAoAAQAFABYADAAAAP0ACgABAAYAFgANAAAA/QAKAAIAAAAWAA4AAAD9AAoAAgABABYACAAAAP0ACgACAAIAFgAJAAAA/QAKAAIAAwAWAAoAAAD9AAoAAgAEABYACwAAAP0ACgACAAUAFgAPAAAA/QAKAAIABgAWAA0AAAD9AAoAAwAAABYAEAAAAP0ACgADAAEAFgAIAAAA/QAKAAMAAgAWAAkAAAD9AAoAAwADABYACgAAAP0ACgADAAQAFgALAAAA/QAKAAMABQAWABEAAAD9AAoAAwAGABYADQAAAAgCEAAAAAAABgD/AAAAAAAAAAAACAIQAAEAAAAGAP8AAAAAAAAAAAAIAhAAAgAAAAYA/wAAAAAAAAAAAAgCEAADAAAABgD/AAAAAAAAAAAAPgISAL4HAAAAAEAAAAAAAAAAAAAAAEEACgAAAAEAAQBAAAIAHQAPAAIAAAAAAAABAAAAAAAAABIAAgAAAAoAAAD//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////wEAAAACAAAAAwAAAAQAAAAFAAAABgAAAAcAAAAIAAAACQAAAAoAAAALAAAADAAAAA0AAAAOAAAADwAAABAAAAARAAAAEgAAABMAAAAUAAAAFQAAABYAAAAXAAAAGAAAABkAAAAaAAAAGwAAABwAAAAdAAAAHgAAAB8AAAAgAAAAIQAAACIAAAAjAAAAJAAAACUAAAAmAAAAJwAAACgAAAD+/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////v///wIAAAADAAAABAAAAAUAAAAGAAAA/v////7////+//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////8=\", \"incident.xls\", \"application/vnd.ms-excel\", \"incident\", \"71e0fa504750210042bd757f2ede2730\");\n\n//gs.info(\"Attachment: \" + attachment);\n"
  },
  {
    "path": "Integration/Attachments/Base 64 to Attachment/base64toattachment.js",
    "content": "/*************************************************************************************/\n// INPUT ARGUMENTS\n// content - Base 64 of an attachment\n// fileName - Name of the attachment that should be created with extension eg incidents.xls\n// contentType - Content Type value of the attachment eg \tapplication/vnd.ms-excel, image/jpeg\n// table - Table where the new attachment should be uploaded\n// record - Record where the new attachment should be uploaded\n\n// OUTPUT ARGUMENTS\n// attachmentId - sys_id of attachment\n/*************************************************************************************/\n\nfunction convertBase64ToAttachment(content, fileName, contentType, table, record) {\n    var attachRecord = new GlideRecord(table);\n    if(attachRecord.get(record)){\n\t\t  var sysAttachment = new GlideSysAttachment();\n\t\t  var decodeAttachment = GlideStringUtil.base64DecodeAsBytes(content);\n\t\t  var attachmentId = sysAttachment.write(attachRecord, fileName, contentType, decodeAttachment);\n\t\t  return attachmentId;\n\t}else{\n\t\treturn \"Record not found\";\n\t}\t\n}\n"
  },
  {
    "path": "Integration/Attachments/CSVParser/README.md",
    "content": "The script parses through a CSV attachment accessed from the instance and reads each data row by row and column by column. You could also specify the required separator and quote character to escape them.\n\nSample Usage:  \n\nparseCSVFile(\"sys_id=76fc711f2f0b7050809e51172799b685\");\n\n"
  },
  {
    "path": "Integration/Attachments/CSVParser/csvparser.js",
    "content": "/********************************************************************************/\nInput:\nattachmentQuery - Pass an encoded query to access the CSV attachment\n\nOutput:\nLogs each rows and columns in the CSV file, use according to your logic\n\n/********************************************************************************/\n\nfunction parseCSVFile(attachmentQuery) {\n    var attachmentRecord = new GlideRecord(\"sys_attachment\");\n    attachmentRecord.addEncodedQuery(attachmentQuery);\n    attachmentRecord.query();\n    if (attachmentRecord.next()) {\n        var stringUtil = new GlideStringUtil();\n        var sysAttachment = new GlideSysAttachment();\n        //Get the attachment content in bytes\n        var bytesData = sysAttachment.getBytes(attachmentRecord);\n        // Encoded and decode the attachment content into a string\n        var encData = stringUtil.base64Encode(bytesData);\n        var decData = stringUtil.base64Decode(encData) + '';\n\n        //Specify seperator and quote character to parse correctly\n        var delimiter = ',';\n        var quoteCharacter = '\"';\n        //Split into different rows\n        var csvArray = decData.split(\"\\r\\n\");\n        for (var row in csvArray) {\n            if (!!csvArray[row]) {\n                //Parse each row through the parser and extract each columns\n                var csvParser = new sn_impex.CSVParser().parseLineToArray(csvArray[row], delimiter, quoteCharacter);\n                //Depending on number of columns, specify array index to access or loop them\n                gs.info(row + \" - \" + csvParser[0] + \" - \" + csvParser[1]);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Integration/Attachments/CSVParser/script.js",
    "content": "/********************************************************************************/\nInput:\nattachmentQuery - Pass sysId CSV attachment\n\nOutput:\nconverted into object from CSV\n\n/********************************************************************************/\n\nfunction parseCSVFile(sysId) {\n    var attachmentRecord = new GlideRecord(\"sys_attachment\");\n    attachmentRecord.get(sysId);\n    attachmentRecord.query();\n\n    if (attachmentRecord.next()) {\n        var stringUtil = new GlideStringUtil();\n        var sysAttachment = new GlideSysAttachment();\n        var bytesData = sysAttachment.getBytes(attachmentRecord);\n        var encData = stringUtil.base64Encode(bytesData);\n        var decData = stringUtil.base64Decode(encData) + '';\n\n        var delimiter = ',';\n        var quoteCharacter = '\"';\n        var csvArray = decData.split(\"\\r\\n\");\n\n        var index = 0\n        var result = [];\n        for (index = 0; index < csvArray.length; index++) {\n            var row = csvArray[index];\n            if (row) {\n            var csvParser = new sn_impex.CSVParser().parseLineToArray(row, delimiter, quoteCharacter);\n            var rowObject = {};\n            for (var i = 0; i < csvParser.length; i++) {\n                rowObject['column' + (i + 1)] = csvParser[i];\n            }\n            result.push(rowObject);\n            }\n        }\n        return result;\n    }\n}"
  },
  {
    "path": "Integration/Attachments/Calculate attachment hash code/README.md",
    "content": "This is example of recalculate the hash code using the glide digest getSHA256HexFromInputStream method.\n\nGlideDigest() -\n    This class provides methods for creating a message digest from strings or input streams using MD5, SHA1, or SHA256 hash algorithms.\n\nDocs link: https://developer.servicenow.com/dev.do#!/reference/api/tokyo/server/no-namespace/c_GlideDigestScopedAPI#r_SGDigest-GlideDigest\n\ngetSHA256HexFromInputStream - function takes GlideScriptableInputStream input stream as parameter."
  },
  {
    "path": "Integration/Attachments/Calculate attachment hash code/calculateHash.js",
    "content": "function calculateHash(attachmentId){\n    var attachmentStream = new GlideSysAttachment().getContentStream(attachmentId);\n    var gDigest = new GlideDigest();\n    var sha256Hash = gDigest.getSHA256HexFromInputStream(attachmentStream);\n    if (sha256Hash) {\n        gs.info(\"Hash code of the attachment file: \" + sha256Hash);\n    }\n}\n\n"
  },
  {
    "path": "Integration/Attachments/Convert KnowledgePage to PDF/Convert_KnowledgePage_to_PDF.js",
    "content": "// Convert Knowledge Page HTML to PDF and attach it.\n// Create PDF using GeneralPDF.\n// Try it from a simple HTML conversion.\n// It seems that the conversion fails if the HTML contains image files.\n// See the Scriptinclude GeneralPDF for details. \n// GeneralPDF is already in ScriptInclude, but it may not exist in some environments\n\nvar grKnow = new GlideRecord('kb_knowledge');\n// Get a simple HTML KnowledgePage\nif (grKnow.get('<KnowledgePage sys_id>') && grKnow.getValue('text')) {\n    // Create PDF Document.\n    var pdfDoc = new GeneralPDF.Document(null, null, null, null, null, null);\n    var document = new GeneralPDF(pdfDoc);\n    document.startHTMLParser();\n    document.addHTML(grKnow.getValue('text'));\n    document.stopHTMLParser();\n    // Create PDF Attachment.\n    var att = new GeneralPDF.Attachment();\n    att.setTableName(grKnow.getTableName());\n    att.setTableId(grKnow.getValue('sys_id'));\n    // Attached file name. \n    att.setName('TestPDF.pdf');\n    att.setType('application/pdf');\n    att.setBody(document.get());\n    // Attachment creation\n    GeneralPDF.attach(att);\n}\n"
  },
  {
    "path": "Integration/Attachments/Convert KnowledgePage to PDF/README.md",
    "content": "# Convert Knowledge Page HTML to PDF and attach it.\n* Create PDF using GeneralPDF.\n* Try it from a simple HTML conversion.\n* It seems that the conversion fails if the HTML contains image files.\n* See the Script Include GeneralPDF for details. \n* GeneralPDF is already in Script Include, but it may not exist in some environments\n\n```javascript\nvar grKnow = new GlideRecord('kb_knowledge');\n// Get a simple HTML KnowledgePage\nif(grKnow.get('<KnowledgePage sys_id>') && grKnow.getValue('text')){\n    // Create PDF Document.\n    var pdfDoc = new GeneralPDF.Document(null, null, null, null, null, null);\n    var document = new GeneralPDF(pdfDoc);\n    document.startHTMLParser();\n    document.addHTML(grKnow.getValue('text'));\n    document.stopHTMLParser();\n    // Create PDF Attachment.\n    var att = new GeneralPDF.Attachment();\n    att.setTableName(grKnow.getTableName());\n    att.setTableId(grKnow.getValue('sys_id'));\n    // Attached file name. \n    att.setName('TestPDF.pdf');\n    att.setType('application/pdf');\n    att.setBody(document.get());\n    // Attachment creation\n    GeneralPDF.attach(att);\n}\n```\n"
  },
  {
    "path": "Integration/Attachments/Create Attachments/Create attachment via script.js",
    "content": "//It can be used within a Business Rule - After Insert\n(function executeRule(current, previous /*null when async*/) {\n\n\tinsertAttachment();\n\n})(current, previous);\n\nfunction insertAttachment() {\n\n\tvar gsa = new GlideSysAttachment(); \n\tvar attachmentId = gsa.write(current, \"fileName.txt\", 'text/plain', \"some data\");\n\n}\n"
  },
  {
    "path": "Integration/Attachments/Create Attachments/README.md",
    "content": "## Create an Attachment via script\n\nYou can create a Business Rule (ie: After Insert) to automatically create an Attachment. \n\nIn this example we're creating a text file containing 'some data'. \n\nThe file name is 'fileName.txt'\n"
  },
  {
    "path": "Integration/Attachments/Delete RITM Attachment/README.md",
    "content": "The Delete RITM attachment business rule will automatically deletes the attachment on RITM, when the attachment is deleted on SCTASK.\n"
  },
  {
    "path": "Integration/Attachments/Delete RITM Attachment/deleteattachment.js",
    "content": "// To automatically delete RITM attachment, when an attachment is deleted on SCTASK.\n// Create a After Delete Business Rule on sys_attachment table, and set business rule condition as Table name is \"sctask\", so that when an attachment is deleted on SCTASK, this business rule will run.\n\nvar glideAttachment = new GlideRecord(\"sys_attachment\");\n    var glideTask = new GlideRecord('sc_task');\n    if (glideTask.get(current.table_sys_id)) {\n        glideAttachment.addEncodedQuery(\"table_name=sc_req_item^table_sys_id=\" + glideTask.request_item.sys_id + \"^file_name=\" + current.file_name);\n        glideAttachment.query();\n        if(glideAttachment.next())\n        \n\t\tvar attachment = new GlideSysAttachment();\n\t\t\tattachment.deleteAttachment(glideAttachment.getValue(\"sys_id\"));\n\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Integration/Attachments/ExportAttachmentsToMidServer/README.md",
    "content": "The snippet can be used to export all attachments within any record in ServiceNow to the mid server. You could specify a relative file path within the server's agent folder and it will copy them into it.\n\nSample Usage\n\nexportAttachmentsToMid(\"66a4daff2f9ff810ba1b52492799b6f1\", \"\\\\Incident\\\\INC00293930\", \"Mid Server 01\");\n"
  },
  {
    "path": "Integration/Attachments/ExportAttachmentsToMidServer/exportattachmentstomid.js",
    "content": "/**********************************************************************************************/\nInput\nrecordId - The sys_id of record whose attachments need to be exported\nfilePath - The relative file path where attachments be exported. All attachments will be exported into agent\\export\\ folder of your mid server\nmidServerName - Name of your mid server\n\nOutput\nAll attachments in the input record are exported into the relative file path specified. Eg if filePath provided is \\incident\\INC000234442\\ , the attachments inside INC000234442 will be copied over mid server path at \\agent\\export\\incident\\INC000234442\\\n\nNote: This can also create new folders automatically\n\n/**********************************************************************************************/\n\nfunction exportAttachmentsToMid(recordId, filePath, midServerName) {\n    var attachment = new GlideRecord(\"sys_attachment\");\n    if (attachment.get('table_sys_id', recordId)) {\n\n        //Create an entry in the ecc_agent_attachment table\n        var eccAttachment = new GlideRecord(\"ecc_agent_attachment\");\n        eccAttachment.newRecord();\n        eccAttachment.setValue(\"name\", \"Export Attachment\");\n        eccAttachment.setValue(\"short_description\", \"Exporting attachment of \" + recordId);\n        var eccAttachmentId = eccAttachment.insert();\n\n        //Copies all attachment from the record into the agent attachment table for export\n        GlideSysAttachment.copy(attachment.getValue(\"table_name\"), recordId, 'ecc_agent_attachment', eccAttachmentId);\n\n        //Access all the copied attachments and copy them one by one into the mid server file path\n        var copiedAttachments = new GlideRecord('sys_attachment');\n        copiedAttachments.addQuery('table_name', 'ecc_agent_attachment');\n        copiedAttachments.addQuery('table_sys_id', eccAttachmentId);\n        copiedAttachments.query();\n        while (copiedAttachments.next()) {\n            // Create XML Payload for ECC\n            var xmlString = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>' +\n                '<parameters>' +\n                '<parameter name=\\\"stream_relay_response_topic\\\" value=\\\"ExportSetResult\\\"/>' +\n                '<stream_relay_source attachment_sys_id=\\\"' + copiedAttachments.getUniqueValue() + '\\\" type=\\\"AttachmentSource\\\"/>' +\n                '<stream_relay_transform attachment.table_sys_id=\\\"' + eccAttachmentId + '\\\" order=\\\"0\\\" stream_relay_transfer_progress_interval=\\\"150\\\" type=\\\"AttachmentProgressTransformer\\\"/>' +\n                '<stream_relay_sink path=\\\"' + filePath + copiedAttachments.getValue(\"file_name\") + '\\\" type=\\\"FileSink\\\"/>' +\n                '</parameters>';\n\n            // Create ECC Record\n            var eccQueue = new GlideRecord('ecc_queue');\n            eccQueue.newRecord();\n            eccQueue.agent = 'mid.server.' + midServerName;\n            eccQueue.topic = 'StreamPipeline';\n            eccQueue.queue = 'output';\n            eccQueue.state = 'ready';\n            eccQueue.payload = xmlString;\n            eccQueue.insert();\n        }\n    } else {\n        return \"No attachments\";\n    }\n}\n"
  },
  {
    "path": "Integration/Attachments/ExportRecordsAnyFormat/README.md",
    "content": "The snippet can be used to export records from any table and then attach on another record. Records can be exported records from any table view based on an encoded query in any of the these formats: CSV, XLS, XLSX, PDF, JSON, XML, XSD, SCHEMA, RSS. If required, the attachment can be send out to users through a notification (with check include attachments).\n\n#### Access & Authentication\nA user with web service access and read access to the data needs to be present in the instance. The user must also have write access to the record where the file will be attached.\n\nIn the script there are two options for authentication:\n\n1. [Credentials](https://docs.servicenow.com/en-US/bundle/vancouver-platform-security/page/product/credentials/reference/credentials-getting-started.html) - preferred\n2. Using system properties to store username and password - less secure\n\n#### Input Format\n\n- tableName: Name of table whose records need to be exported\n- recordId: sys_id of the record where the exported attachment should be uploaded, this record must exist on the same table\n- recordQuery: encoded query to access the required records. For PDF files, you should only pass sys_id eg: sys_id=b3f076504750210042bd757f2ede273f\n- recordView: Specify the required view, Pass empty string for default view eg: ess, portal,\n- dataType: The required export format - Supported formats eg CSV, XLS, EXCEL, XLSX, PDF, JSONv2, XML, XSD, SCHEMA, RSS \n- fileName: Name of exported file along with its extension eg fileName.csv, fileName.json\n\nEXAMPLE: ```gs.print(exportRecords(tableName, recordId, recordQuery, recordView, dataType, fileName));```\n\n### Sample Usage\n\n#### Export all active incidents from the ESS view into XLSX format \n\n```javascript\ngs.print(exportRecords('incident', 'b3f076504750210042bd757f2ede273f', 'active=true', 'ess', 'XLSX', 'Active Incidents.xlsx'));\n```\n\n#### Export all active incident from the default view in JSON format\n\n```javascript\ngs.print(exportRecords('incident', 'b3f076504750210042bd757f2ede273f', 'active=true', '', 'JSONv2', 'Active Incidents.json'));\n```\n\n#### Export a record into PDF\n\n```javascript\ngs.print(exportRecords('incident', 'b3f076504750210042bd757f2ede273f', 'sys_id=b3f076504750210042bd757f2ede273f', '', 'PDF', 'record.pdf'));\n```\n\n\n"
  },
  {
    "path": "Integration/Attachments/ExportRecordsAnyFormat/exportRecords.js",
    "content": "/*\nINPUT:\ntableName - Name of table whose records need to be exported\nrecordId - sys_id of the record where the exported attachment should be uploaded\nrecordQuery - encoded query to access the required records. For PDF files, you should only pass sys_id eg: sys_id=b3f076504750210042bd757f2ede273f\nrecordView - Specify the required view, Pass empty string for default view eg: ess, portal,\ndataType - The required export format - Supported formats eg CSV, XLS, EXCEL, XLSX, PDF, JSONv2, XML, XSD, SCHEMA, RSS \nfileName - Name of exported file along with its extension eg fileName.csv, fileName.json\n\nOUTPUT:\nstatus - HTTP status of export\n200 = Successfully exported\n\nAccess - The user should have required access/roles to read the exported tables, and write to the record where the file will be attached\n\nAuthentication option #1 - preferred\nCreate a Basic Auth credential record and insert the sys_id of the Credential record in the script below\n  \nAuthentication option #2 - less secure\nCreate 2 system properties to store the user_name and password of a web service access user\npdf.export.user.id - user_name of the web service only access user\npdf.export.user.password - password of the user\n*/\n\nfunction exportRecords(tableName, recordId, recordQuery, recordView, dataType, fileName) {\n    var response;\n    var status;\n    var url;\n    try {\n        var restMessage = new sn_ws.RESTMessageV2();\n        restMessage.setHttpMethod('GET');\n        if(dataType == \"PDF\"){\n            //For PDF, sys_id should be passed as it accepts only a single record\n            url = gs.getProperty('glide.servlet.uri') + tableName + '.do?'+ dataType + '&' + recordQuery + '&sysparm_view='+recordView;\n        }else{\n            url = gs.getProperty('glide.servlet.uri') + tableName + '.do?'+ dataType + '&sysparm_query=' + recordQuery + '&sysparm_view='+recordView;\n        }\n        restMessage.setEndpoint(url);\n  \n        //Authentication option #1 - preferred\n        var credentialID = \"ef43c6d40a0a0b5700c77f9bf387afe3\"; //SYSID of the Credential record, REPLACE VALUE FROM YOUR INSTANCE\n        var provider = new sn_cc.StandardCredentialsProvider();\n        var credential = provider.getCredentialByID(credentialID);\n        restMessage.setBasicAuth(credential.getAttribute(\"user_name\"), credential.getAttribute(\"password\"));\n        \n        //Authentication option #2 - less secure\n        //restMessage.setBasicAuth(gs.getProperty('pdf.export.user.id'), gs.getProperty('pdf.export.user.password'));\n        \n        restMessage.saveResponseBodyAsAttachment(tableName, recordId, fileName);\n        response = restMessage.execute();\n        status = response.getStatusCode();\n        return status;\n    } catch (ex) {\n        gs.error(ex.getMessage());\n    }\n}\n"
  },
  {
    "path": "Integration/Attachments/Send Attachment to MID Server/README.md",
    "content": "This script helps you to copy file to MID Server/SFTP SErver via MID Server SCript Include. You can use this scripting to copy the file from ServiceNow Instance to MID Server or SFTP Server or any other Server. You just need to pass the parameters while calling this script like IP of Server, Server Authentication username and password. You can write this script in MID Server Script Include table in ServiceNow and you can call this script from any business rule with the help of JAvascript Probe. This is very useful script and can be used in scoped and global application. \n"
  },
  {
    "path": "Integration/Attachments/Send Attachment to MID Server/Send Attachment to MID Server.js",
    "content": "var AttachmentSftpUtils = Class.create();\nAttachmentSftpUtils.prototype = {\n\tinitialize: function () {\n\t\t\t// Set up the Packages references\n\t\t\n\t\tthis.File = Packages.java.io.File;\n\t\tthis.FileOutputStream = Packages.java.io.FileOutputStream;\n\t\t\n\t\t\n\t\t//get the parameters\n\t\t\n\t\tthis.relativeUrl = probe.getParameter(\"relativeUrl\");\n\t\tthis.domain = probe.getParameter(\"httpDomain\"); \n\t\tthis.midlogs = probe.getParameter(\"MidLogs\");\n\t\tthis.filename = probe.getParameter(\"filename\");\n\t\tthis.midserverfilepath = probe.getParameter(\"midserverpath\");\n\t\tthis.midserverfilename = probe.getParameter(\"filename\");\n\t\t//this.deleteAfterUpload = probe.getParameter(\"deleteAfterUpload\");\n\t\tthis.SnowMidUserName = probe.getParameter(\"SnowMidUsername\");\n\t\tthis.SnowMidPassword = probe.getParameter(\"SnowMidPassword\");\n\t\t\n\t\t//sftp\n\t\t\n\t\tthis.targetServer = probe.getParameter('sftpTargetServer');\n\t\tthis.targetUsername = probe.getParameter('sftpTargetUsername');\n\t\tthis.targetPassword = probe.getParameter('sftpTargetPassword');\n\t\tthis.targetPort = probe.getParameter('sftpTargetPort');\n\t\tthis.sftpFilePath = probe.getParameter('sftpFilePath');\n\t},\n\t\n\tprocessFileTransfer: function() {\n\t\t\n\t\t\tthis.log(\"ShammaSalhotra\");\n\t\t\n\t\t\n\t\tvar localFileName = this.midserverfilepath + \"/\" + this.midserverfilename;\n\t\t\n\t\ttry {\n\t\t\tvar fn = this.saveToFile(localFileName);\n\t\t\tthis.log(\"File is moved to MIDserver or not \"+ fn);\n\t\t\n\t\t\tif(fn){\n\t\t\t\tthis.log(\"Initiated file transfer from MID Server: \" + localFileName);\n\t\t\t\t\n\t\t\t\tvar fnc = this.sftpFile(this.targetServer, this.targetUsername, this.targetPassword, localFileName, this.sftpFilePath + this.midserverfilename);\n\t\t\t\tif(fnc!=\"\"){\n\t\t\t\t\t\n\t\t\t\t\treturn fnc;\n\t\t\t\t\t\n\t\t\t\t}\n\t\t\t\treturn \"file is moved to mid server\";\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tthis.log(\"ShammaSalhotra1\");\n\t\t\tthis.log(\"Error in writing file to SFTP server: \" + e);\n\t\t\t\n\t\t\treturn \"Error in writing file to SFTP server: \" + e;\n\t\t}\n\t\t\n\t},\n\t\tsaveToFile: function(targetPath) {\n\t\tvar tmpLoc;\n\t\tvar result = true;\n\t\t\n\t\t//var DPassword = new Packages.com.glide.util.Encrypter().decrypt(this.SnowMidPassword); // decrypting the password\n\t\tvar DPassword = this.SnowMidPassword;\n\t\t//Initiating the webservice to get the attachment from servicenow\n\t\t\n\t\tthis.HttpClients = Packages.com.glide.communications.HTTPRequest; // GlideHTTPRequest\n\t\t\n\t\tvar munewurl = this.relativeUrl;\n\t\t\n\t\t\n\t\tvar request = new this.HttpClients(munewurl);\n\t\trequest.setBasicAuth(this.SnowMidUserName,DPassword);\n\t\trequest.addHeader('Accept','application/json');\n\t\tvar response = request.get();\n\t\t\n\t\t\n\t\t\n\t\tvar status = response.getStatusCode();\n\t\t\n\t\tthis.log(\"Response \" + status);\n\t\ttmpLoc = targetPath;//this.midserverfilepath + this.midserverfilename;\n\t\t\n\t\tif( status == \"200\" ){\n\t\t\t// Save the attachment in midserver\n\t\t\t\n\t\t\ttry {\n\t\t\t\t\n\t\t\t\tthis.log(\"Saving the attachment to \" + this.filename +\"::\" + targetPath);\n\t\t\t\tvar f = new this.File(targetPath);\n\t\t\t\tvar inputStream  = response.getBodyAsStream();\n\t\t\t\tvar out = new this.FileOutputStream(f);\n\t\t\t\tvar buf = Packages.java.lang.reflect.Array.newInstance(Packages.java.lang.Byte.TYPE, 1024);\n\t\t\t\twhile ((len = inputStream.read(buf)) > 0) {\n\t\t\t\t\t\n\t\t\t\t\tout.write(buf, 0, len);\n\t\t\t\t}\n\t\t\t\tout.close();\n\t\t\t\t\n\t\t\t\tthis.log(\"File saved to: \" + f);\n\t\t\t\tinputStream.close();\n\t\t\t\t\n\t\t\t}catch (e) {\n\t\t\t\t\n\t\t\t\tresult = false;\n\t\t\t}\n\t\t}\n\t\telse{\n\t\t\t\n\t\t\tresult = false;\n\t\t}\n\t\treturn result;\n\t},\n\t\n\t\n\tsftpFile : function(hostName, userName, password, localFileName, remoteFileName) {\n\t\t// Initiate the file transfer from midserver to sftp server\n\t\tthis.log(\"Shamma\");\n\t\tvar result = \"\";\n\t\tthis.log('sftpFile(): attempting to connect to ' + hostName);\n\t\tvar ssh = new Packages.com.sshtools.j2ssh.SshClient();\n\t\tvar ignoreHost = new Packages.com.sshtools.j2ssh.transport.IgnoreHostKeyVerification();\n\t\tif (!this.targetPort){\n\t\t\tthis.targetPort = 22;\n\t\t}\n\t\tthis.log('sftpFile(): attempting to connect to ' + hostName + \" on port \" + this.targetPort);\n\t\tssh.connect(hostName, this.targetPort, ignoreHost);\n\t\t\n\t\tvar pwd = new Packages.com.sshtools.j2ssh.authentication.PasswordAuthenticationClient();\n\t\t//var authPassword = new Packages.com.glide.util.Encrypter().decrypt(password);\n\t\t//var tarpassword = this.targetPassword;\n\t\tpwd.setUsername(userName);\n\t\tpwd.setPassword(password);\n\t\t\n\t\t\n\t\tthis.log('sftpFile(): attempting to copy ' + localFileName + ' to ' + remoteFileName);\n\t\tif(ssh.authenticate(pwd) == new Packages.com.sshtools.j2ssh.authentication.AuthenticationProtocolState().COMPLETE) {\n\t\t\tsftp = ssh.openSftpClient();\n\t\t\t\n\t\t\ttry {\n\t\t\t\tsftp.put(localFileName, remoteFileName);\n\t\t\t\tthis.log(\"File successfully uploaded to sftp \" + remoteFileName);\n\t\t\t\t\n\t\t\t\tresult = \"File successfully uploaded to sftp\";\n\t\t\t\t\n\t\t\t\tif (this.deleteAfterUpload == \"true\") {\n\t\t\t\t\tthis.log(\"deleteAfterUpload -> \" + this.deleteAfterUpload + \", deleting local file...\");\n\t\t\t\t\tnew this.File(localFileName)[\"delete\"]();\n\t\t\t\t}\n\t\t\t} catch(e) {\n\t\t\t\tthis.log('FILE NOT FOUND ' + remoteFileName + ' or error: ' + e);\n\t\t\t\tresult = 'FILE NOT FOUND ' + remoteFileName + ' or error: ' + e;\n\t\t\t}\n\t\t\tsftp.quit();\n\t\t\ttry{\n\t\t\t\t// kill connection\n\t\t\t\tssh.disconnect();\n\t\t\t}\n\t\t\tcatch(e){\n\t\t\t\tthis.log('Manual connection kill not successful with error: ' + e);\n\t\t\t\tresult = 'Manual connection kill not successful with error: ' + e;\n\t\t\t}\n\t\t}\n\t\telse {\n\t\t\tresult = 'Error ' + new Packages.com.sshtools.j2ssh.authentication.AuthenticationProtocolState().COMPLETE;\n\t\t}\n\t\t\n\t\treturn result;\n\t\t\n\t},\n\t\n\tlog: function(data) {\n\t\tif (this.midlogs == \"true\") {\n\t\t\tms.log(data);\n\t\t}\n\t},\n\t\n\ttype: AttachmentSftpUtils\n};\n"
  },
  {
    "path": "Integration/Attachments/Show RITM has Attachments/README.md",
    "content": "This feature requires a \"Display\" Business rule on [sc_task] to pass true/false to the scratchpad and then an \"onLoad\" Client Script to display a message on the Catalog Task form if the RITM has attachments.\n\nThe result is a message on the Catalog Task form (below the RITM field) indicating when a RITM has attachments.\n"
  },
  {
    "path": "Integration/Attachments/Show RITM has Attachments/ShowRITMhasAttachment_BR.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Check RITM for attachments and pass True/False to client via scratchpad\n\tvar grAttachment = new GlideRecord('sc_req_item');\n\tgrAttachment.addQuery('sys_id', current.request_item);\n\tgrAttachment.query();\n\tif (grAttachment.next()){\n\t\t\tg_scratchpad.hasAttachments = grAttachment.hasAttachments();\n\t}\n\t\n})(current, previous);\n"
  },
  {
    "path": "Integration/Attachments/Show RITM has Attachments/ShowRITMhasAttachment_CS.js",
    "content": "function onLoad() {\n\tif (g_scratchpad.hasAttachments == true) {\n\t\tg_form.showFieldMsg('request_item', \"Contains attachment(s)\",'info');\n\t}\n}\n"
  },
  {
    "path": "Integration/Attachments/attachmentToXMLParse/README.md",
    "content": "How to use this script:\n\n1. Create new Script Include named as \"getXMLContent\"\n2. Add the script provided in the code.js script\n\nHow to Test this script:\n\n1. Create Data source XML Type\n2. Attach a xml attachment to it\n3. Use the script include in below format\n4. Pass the Data source table name and sysid of the data source in the function\n\nThis script block will extract the XML content from the ServiceNow XML attachment. Below is an example:\n\n```var xmlContent = new getXMLContent();```\n\n```gs.print(xmlContent.getXMLContentFromAttachment('sys_data_source',<sys_id of the record where attachment attached>));```\n"
  },
  {
    "path": "Integration/Attachments/attachmentToXMLParse/code.js",
    "content": "  getXMLContentFromAttachment: function(attachmentTableName,tableSysId) {\n        var gsa = new GlideSysAttachment();\n\n        var bytesInFile = gsa.getBytes(attachmentTableName, tableSysId); // Sysid of the data source\n\n        var originalContentsInFile = Packages.java.lang.String(bytesInFile); // originalContentsInFile\n\n        originalContentsInFile = String(originalContentsInFile);\n\n        var helper = new XMLHelper(originalContentsInFile);\n        var obj = helper.toObject();\n\n        return obj;\n\n    }\n"
  },
  {
    "path": "Integration/AzureAD Integration/Dynamically create reference records/Load_cmn_location.js",
    "content": "/***********************************************\nCONTEXT: https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0779148\n\nFunctions:\n\n* locationExists(location) \n\t- Parameters : \"location\" - string of cmn_location name to search for\n\t- Logic: query cmn_location for existing name using temp location string as a parameter.\n\t- Outputs: sys_id of corresponding cmn_location record OR null/falsey value if none found\n\n* createLocation(location)\n\t- Parameters : \"location\" - string of cmn_location name to create\n\t- Logic: initialize cmn_location record using temp location string as a parameter for the name value\n\t- Outputs: sys_id of new cmn_location record\n************************************************/\n\n\n(function executeRule(current, previous /*null when async*/) {\n\t//Checks for location using locationExists, if false call createLocation passing the \n\t//name of the location as a parameter\n\tcurrent.location = locationExists(current.u_temp_location) || createLocation(current.u_temp_location);\n\t\n\t//Queries location table for record that matches the name passed in\n\t//If query returns results return true, else return false\n\tfunction locationExists(location){\n\t\tvar gr = new GlideRecord(\"cmn_location\");\n\t\tgr.addQuery(\"name\", location);\n\t\tgr.query();\n\t\tif(gr.next())\n\t\t   return gr.sys_id;\n\t\telse\n\t\t\treturn undefined;\n\t}\n\t//Creates record on cmn_location using the passed parameter as the name\n\tfunction createLocation(location){\n\t\tvar gr = new GlideRecord(\"cmn_location\");\n\t\tgr.initialize();\n\t\tgr.name = location;\n\t\tgr.parent = '8201c34fac1d55eb36e59da730b7d035';//Global as parent\n\t\treturn gr.update();\n\t}\n})(current, previous);"
  },
  {
    "path": "Integration/AzureAD Integration/Dynamically create reference records/README.md",
    "content": "******* READ ME FOR AZURE USER PROVISIONING TO MAINTAIN REFERENCE FIELDS/TABLES ***** https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0779148\n\nBecause the Microsoft provided Azure Enterprise intergation with servicenow only creates records on the sys_user and sys_user_group tables this removes our ability to maintain the fields/tables that are referenced by these records (core tables).\n\nThis typically would be handled by a \"Choice Action\" of \"create\" on a transform map (in comparision to a typical LDAP user provisioning configuration).\n\nIn order to regain this functionality we must change the attribute mapping from within the Azure admin panel to map the attributes which correspond to these reference values to custom string fields on the ServiceNow side. (i.e u_department) once we are mapping these values to a custom string field we will manually add the \"Find/Create\" logic within a business rule configured to fire anytime these custom field values change.\n\nSudo logic is as follows(u_location example):\n\nif u_location changes query for record on cmn_location with the same name if found result return sys_id if no result intitialize gliderecord, insert return new record sys_id\n\n//relate returned sys_id in OOB reference field current.location = result.sys_id"
  },
  {
    "path": "Integration/Data Export to ML Pipeline/Export Data for ML Training/README.md",
    "content": "# Export ServiceNow Data to ML Pipeline\n\n## Overview\nThis snippet shows how to export incident data from ServiceNow and feed it into an external ML pipeline for analysis and predictions.\n\n## What It Does\n- **Script Include**: Queries incidents from ServiceNow\n- **Scripted REST API**: Exposes data as JSON endpoint\n- **Python Script**: Consumes data, preprocesses it, and runs basic ML operations\n- **Result Storage**: Sends predictions back to ServiceNow\n\n## Use Cases\n- Predict incident resolution time\n- Classify tickets automatically\n- Detect anomalies in service data\n- Smart assignment recommendations\n\n## Files\n- `data_export_script_include.js` - Server-side Script Include to query incident data\n- `export_data_rest_api.js` - Scripted REST API to expose data as JSON endpoint\n\n## How to Use\n1. Create a Script Include in ServiceNow named `MLDataExporter` using `data_export_script_include.js`\n2. Create a Scripted REST API with base path `/api/ml_export` and resource `/incidents` using `export_data_rest_api.js`\n3. Call the endpoint: `GET /api/ml_export/incidents?limit=100`\n4. External ML systems can fetch formatted incident data via this REST endpoint\n\n## Requirements\n- ServiceNow instance with REST API access\n- Python 3.8+ with requests library\n- API credentials (username/password or OAuth token)\n"
  },
  {
    "path": "Integration/Data Export to ML Pipeline/Export Data for ML Training/data_export_script_include.js",
    "content": "// Script Include: MLDataExporter\n// Purpose: Query incident data for ML pipeline consumption\n// Usage: var exporter = new MLDataExporter(); var data = exporter.getIncidentData(limit);\n\nvar MLDataExporter = Class.create();\nMLDataExporter.prototype = {\n    initialize: function() {},\n    \n    // Extract incident records suitable for ML training\n    getIncidentData: function(limit) {\n        limit = limit || 100;\n        var incidents = [];\n        \n        // Query incidents from database\n        var gr = new GlideRecord('incident');\n        gr.addQuery('active', 'true');\n        gr.addQuery('state', '!=', ''); // exclude blank states\n        gr.setLimit(limit);\n        gr.query();\n        \n        while (gr.next()) {\n            // Extract fields relevant for ML analysis\n            incidents.push({\n                id: gr.getValue('sys_id'),\n                description: gr.getValue('description'),\n                short_description: gr.getValue('short_description'),\n                category: gr.getValue('category'),\n                priority: gr.getValue('priority'),\n                impact: gr.getValue('impact'),\n                urgency: gr.getValue('urgency'),\n                state: gr.getValue('state'),\n                created_on: gr.getValue('sys_created_on'),\n                resolution_time: this._calculateResolutionTime(gr)\n            });\n        }\n        \n        return incidents;\n    },\n    \n    // Calculate resolution time in hours (useful ML feature)\n    _calculateResolutionTime: function(gr) {\n        var created = new GlideDateTime(gr.getValue('sys_created_on'));\n        var resolved = new GlideDateTime(gr.getValue('sys_updated_on'));\n        var diff = GlideDateTime.subtract(created, resolved);\n        return Math.abs(diff / (1000 * 60 * 60)); // convert to hours\n    },\n    \n    type: 'MLDataExporter'\n};\n"
  },
  {
    "path": "Integration/Data Export to ML Pipeline/Export Data for ML Training/export_data_rest_api.js",
    "content": "// Scripted REST API Resource: ML Data Export\n// Base Path: /api/ml_export\n// Resource Path: /incidents\n// HTTP Method: GET\n// Parameters: ?limit=100&offset=0\n\n(function process(request, response) {\n    try {\n        // Get query parameters\n        var limit = request.getParameter('limit') || 100;\n        var offset = request.getParameter('offset') || 0;\n        \n        // Use the Script Include to fetch data\n        var exporter = new MLDataExporter();\n        var incidents = exporter.getIncidentData(limit);\n        \n        // Prepare response with metadata\n        var result = {\n            status: 'success',\n            count: incidents.length,\n            data: incidents,\n            timestamp: new GlideDateTime().toString()\n        };\n        \n        response.setContentType('application/json');\n        response.setStatus(200);\n        response.getStreamWriter().writeString(JSON.stringify(result));\n        \n    } catch (error) {\n        // Error handling for ML pipeline\n        response.setStatus(500);\n        response.setContentType('application/json');\n        var error_response = {\n            status: 'error',\n            message: error.toString(),\n            timestamp: new GlideDateTime().toString()\n        };\n        response.getStreamWriter().writeString(JSON.stringify(error_response));\n        gs.log('ML Export API Error: ' + error.toString(), 'MLDataExport');\n    }\n})(request, response);\n"
  },
  {
    "path": "Integration/GraphQL Integration API/Incident GraphQL resolvers/CI Resolver.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\n    ci = env.getSource(); // This will return the sys_id of CI.\n\tvar now_gr = new GlideRecord('cmdb_ci');\n\tnow_gr.addQuery('sys_id',ci);  // This will check, if the sys_id exist or not.\n\tnow_gr.query();\n\treturn now_gr;  // This will return the whole CI record.\n\n})(env);\n"
  },
  {
    "path": "Integration/GraphQL Integration API/Incident GraphQL resolvers/GraphQL schema.js",
    "content": "schema {\n    query: Query\n}\n\ntype Query {\n\tEntersysId(sys_id:ID!):incident  // Entery point of data, you can define your own as per your requirement.\n}\ntype incident{\n\tsys_id:DisplayableString  // This is the type of data.\n\tnumber:DisplayableString\n\tshort_description:DisplayableString\n\tcaller_id:User @source(value:\"caller_id.value\")  // @source is used for extended tables, here caller_id extends to sys_user table.\n\tcmdb_ci:CI @source(value:\"cmdb_ci.value\")  //@source is used for extended tables, here configuration item extends to cmdb_ci table.\n}\ntype User{\n\tsys_id:DisplayableString\n\tuser_name:DisplayableString   // This will return the information of user (caller_id).\n\tfirst_name:DisplayableString\n\tlast_name:DisplayableString\n}\ntype CI{\n\tsys_id:DisplayableString\n\tinstall_status:DisplayableString  // This will return the information of configuration item (CI).\n\tname:DisplayableString\n}\ntype DisplayableString{\n\tvalue:String  \\\\ This will return the value.\n\tdisplay_value:String  \\\\This will return the display value.\n}\n"
  },
  {
    "path": "Integration/GraphQL Integration API/Incident GraphQL resolvers/Incident Resolver.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\n    var sysid = env.getArguments().sys_id; // This will return the sys_id of incident record.\n\tvar now_gr = new GlideRecord('incident');\n\tnow_gr.addQuery('sys_id',sysid); // This will check, if the sys_id exist or not.\n\tnow_gr.query();\n\treturn now_gr;  // This will return the whole incident record.\n\n})(env);\n"
  },
  {
    "path": "Integration/GraphQL Integration API/Incident GraphQL resolvers/README.md",
    "content": "I have created a graphql API, which helps to get the details of incident record with details of caller and configuration item. I have defined the schema which is basically entry point of data after that, created resolvers which defines the logic of graphql schema that how you will get the values from server.\nUser Resolver: It will help to get the details of caller_id like (sys_id, first_name, last_name etc.) from the server.\nCI Resolver: It will help to get the details of configuration item like (sys_id, operational_status etc.)from the server .\nIncident Resolver: It will help to get the details of incident from the server (like short_description, description etc.) based on provided sys_id in schema .\n"
  },
  {
    "path": "Integration/GraphQL Integration API/Incident GraphQL resolvers/User Resolver.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\n   var user = env.getSource(); // This will return the sys_id of caller_id.\n\tvar now_gr = new GlideRecord('sys_user');\n\tnow_gr.addQuery('sys_id',user);  // This will check, if the sys_id exist or not.\n\tnow_gr.query();\n\treturn now_gr; // This will return the whole incident record.\n\n})(env);\n"
  },
  {
    "path": "Integration/ITSM/Bulk task_ci REST API/README.md",
    "content": "Purpose: \n\nAllow bulk task_ci record inserts via a custom rest API endpoint. This will allow integration apps to insert task_ci relationships en masse on task records without needing to make an inordinate number of task_ci table API calls. Additionally, we can provide logic and filtering capabilities beyond the table API to make sure the right CI is associated to the task.\n\nEndpoint:\n\nhttps://<<<YOUR_INSTANCE_NAME>>>.service-now.com/api/gmi/related_live_ci_to_task\n\nPayload Details:\n\nUser posts a JSON object with the task number and an array of CIs to associate to that task. Methods to identify the CIs being inserted can be sys_id, serial, FQDN, name, and IP, with that order of preference. Typically, we will only expect 1 identifier per CI line. fields in the post body:\n\nTask: Task number that we should associate the CIs to. Required field, fail and return error code if this is not populated.\n\nPost Body Example. Note that this is to illustrate the multiple ways users can identify CIs.\n\n\n{\n  \"task\": \"INC123\",\n  \"query_string\": \"status=1\",\n  \"cis\": [\n    {\n      \"name\": \"p3acme1\"\n    },\n    {\n      \"serial\": \"123\"\n    },\n    {\n      \"sys_id\": \"abc123\"\n    },\n    {\n      \"ip\": \"123.0.0.1\"\n    },\n    {\n      \"FQDN\": \"p2acme.secureserver.net\"\n    }\n  ]\n}\n"
  },
  {
    "path": "Integration/ITSM/Bulk task_ci REST API/liveCItoTAsk.js",
    "content": "(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\n    var numInserted;\n    var reqBody = request.body.data;\n    var a = JSON.stringify(reqBody);\n    var parser = new JSONParser();\n    var parsed = parser.parse(a);\n    var str = '';\n    var str1 = '';\n    var j;\n    var qstring = reqBody.query_string;\n    if (qstring != '') {\n        var cmdb = new GlideRecord('cmdb_ci');\n        cmdb.addEncodedQuery(qstring);\n        cmdb.query();\n        var item_access = [];\n        while (cmdb.next()) {\n\n            item_access.push(cmdb.getValue(\"sys_id\"));\n\n        }\n\n\n    }\n\n    var task = new GlideRecord('task');\n    task.addQuery('number', reqBody.task);\n    task.query();\n    gs.info(\"TAsk reow co \" + task.getRowCount());\n    if (task.getRowCount() < 1) {\n\n        response.setError(\"Please provide valid TASK number\");\n        return;\n\n    }\n    if (task.next()) {\n\n\n        var task_id = task.sys_id;\n\n\n\n    }\n\n    if (item_access.length > 0) {\n\n\n        var cd = new GlideRecord('cmdb_ci');\n        cd.addEncodedQuery(qstring);\n        cd.query();\n        gs.info(\"NOF of recdd \" + qstring);\n        while (cd.next()) {\n            var grDS5 = new GlideRecord(\"task_ci\");\n            grDS5.initialize();\n            grDS5.setValue(\"ci_item\", cd.sys_id);\n            grDS5.setValue(\"task\", task_id);\n            grDS5.insert();\n        }\n\n\n\n    }\n\n\n\n    //gs.info(\"NOF of re \" + item_access.length);\n    //gs.info(\"Enoded Query \" + 'name=' + reqBody.cis[j].name + 'ORserial_number=' + reqBody.cis[j].serial + 'ORsys_id=' + reqBody.cis[j].sys_id + 'ORip_address=' + reqBody.cis[j].ip + 'ORfqdn=' + reqBody.cis[j].FQDN);\n    for (j = 0; j < reqBody.cis.length; j++) {\n\n        if (item_access.length == null) {\n\n            if (reqBody.cis[j].name != null) {\n                var c = new GlideRecord('cmdb_ci');\n                c.addEncodedQuery('name=' + reqBody.cis[j].name);\n                c.addQuery('u_cmdb_ci_status', '40c951a34f1cfa00dc4927201310c73b');\n                c.query();\n                gs.info(\"NOF of recc \" + c.getRowCount());\n                while (c.next()) {\n                    var grDS = new GlideRecord(\"task_ci\");\n                    grDS.initialize();\n                    grDS.setValue(\"ci_item\", c.sys_id);\n                    grDS.setValue(\"task\", task_id);\n                    grDS.insert();\n                }\n            }\n\n            if (reqBody.cis[j].serial != null) {\n                var c1 = new GlideRecord('cmdb_ci');\n                c1.addEncodedQuery('serial_number=' + reqBody.cis[j].serial);\n                c1.addQuery('u_cmdb_ci_status', '40c951a34f1cfa00dc4927201310c73b');\n                c1.query();\n                gs.info(\"NOF of recc \" + c1.getRowCount());\n                while (c1.next()) {\n                    var grDS1 = new GlideRecord(\"task_ci\");\n                    grDS1.initialize();\n                    grDS1.setValue(\"ci_item\", c1.sys_id);\n                    grDS1.setValue(\"task\", task_id);\n                    grDS1.insert();\n                }\n            }\n\n            if (reqBody.cis[j].sys_id != null) {\n                var c2 = new GlideRecord('cmdb_ci');\n                c2.addEncodedQuery('sys_id=' + reqBody.cis[j].sys_id);\n                c2.addQuery('u_cmdb_ci_status', '40c951a34f1cfa00dc4927201310c73b');\n                c2.query();\n                gs.info(\"NOF of recc \" + c2.getRowCount());\n                while (c2.next()) {\n                    var grDS2 = new GlideRecord(\"task_ci\");\n                    grDS2.initialize();\n                    grDS2.setValue(\"ci_item\", c2.sys_id);\n                    grDS2.setValue(\"task\", task_id);\n                    grDS2.insert();\n                }\n            }\n\n            if (reqBody.cis[j].ip != null) {\n                var c3 = new GlideRecord('cmdb_ci');\n                c3.addEncodedQuery('ip_address=' + reqBody.cis[j].ip);\n                c3.addQuery('u_cmdb_ci_status', '40c951a34f1cfa00dc4927201310c73b');\n                c3.query();\n                gs.info(\"NOF of recc \" + c3.getRowCount());\n                while (c3.next()) {\n                    var grDS3 = new GlideRecord(\"task_ci\");\n                    grDS3.initialize();\n                    grDS3.setValue(\"ci_item\", c3.sys_id);\n                    grDS3.setValue(\"task\", task_id);\n                    grDS3.insert();\n                }\n            }\n\n            if (reqBody.cis[j].FQDN != null) {\n                var c4 = new GlideRecord('cmdb_ci');\n                c4.addEncodedQuery('fqdn=' + reqBody.cis[j].FQDN);\n                c4.addQuery('u_cmdb_ci_status', '40c951a34f1cfa00dc4927201310c73b');\n                c4.query();\n                gs.info(\"NOF of recc \" + c4.getRowCount());\n                while (c4.next()) {\n                    var grDS4 = new GlideRecord(\"task_ci\");\n                    grDS4.initialize();\n                    grDS4.setValue(\"ci_item\", c4.sys_id);\n                    grDS4.setValue(\"task\", task_id);\n                    grDS4.insert();\n                }\n            }\n\n\n\n        }\n\n\n\n    }\n\n    response.setBody({\n        \"code\": \"CIs created sucessfully\",\n\n    });\n    response.setContentType(\"application/json\");\n    response.setStatus(200);\n\n\n\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Import Set API/Attachment Handler/README.md",
    "content": "**Attachment parser for Import Set API**\n\nWhen Json payload comes in the staging table then payload \":\" gets converted \"=\" due to which it becomes challenging to parse the encoded version of attachment, so, to get rid of this problem \nI have written the script include which parses the attachment correctly and inserts the same in the table.\n"
  },
  {
    "path": "Integration/Import Set API/Attachment Handler/attachmentParser.js",
    "content": "var ashParser;\n    var attachMetaData = attachmentParserUtil.attachmentHandler(source.u_attachment);\n\n    try {\n        /*\n        This checks if Attachment attribute ends with ']' then it is multi attachment else single attachment which requires parsing directly\n        */\n        if (source.u_attachment.endsWith(\"]\")) {\n\n            for (var ash in attachMetaData) {\n\n                ashParser = JSON.parse(attachMetaData[ash]);\n\n                attachmentParserUtil.attachmentDecoder(target, ashParser[\"file_name\"], ashParser[\"file_type\"], ashParser[\"file_content\"]);\n\n            }\n\n\n        } else {\n/*\n        This checks if Attachment attribute ends with '}' then it is single attachment \n*/\n            ashParser = JSON.parse(attachMetaData);\n\n            attachmentParserUtil.attachmentDecoder(target, ashParser[\"file_name\"], ashParser[\"file_type\"], ashParser[\"file_content\"]);\n\n        }\n    } catch (e) {\n        log.error(e);\n    }\n"
  },
  {
    "path": "Integration/Import Set API/Attachment Handler/attachmentParserUtil.js",
    "content": "var attachmentParserUtil = Class.create();\n\nattachmentParserUtil.attachmentDecoder = function(target, filename, filetype, attachment_base64) {\n\n    var attachment = new GlideSysAttachment();\n\n    var decodedBytes = GlideStringUtil.base64DecodeAsBytes(attachment_base64);\n\n    var id = attachment.write(target, filename, filetype, decodedBytes);\n    return;\n\n};\n\nattachmentParserUtil.attachmentEncoder = function(attachmentID) {\n\t\n    try {\n        var gsa, binData, file_name = [],\n        file_type = [],\n        file_content = [];\n        var attachFetch = new GlideRecord(\"sys_attachment\");\n        attachFetch.addQuery(\"table_sys_id\", attachmentID).addCondition(\"<filter>\");\n        attachFetch.query();\n        while (attachFetch.next()) {\n\n            file_name.push(attachFetch.file_name.toString());\n            file_type.push(attachFetch.content_type.toString());\n            gsa = new GlideSysAttachment();\n            binData = gsa.getBytes(attachFetch);\n            file_content.push(GlideStringUtil.base64Encode(binData));\n        }\n\nreturn JSON.stringify({\n            'file_name': file_name,\n            'file_content': file_content,\n            'file_type': file_type\n});\n\t\t\n// return GlideStringUtil.base64Encode(binData);\n\t\t\n    } catch (error) {\n\n        gs.error(error);\n    }\n\n\n};\n\nattachmentParserUtil.attachmentHandler = function(attachment) {\n\n    if (attachment.endsWith(\"]\")) {\n\n        return attachmentParserUtil.attachmentMultiAttachment(attachment);\n    } else if (attachment.endsWith(\"}\")) {\n\n        return attachmentParserUtil.attachmentSingleAttachment(attachment);\n\n    }\n};\n\nattachmentParserUtil.attachmentSingleAttachment = function(attachment) {\n\n    var attachDetails = {};\n\n    var bracketRemove = attachment.lastIndexOf(\"}\");\n\n    var attachmentNew = attachment.slice(1, bracketRemove);\n\n    attachmentNew = attachmentNew.split(\",\");\n\n    for (var attach in attachmentNew) {\n\n        attachDetails[attachmentNew[attach].split(\"=\")[0].trim()] = attachmentNew[attach].split(\"=\")[1].toString();\n        if (attachmentNew[attach].split(\"=\")[0].trim() == \"file_content\") {\n\n            attachDetails[attachmentNew[attach].split(\"=\")[0].trim(\"\")] = attachmentNew[attach].substr(attachmentNew[attach].indexOf(\"=\") + 1);\n        }\n\n    }\n    return JSON.stringify(attachDetails, null, 3);\n\n};\n\nattachmentParserUtil.attachmentMultiAttachment = function(attachment) {\n    var attachmentArray = [],\n        finalArray = [];\n    var attachDetails = {};\n\n    var bracketRemove = attachment.lastIndexOf(\"]\");\n\n    var attachmentNew = attachment.slice(1, bracketRemove);\n\n    attachmentNew = attachmentNew.split(\"},\");\n\n\n    for (var outerLoop in attachmentNew) {\n\n        var outerArray = attachmentNew[outerLoop].trim();\n\n        outerArray = outerArray.slice(1).split(\",\");\n\n\n\n        for (var x in outerArray) {\n\n            attachDetails[outerArray[x].split(\"=\")[0].trim()] = outerArray[x].split(\"=\")[1];\n            if (outerArray[x].split(\"=\")[0].trim() == \"file_content\") {\n\t\t\t\t\n                if (outerArray[x].substr(outerArray[x].indexOf(\"=\") + 1).endsWith(\"}\")) {\n\n                    var cont = outerArray[x].substr(outerArray[x].indexOf(\"=\") + 1);\n\n\t\t\t\t\t\tcont = cont.substr(0, cont.length - 1);\n\t\t\t\t\t\n                    attachDetails[outerArray[x].split(\"=\")[0].trim(\"\")] = cont;\n                }\n            }\n\n        }\n\n      attachmentArray.push(JSON.stringify(attachDetails, null, 4));\n    }\n\n    return attachmentArray;\n\n};\n\n\n\nattachmentParserUtil.attachmentSingleEncoder = function(attachmentID) {\n\t\n    try {\n        var gsa, binData, file_name = [],\n        file_type = [],\n        file_content = [];\n        var attachFetch = new GlideRecord(\"sys_attachment\");\n        attachFetch.addQuery(\"table_sys_id\", attachmentID).addCondition(\"<filter>\");\n\t\tattachFetch.orderByDesc('sys_created_on');\n\t\tattachFetch.setLimit(1);\n        attachFetch.query();\n        while (attachFetch.next()) {\n\n            file_name.push(attachFetch.file_name.toString());\n            file_type.push(attachFetch.content_type.toString());\n            gsa = new GlideSysAttachment();\n            binData = gsa.getBytes(attachFetch);\n            file_content.push(GlideStringUtil.base64Encode(binData));\n        }\n\nreturn JSON.stringify({\n            'file_name': file_name,\n            'file_content': file_content,\n            'file_type': file_type\n});\n} catch (error) {\n\n        gs.error(error);\n    }\n\n\n};\n\n"
  },
  {
    "path": "Integration/Import Sets/Import sets overview/ModelManufacture.README.md",
    "content": "When importing or processing Configuration Items (CIs), especially hardware assets, missing model or manufacturer data can cause CI creation failures or incomplete relationships.\nThis script handles that automatically by:\n* Checking if a manufacturer already exists in the core_company table.\n* Checking if a model already exists in the cmdb_model hierarchy.\n* Creating the manufacturer and/or model records if they are missing.\n\nHow It Works\n\n1. Extracts model and manufacturer names from the data source (source.u_model and source.u_manufacturer).\n2. Calls getOrCreateManufacturer():\n    * Searches the core_company table by name.\n    * Creates a new company record if not found.\n3. Calls getOrCreateModel():\n    * Searches the cmdb_model table (including child tables).\n    * If no match exists, inserts a new record in cmdb_hardware_product_model.\n4. Logs each action (found, created, or failed) for debugging and auditing.\n"
  },
  {
    "path": "Integration/Import Sets/Import sets overview/ModelManufacture.js",
    "content": "  var modelName = source.u_model;\n    var manufacturerName = source.u_manufacturer;\n \nfunction getOrCreateModel(modelName, manufacturerName) {\n        try {\n            var manufacturerSysId = getOrCreateManufacturer(manufacturerName);\n            if (!manufacturerSysId) {\n                gs.error(\"MODEL SCRIPT: Failed to find or create manufacturer: \" + manufacturerName);\n                return null;\n            }\n\n            // Query cmdb_model to check if any existing model (including child tables) exists\n            var modelGr = new GlideRecord('cmdb_model');\n            modelGr.addQuery('name', modelName);\n            modelGr.addQuery('manufacturer', manufacturerSysId);\n            modelGr.query();\n\n            if (modelGr.next()) {\n                gs.info(\"MODEL SCRIPT: Found existing model: \" + modelGr.getUniqueValue());\n                return modelGr.getUniqueValue();\n            } else {\n                // Create in child table: cmdb_hardware_product_model\n                var newModel = new GlideRecord('cmdb_hardware_product_model');\n                newModel.initialize();\n                newModel.setValue('name', modelName);\n                newModel.setValue('manufacturer', manufacturerSysId);\n                var newModelSysId = newModel.insert();\n\n                if (newModelSysId) {\n                    gs.info(\"MODEL SCRIPT: Created new hardware model: \" + newModelSysId);\n                    return newModelSysId;\n                } else {\n                    gs.error(\"MODEL SCRIPT: Failed to insert new model for \" + modelName);\n                    return null;\n                }\n            }\n        } catch (e) {\n            gs.error(\"MODEL SCRIPT: Error in getOrCreateModel(): \" + (e.message || e));\n            return null;\n        }\n    }\n\n    function getOrCreateManufacturer(name) {\n        try {\n            var companyGr = new GlideRecord('core_company');\n            companyGr.addQuery('name', name);\n            companyGr.query();\n\n            if (companyGr.next()) {\n                gs.info(\"MODEL SCRIPT: Found manufacturer: \" + companyGr.getUniqueValue());\n                return companyGr.getUniqueValue();\n            } else {\n                companyGr.initialize();\n                companyGr.setValue('name', name);\n                //companyGr.setValue('manufacturer', true); // Ensure it’s marked as manufacturer\n                var newMfrSysId = companyGr.insert();\n\n                if (newMfrSysId) {\n                    gs.info(\"MODEL SCRIPT: Created new manufacturer: \" + newMfrSysId);\n                    return newMfrSysId;\n                } else {\n                    gs.error(\"MODEL SCRIPT: Failed to insert new manufacturer: \" + name);\n                    return null;\n                }\n            }\n        } catch (e) {\n            gs.error(\"MODEL SCRIPT: Error in getOrCreateManufacturer(): \" + (e.message || e));\n            return null;\n        }\n    }\n"
  },
  {
    "path": "Integration/Import Sets/Import sets overview/README.md",
    "content": "# Import Sets\n\nHere you can put scripts related to data imports and transforms"
  },
  {
    "path": "Integration/Import Sets/Import sets overview/TriggerDataSource.README.md",
    "content": "The triggerDataSource() function eliminates the need for manually executing a Data Source from the UI. It programmatically triggers the import of a predefined Data Source record and loads the associated data into an Import Set table.\nThis function is typically used in:\n* Scheduled Script Executions\n* Flow Designer Actions.\n"
  },
  {
    "path": "Integration/Import Sets/Import sets overview/TriggerDataSource.js",
    "content": "triggerDataSource: function() {\n\n    var dataSourceSysId = gs.getProperty('ds.tag.based.sys.id'); //Store the sysId of DataSource from system property\n\n    var grDataSource = new GlideRecord('sys_data_source');\n    if (grDataSource.get(dataSourceSysId)) {\n        var loader = new GlideImportSetLoader(); //OOB Method to load\n        var importSetRec = loader.getImportSetGr(grDataSource);\n        var ranload = loader.loadImportSetTable(importSetRec, grDataSource);\n        importSetRec.state = \"loaded\";\n        importSetRec.update();\n        return importSetRec.getUniqueValue();\n    }\n},\n"
  },
  {
    "path": "Integration/Import Sets/debug/README.md",
    "content": "# Debugging Import Sets & Transform Maps\n\nWhen you load data and execute the transform maps via the platform UI, it runs in the background which means it is not accessible to the Script Debugger.\n\nUsing this snippet you can pass the Import Set sys_id (in a state of Pending) and it will execute all the transform maps on that import set in the foreground, and be available to step through any code in the Script Debugger.\n"
  },
  {
    "path": "Integration/Import Sets/debug/debugImportSet.js",
    "content": "/**\n * This function is useful for debugging the transform maps by triggering in the foreground\n * @param {String} importSetId sys_id of sys_import_set\n * @returns \n */\nfunction executeAllTransformMapsOnImportSetId(importSetId) {\n    if(!importSetId) return;\n    var grImportSet = new GlideRecord('sys_import_set');\n    if(!grImportSet.get(importSetId)) return;\n    var Transform = new GlideImportSetTransformer(); \n    return Transform.transformAllMaps(grImportSet);\n}\n\nvar importSetId = '';\nexecuteAllTransformMapsOnImportSetId(importSetId);"
  },
  {
    "path": "Integration/Import Sets Debug/Debug import set payloads/README.md",
    "content": "# Debugging Import Sets & Transform Maps\n\nWhen you load data and execute the transform maps via the platform UI, it runs in the background which means it is not accessible to the Script Debugger.\n\nUsing this snippet you can pass the Import Set sys_id (in a state of Pending) and it will execute all the transform maps on that import set in the foreground, and be available to step through any code in the Script Debugger.\n"
  },
  {
    "path": "Integration/Import Sets Debug/Debug import set payloads/debugImportSet.js",
    "content": "/**\n * This function is useful for debugging the transform maps by triggering in the foreground\n * @param {String} importSetId sys_id of sys_import_set\n * @returns \n */\nfunction executeAllTransformMapsOnImportSetId(importSetId) {\n    if(!importSetId) return;\n    var grImportSet = new GlideRecord('sys_import_set');\n    if(!grImportSet.get(importSetId)) return;\n    var Transform = new GlideImportSetTransformer(); \n    return Transform.transformAllMaps(grImportSet);\n}\n\nvar importSetId = '';\nexecuteAllTransformMapsOnImportSetId(importSetId);"
  },
  {
    "path": "Integration/MIDServer/API Class Examples/README.md",
    "content": "# MIDServer class samples\n\n* Description: Usage samples for the MIDServer API class.\n* [MIDServer Code Snippet](scripts.js)\n* [MIDServer API Doc](https://developer.servicenow.com/dev.do#!/reference/api/rome/server_legacy/c_MIDServerAPI)"
  },
  {
    "path": "Integration/MIDServer/API Class Examples/scripts.js",
    "content": "// Get a mid server object by name\nvar ms = MIDServer.getByName(\"windows_mid\");\n// Stringify the object for display purposes\ngs.info(JSON.stringify(ms)); \n// Show the value of each API property \ngs.info('hostmane ' + ms.hostname);\ngs.info('hostOS ' + ms.hostOS);\ngs.info('ip ' + ms.ip);\ngs.info('name ' + ms.name);\ngs.info('routerIP ' + ms.routerIP);\ngs.info('status ' + ms.status);\ngs.info('sysID ' + ms.sysID);\ngs.info('url ' + ms.url);\ngs.info('version ' + ms.version);\ngs.info('windowsDomain ' + ms.windowsDomain);\n\n// After configuring default mid server for an app by navigating to MID Server > Applications\nmid = MIDServer.getDefaultForApp('Discovery');\ngs.info('defaultMid: ' + JSON.stringify(mid));\n\n// To demonstrate the other API methods, get a discovery schedule instance that uses a mid server since it is a required input property\n// the input is the schedule's sys_id\nvar schedule = new DiscoverySchedule('6cadd0a92f8f30102613802df699b602');\ngs.info('schedule: ' + JSON.stringify(schedule));\n\n// Get the default mid server for a schedule (If it is associated with a default app)\nvar defaultMidServer = MIDServer.getDefault(schedule);\ngs.info('defaultMidServer: ' + JSON.stringify(defaultMidServer));\n"
  },
  {
    "path": "Integration/Mail Scripts/Add Checklist/README.md",
    "content": "# Add Checklist\n\nIf a checklist exists for the task, add it to the email notification.\n"
  },
  {
    "path": "Integration/Mail Scripts/Add Checklist/script.js",
    "content": "(function runMailScript(current, template, email, email_action, event) {\n\n    var grTable = current.getValue('sys_class_name');\n    var grSysId = current.getValue('sys_id');\n    var grChecklist = new GlideRecord(\"checklist\");\n    grChecklist.addQuery(\"table\", grTable);\n    grChecklist.addQuery(\"document\", grSysId);\n    grChecklist.query();\n    if (grChecklist.next()) {\n        var grChecklistItem = new GlideRecord(\"checklist_item\");\n        grChecklistItem.addQuery(\"checklist\", grChecklist.getValue(\"sys_id\"));\n        grChecklistItem.orderBy(\"order\");\n        grChecklistItem.query();\n        if (grChecklistItem.hasNext()) {\n            template.print(\"<strong>Checklist:</strong>\");\n            while (grChecklistItem.next()) {\n                var checked = \"\";\n                if (grChecklistItem.getValue(\"complete\") == \"1\") {\n                    checked = \"checked\";\n                }\n                template.print(\"<br/>\");\n                template.print(gs.getMessage(\"<input type='checkbox' {0} disabled />\", [checked]));\n                template.print(\"&nbsp;\");\n                template.print(grChecklistItem.getValue(\"name\"));\n            }\n        }\n    }\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/Add HTML Table for Requested Item Variables/README.md",
    "content": "Use this snippet to create an HTML file of requested item variables.  Will exclude variables that are not needed (empty/undefined)\n"
  },
  {
    "path": "Integration/Mail Scripts/Add HTML Table for Requested Item Variables/requested_items_detail.js",
    "content": "// Get Requested Item (this query can be modified depending on where you are calling from)\nvar reqitem = new GlideRecord('sc_req_item');\nreqitem.addQuery(\"sys_id\", current.request_item);\nreqitem.query();\n\n// Now loop through the items and build the table\nwhile(reqitem.next()) {\n\t// Get Owned Variables for Requested Item and sort by Order\n\tvar ownvar = new GlideRecord('sc_item_option_mtom');\n\townvar.addQuery('request_item.number', reqitem.number);\n\townvar.addQuery('sc_item_option.value','!=','');\n\townvar.orderBy('sc_item_option.order');\n\townvar.query();\n\t\n\ttemplate.print('<table>');\n\t\n\twhile(ownvar.next()) {\n\t\t// Add Question, Answer and Order into notification mail\n\t\t// Set variable v to variable name\n\t\tvar field = ownvar.sc_item_option.item_option_new;\n\t\tvar fieldValue = ownvar.sc_item_option.item_option_new.name;\n\t\t// skip if value empty\n\t\tif (reqitem.variables[fieldValue].getDisplayValue() == '') continue;\n\t\t// skip if value undefined\n\t\tif (reqitem.variables[fieldValue] == undefined) continue;\n\t\t// Print variable name\n\t\ttemplate.print( '<tr>');\n\t\ttemplate.print( '<td><strong>' + field.getDisplayValue() + '</td>');\n\t\t// Print Display Value for each variable in Requested Item\n\t\ttemplate.print( '<td>' + reqitem.variables[fieldValue].getDisplayValue() + '</td>');\n\t\ttemplate.print( '</tr>');\n\t}\n\t\n\ttemplate.print('</table>');\n}\n"
  },
  {
    "path": "Integration/Mail Scripts/Add Users in Watchlist to CC/Add Users in Watchlist to CC",
    "content": "// Use this script to add a user in watchlist in any tables that extended from Task table\n\nif (!current.watch_list.nil()) {\n    var watcherIds = current.watch_list.split(',');\n        for (var i = 0; i < watcherIds.length; i++) {\n            var user = new GlideRecord(\"sys_user\");\n            user.addQuery(\"sys_id\", watcherIds[i]);\n            user.addQuery(\"notification\", 2);\n            user.addQuery(\"email\", \"!=\", \"\");\n            user.query();\n            if (user.next()) {\n                email.addAddress('cc', user.email.toString());\n            } else {\n                email.addAddress('cc', watcherIds[i]);\n            }\n        }\n}\n"
  },
  {
    "path": "Integration/Mail Scripts/Add Users in Watchlist to CC/README.md",
    "content": "This script allow users in the watchlist to be added to the CC in the Email. This script will work for a table extended from Task table. You can make changes to the field 'watch_list' to match to any other user list field to be added to CC\n"
  },
  {
    "path": "Integration/Mail Scripts/Add a link which opens ticket in Service Portal/README.md",
    "content": "Use this script to add a link in email notification which opens the ticket in Service Portal \n"
  },
  {
    "path": "Integration/Mail Scripts/Add a link which opens ticket in Service Portal/script.js",
    "content": "  var url = '<a href=\"' + gs.getProperty('glide.servlet.uri') + 'sp?id=ticket&table=' + current.sys_class_name + '&sys_id=' + current.sys_id + '\">Ticket Link</a>';  //Replace sp with your portal.\n  template.print(url);\n"
  },
  {
    "path": "Integration/Mail Scripts/Call Script Include in Notification Mail Script/README.md",
    "content": "Here is the syntax to call your Script Include from a Global or Scoped application into Notification Mail script.\n\n"
  },
  {
    "path": "Integration/Mail Scripts/Call Script Include in Notification Mail Script/call_script_include.js",
    "content": "(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template,\n          /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action,\n          /* Optional GlideRecord */ event) {\n  \n   //Call Global Application - Script Include in Mail script\n    var getReturnfromGlobalSI = new global.ScriptIncludeName().functionName();\n    template.print(getReturnfromGlobalSI);\n          \n   //Call Scoped Application - Script Include in Mail script\n    var getReturnfromScopedSI = new x_scope_name.ScriptIncludeName().functionName();\n    template.print(getReturnfromScopedSI);\n   \n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/Call UI Message or System Property in Notification Mail Script/README.md",
    "content": "Here is the syntax to call your UI Message or System property into Notification Mail script.\n\n"
  },
  {
    "path": "Integration/Mail Scripts/Call UI Message or System Property in Notification Mail Script/call_UIMessage_or_sysProperty.js",
    "content": "(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template,\n          /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action,\n          /* Optional GlideRecord */ event) {\n   \n   //Call UI Message in Mail script\n  \n    var getUIMessage = gs.getMessage('World works with ServiceNow');\n    template.print(getUIMessage);\n  \n \n   //Call System Property in Mail script\n  \n    var getSysProperty = gs.getProperty('sys_property_name');\n    template.print(getSysProperty);\n  \n  })(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/Configurer Approve Reject Buttons Using Email Scripts/Email Script.js",
    "content": "(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template,\n/* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action,\n/* Optional GlideRecord */ event) {\n\tvar emailID = gs.getProperty('instance_name')+\"@service-now.com\"; //Get your intance address\n\tvar apButStr = '<a href=\"mailto:'+emailID+'?subject=Re:'+current.sysapproval.number+' - approve&body='+ email.watermark +'\"><img src=\"/approve.png\" alt=\"Approve\" width=\"94\" height=\"34\"></a>'; //instructions to load approve image has been provided in the readme.md file\t\n\tvar rejButStr = '<a href=\"mailto:'+emailID+'?subject=Re:'+current.sysapproval.number+' - reject&body='+ email.watermark +'\"><img src=\"/reject.png\" alt=\"Reject\" width=\"93\" height=\"33\"></a>'; //instructions to load reject image has been provided in the readme.md file\t\n\ttemplate.print(apButStr  +'&nbsp;&nbsp;'+ rejButStr);\n\t\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/Configurer Approve Reject Buttons Using Email Scripts/README.md",
    "content": "Use Case: Set up Approve and Reject buttons using Email script to Approve/Reject through Email (Use thisv email script on the sysapproval_approver Table Notifications)\n\n1)Upload below images to DB tables in ServiceNow (System UI> Images)and use them in email scripts\n\nApprove Button Image : ![image](https://github.com/user-attachments/assets/a7113ce8-7acf-4c78-af29-dde41a816332)\n\nReject Button Image: ![image](https://github.com/user-attachments/assets/9b01e1c8-b8f2-4a14-8274-a7d4d4fdbf73)\n\n\n2)Go to Email Scripts > Click new\n3)Add Scripts (Refer Email script file)\n4)Call this in email in your Notification by using below syntax below\n${mail_script:\"name of the email script\"}\n5)Preview the email verify results\n\nOutput: ![image](https://github.com/user-attachments/assets/6c65a977-de11-4abd-918f-a4edeab4b2ce)\n"
  },
  {
    "path": "Integration/Mail Scripts/Convert DateTime to Date/README.md",
    "content": "Use this mail script to extract the date from GlideDateTime objects and use it in your email notification.\nexample: 2024:10:29 18:18:52 to 2024:10:29\n\nUse case:\nPrint just the date in email body from the GlideDateTime object.\n\nSolution:\nCreate a mail script as shown in script.js and then call this mail script in email body using ${mail_script: your mail script name}\n\n\n"
  },
  {
    "path": "Integration/Mail Scripts/Convert DateTime to Date/script.js",
    "content": "(function runMailScript( /* GlideRecord */ current, /* TemplatePrinter */ template,\n    /* Optional EmailOutbound */\n    email, /* Optional GlideRecord */ email_action,\n    /* Optional GlideRecord */\n    event) {\n\n\n    // Add your code here\n    var date = new GlideDateTime(current.sys_created_on); //datetime object of created date of current record\n    var con_date= date.getLocalDate(); // Gets the date from dateime object in user's time zone\n    template.print(con_date); //prints the date in email body\n\n\n\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/Exclude DateTime details from Comments/README.md",
    "content": "//Retrieves the most recent comment (journal entry) from the comments field of the current record.\n//We can call this notification email script in notifications to get the comments only excluding the name, date/time details.\n\ncurrent.comments.getJournalEntry(1)\n//Extracting the Comment's Content (Removing Username/Date/Time):\n\n.match(/\\n.*/gm)\n//Matches all text after the first newline (\\n). In ServiceNow, journal entries typically start with a username, date, and time stamp followed by the comment text. This regex targets everything after the first line, effectively bypassing the username and timestamp.\n\n\n.join('')\n//Joins the matched lines back into a single string. The empty string ('') is used to remove any newlines in the matched parts.\n\n\n.replace(/^\\s*\\n/gm, \"\")\n//This removes any leading empty lines (^\\s*\\n) or unnecessary whitespace that may remain after removing the username/timestamp, ensuring the comment starts cleanly with actual content.\n\n\nResult: The final output is the content of the most recent comment without the username, date, or time. This is useful for including clean, user-entered content in an email notification, without system-generated metadata like when the comment was added or who added it.\n"
  },
  {
    "path": "Integration/Mail Scripts/Exclude DateTime details from Comments/commentsWithoutDateTime.js",
    "content": "(function runMailScript(current, template, email, email_action, event) {\n\n    current.comments.getJournalEntry(1).match(/\\n.*/gm).join('').replace(/^\\s*\\n/gm, \"\"); //getting the comments without the username,date/time\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/HTML Table Creation from ServiceNow Table/MailScript.js",
    "content": "(function runMailScript(\n  /* GlideRecord */ current,\n  /* TemplatePrinter */ template,\n  /* Optional EmailOutbound */\n  email,\n  /* Optional GlideRecord */ email_action,\n  /* Optional GlideRecord */\n  event\n) {\n  // Add your code here\n\n  template.print(\"<style>td, th {text-align: left;padding: 8px;}</style>\");\n  template.print(\n    \"<table style=' font-family: arial, sans-serif;width:90%;margin-left:.5in; border-collapse:collapse;border:none' >\"\n  );\n  template.print(\"<tr style='background-color: #3DCD58;'>\");\n  template.print(\n    \"<th style='color: #ffffff'>SKU</th><th style='color: #ffffff'>SKU Description</th><th style='color: #ffffff'>License Serial Number</th>\"\n  );\n  template.print(\"</tr>\");\n\n  var gr = new GlideRecord(\"example_table\");\n  gr.addQuery(\"example_query\");\n  gr.query();\n  while (gr.next()) {\n    template.print(\"<tr>\");\n    template.print(\"<td>\" + gr.model.name + \"</td>\");\n    template.print(\"<td>\" + gr.model.short_description + \"</td>\");\n    template.print(\"<td>\" + gr.serial_number + \"</td>\");\n    template.print(\"</tr>\");\n  }\n\n  template.print(\"</table>\");\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/HTML Table Creation from ServiceNow Table/README.md",
    "content": "This mail script uses a GlideRecord object to get values from a ServiceNow table and then format that information into an HTML table in a notification.\n\nReplace \"example_table\" with your desired table.\n\nChange the style tags to fit your desired results, like background-color, etc.\n"
  },
  {
    "path": "Integration/Mail Scripts/Open Survey In Portal/README.md",
    "content": "1)create a notification on \"Assessment Instance\" table</br>\n2)when record is created and state is ready to take trigger notification to assigned to </br>\n3)use this mail script to get the url to survey in the email that takes them to the portal</br>\n"
  },
  {
    "path": "Integration/Mail Scripts/Open Survey In Portal/open_survey.js",
    "content": "\n var survey = '<a href=\"' + gs.getProperty('glide.servlet.uri') + '/sp?id=take_survey&instance_id=' +current.sys_id + '\">Click Here to take Survey </a>';  \n template.print(survey);\n"
  },
  {
    "path": "Integration/Mail Scripts/Print variables to mail/README.md",
    "content": "Script block to be used within a Notification Email Script or as a standalone one.\nIt prints all variables + answers of a catalog produced record to the email body, along with the short description of the task on top (please check script).\n"
  },
  {
    "path": "Integration/Mail Scripts/Print variables to mail/printVarsToMail.js",
    "content": "(function runMailScript( /* GlideRecord */ current, /* TemplatePrinter */ template,\n    /* Optional EmailOutbound */\n    email, /* Optional GlideRecord */ email_action,\n    /* Optional GlideRecord */\n    event) {\n\n    var tableName = current.getDisplayValue('sys_class_name');\n    \n    template.print(\"<p></p>\" + tableName + \": \");\n    \n    printVars();\n\n    function printVars() {\n        var varSet = new GlideappVariablePoolQuestionSet();\n        varSet.setRequestID(current.getValue('sys_id'));\n        varSet.load();\n        template.print(current.getDisplayValue('cat_item') + \"\\n\");\n        template.print(\"\\n\");\n        var variables = varSet.getFlatQuestions();\n        for (var i = 0; i < variables.size(); i++) {\n            if (variables.get(i).getLabel() != '') {\n                if (variables.get(i).getDisplayValue() != '') {\n//                     template.space(6);\n                    template.print(variables.get(i).getLabel() + \" : \" + variables.get(i).getDisplayValue() + \"<br/>\");\n                }\n            }\n        }\n    }\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/PrintRecordDetailsinEmailBody/README.md",
    "content": "1. Overview\nThis mail script is designed for use in ServiceNow notifications. It dynamically generates an HTML structure that displays key record details (Short Description, Description, and Comments) in a visually appealing format. The script can be used in any ServiceNow notification to provide recipients with a clear, styled summary of the record's information.\n\n2. How It Works\nKey Fields:\n\nShort Description: Retrieves and displays the record’s short description.\nDescription: Retrieves the full description of the record, preserving white spaces and line breaks.\nComments: Retrieves the comments added to the record.\n\nThe content is wrapped inside a <div> container, with styling applied using the HTML <font> tag to set the font size, bold text, and color.\nThe use of gs.getMessage() allows for internationalization, ensuring that the script works well in multi-language environments.\n3. Steps to Implement\nCreate a Notification:\n\nNavigate to System Notification > Email > Notifications in ServiceNow.\nCreate a new notification or edit an existing one based on your requirements.\nInsert the Mail Script:\n\nIn the notification, go to the Message tab.\nUse the Mail Script type to call the script in your notification message.\nThe script will dynamically pull the details from the current record (such as incident or task).\nAdjust the Script:\n\nIf needed, modify the script to display other fields or refine the content (e.g., fetching only specific journal comments).\nYou can add additional fields like Priority, Assigned to, or custom fields using a similar method.\n4. Testing the Script\nSend a Test Email:\n\nAfter configuring the notification, trigger the event to send a test email.\nEnsure the email is formatted correctly and that the Short Description, Description, and Comments are displayed as expected.\nPositive Test Case:\n\nCreate or update a record that matches the notification conditions.\nVerify that the email contains the correct record details in the styled format.\nNegative Test Case:\n\nEnsure that if no comments or description are available, the script does not throw errors but handles the empty fields gracefully.\n5. Benefits\nImproved Formatting: The use of HTML ensures that the notification looks professional, with clear headings and properly formatted text.\nDynamic Content: The script automatically pulls data from the record, reducing manual intervention and the risk of errors.\nInternationalization: The use of gs.getMessage() allows for easy translation and localization of the content, making the script adaptable to global implementations.\nReusability: This script can be reused across multiple notifications and customized easily to include additional fields or change the format.\nThis approach enhances the quality of ServiceNow notifications, providing clear and well-structured information to end users.\n"
  },
  {
    "path": "Integration/Mail Scripts/PrintRecordDetailsinEmailBody/print_record_details_to_body.js",
    "content": "// Start the HTML container with gray color and Helvetica font\ntemplate.print('<div style=\"color: #808080; font-family: helvetica;\">');\n\n// Display the Short Description label in bold and larger font\ntemplate.print('<font size=\"4\"><strong>Short Description: </strong></font>');\n\n// Display the actual short description from the current record in a smaller font\ntemplate.print('<font size=\"3\">' + gs.getMessage(current.short_description).replace(/(\\r\\n|\\n|\\r)/g, '') + '</font>');\ntemplate.print('<br />\\n');  // Add a line break after the short description\n\n// Display the Description label in bold and larger font\ntemplate.print('<font size=\"4\"><strong>' + gs.getMessage('Description') + ':</strong></font>');\n\n// Display the actual description from the current record, preserving line breaks\ntemplate.print('<font size=\"3\" style=\"white-space: pre-line;\">' + gs.getMessage(current.description) + '</font>');\ntemplate.print('<br />\\n');  // Add a line break after the description\n\n// Display the Comments label in bold and larger font\ntemplate.print('<font size=\"4\"><strong>' + gs.getMessage('Comments') + ':</strong></font><br />');\n\n// Display the actual comments from the current record in a smaller font\ntemplate.print('<font size=\"3\">' + gs.getMessage('${current.comments}') + '</font>');\n\n// Close the HTML container\ntemplate.print('</div>');\n"
  },
  {
    "path": "Integration/Mail Scripts/RITM Reject Reason/README.md",
    "content": "After finding that reject reasons added from Employee Center for Requests do not get added to the Approval record but instead the RITM record, I made a change to the reject_reason email script to include the RITM reject reason (if found)\nThe changes calls the Script Include \"RequestNotificationUtil\" with an added function to call the RITM reject reason\n"
  },
  {
    "path": "Integration/Mail Scripts/RITM Reject Reason/reject_reason_new.js",
    "content": "(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template,\n/* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action,\n/* Optional GlideRecord */ event) {\n\n\tvar requestId = current;\n\tvar portalSuffix = new sn_ex_emp_fd.FoundationNotificationUtil().getPortalSuffix();\n\tvar requestUrl = '/' + portalSuffix + '?id=order_status&table=sc_request&sys_id=' + current.sys_id.toString();\n\tvar fontSize = 'font-size: 16px;';\n\tvar lineHeight = 'line-height: 24px;';\n\tvar notificationUtil = new RequestNotificationUtil();\n\n\tvar requestDetails = notificationUtil.getRequestDetails(current.sys_id, current);\n\tvar tasks = requestDetails.tasks;\n\tvar totalTasks = requestDetails.totalTasks;\n\n\tnotificationUtil.createNotificationPrimayAction(template, requestUrl, 'View request');\n\ttemplate.print('<div style=\"font-size: 15pt; line-height:30px;\"><b>About this request</b></div>');\n\n\tvar commentLeft = notificationUtil.getRequestComment(current.sys_id, current.approval);\n\tif (commentLeft) {\n\t\ttemplate.print('<div style=\"padding-top: 16px; ' + fontSize + lineHeight + '\"><span>Rejection notes: </span>' + '<span style=\"font-weight: 600;\">' + commentLeft + '</span></div>');\n\t}\n    //For reject comments added to RITM record\n\tvar commentLeftRITM = notificationUtil.getRejectCommentRITM(current.sys_id);\n    if (commentLeftRITM) {\n        template.print('<div style=\"padding-top:18px; ' + fontSize + lineHeight + '\"><span>Rejection notes: </span>' + '<span style=\"font-weight: 600;\">' + commentLeftRITM + '</span></div><br />');\n    }\n\tif (requestDetails.totalTasks > 1) {\n\t\ttemplate.print('<div style=\"font-size: 15pt;padding-top:16px;font-weight:600;\">Requested items (' + requestDetails.totalTasks + ')</div>');\n\t}\n\ttasks.forEach(function (task, index) {\n\t\tvar borderBottom = 'border-bottom:1px solid #DADDE2';\n\t\ttemplate.print('<div style=\"padding-top:16px;padding-bottom:16px;');\n\t\tif (requestDetails.totalTasks > requestDetails.tasks.length || (index + 1 < requestDetails.tasks.length)) {\n\t\t\ttemplate.print(borderBottom);\n\t\t}\n\t\ttemplate.print('\">');\n\t\ttemplate.print('<div style=\"' + fontSize + lineHeight + '\"><span>Requested item number:</span> <span style=\"font-weight: 600;\">' + task.requestNumber + '</span></div>');\n\t\ttemplate.print('<div style=\"' + fontSize + lineHeight + '\"><span>Short description: </span><span style=\"font-weight: 600;\">' + task.item + '</span></div>');\n\t\ttemplate.print('</div>');\n\t});\n\n\tif (totalTasks > 3) {\n\t\ttemplate.print('<div style=\"' + fontSize + lineHeight + 'padding-top:16px; padding-bottom:16px;\"><a style=\"color:#3C59E7\" href=\"' + requestUrl + '\">');\n\t\ttemplate.print(gs.getMessage('View all items'));\n\t\ttemplate.print('</a></div>');\n\t}\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/Mail Scripts/Redact PII from outbound email body/README.md",
    "content": "# Redact PII from outbound email body\n\n## What this solves\nNotifications sometimes leak personal data into emails. This mail script replaces common identifiers in the email body with redacted tokens before send.\n\n## Where to use\nNotification or Email Script record, Advanced view, \"Mail script\" field. Invoke the function to get a safe body string and print it.\n\n## How it works\n- Applies regex patterns to the email text for emails, phone numbers, IP addresses, NI number style patterns, and 16-digit card-like numbers\n- Replaces matches with descriptive placeholders\n- Leaves HTML tags intact by operating on the plain text portion you pass in\n\n## Configure\n- Extend or tighten patterns for your organisation\n- Toggle specific scrubs on or off in the config block\n\n## References\n- Email Scripts  \n  https://www.servicenow.com/docs/bundle/zurich-platform-administration/page/administer/notification/reference/email-scripts.html\n- Notifications  \n  https://www.servicenow.com/docs/bundle/zurich-platform-administration/page/administer/notification/concept/c_EmailNotifications.html\n"
  },
  {
    "path": "Integration/Mail Scripts/Redact PII from outbound email body/mail_redact_pii.js",
    "content": "// Mail Script: Redact PII from outbound email body\n// Usage inside a Notification (Advanced view):\n//   var safe = redactPii(current.short_description + '\\n\\n' + current.description);\n//   template.print(safe);\n\n(function() {\n  function redactPii(text) {\n    if (!text) return '';\n\n    // Config: toggle specific redactions\n    var cfg = {\n      email: true,\n      phone: true,\n      ip: true,\n      niNumber: true,\n      card16: true\n    };\n\n    var out = String(text);\n\n    // Email addresses\n    if (cfg.email) {\n      out = out.replace(/\\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,}\\b/gi, '[redacted email]');\n    }\n\n    // Phone numbers (UK leaning, permissive, 7+ digits ignoring separators)\n    if (cfg.phone) {\n      out = out.replace(/\\b(?:\\+?\\d{1,3}[\\s-]?)?(?:\\(?\\d{3,5}\\)?[\\s-]?)?\\d{3,4}[\\s-]?\\d{3,4}\\b/g, '[redacted phone]');\n    }\n\n    // IPv4 addresses\n    if (cfg.ip) {\n      out = out.replace(/\\b(?:(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\.){3}(?:25[0-5]|2[0-4]\\d|1?\\d?\\d)\\b/g, '[redacted ip]');\n    }\n\n    // National Insurance number style (AA 00 00 00 A) simplified - UK Specific\n    if (cfg.niNumber) {\n      out = out.replace(/\\b([A-CEGHJ-PR-TW-Z]{2}\\s*\\d{2}\\s*\\d{2}\\s*\\d{2}\\s*[A-D])\\b/gi, '[redacted ni]');\n    }\n\n    // 16 consecutive digits that look like a card (permit separators)\n    if (cfg.card16) {\n      out = out.replace(/\\b(?:\\d[ -]?){13,19}\\b/g, function(match) {\n        var digits = match.replace(/[ -]/g, '');\n        return digits.length >= 13 && digits.length <= 19 ? '[redacted card]' : match;\n      });\n    }\n\n    return out;\n  }\n\n  // Expose function to the mail template scope\n  this.redactPii = redactPii;\n}).call(this);\n"
  },
  {
    "path": "Integration/Mail Scripts/cc all group members/README.md",
    "content": "This mail script can be used to CC all members of a group in the current record context. \n\nUse case: \nCC all members of the assignment group for the current record.\n\nSolution: \nCreate the mail script as mentioned in cc all group members.js file and then call the mail script in you email notification using ${mail_script: your mail script name} in the notification body\n"
  },
  {
    "path": "Integration/Mail Scripts/cc all group members/cc all group members.js",
    "content": "(function runMailScript( /* GlideRecord */ current, /* TemplatePrinter */ template,\n    /* Optional EmailOutbound */\n    email, /* Optional GlideRecord */ email_action,\n    /* Optional GlideRecord */\n    event) {\n\n\n    // Add your code here\n    var grp = new GlideRecord('sys_user_grmember');  //Query to the group member table\n    grp.addQuery(\"group\", current.assignment_group);   //add a filter to query based on the current record's assignment group\n    grp.query();\n    while (grp.next()) {\n       email.addAddress('cc', grp.user.email, grp.user.name); //Passing email as name and 2nd and 3rd parameter\n    }\n\n\n\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Integration/RESTMessageV2/API for Automatic Group creation/Automate Group Creation.js",
    "content": "var request = new sn_ws.RESTMessageV2();\nrequest.setEndpoint('https://instance_name.service-now.com/api/now/table/sys_user_group'); //Target Instance \nrequest.setHttpMethod('POST');\nvar user = 'username';\nvar password = 'password';\nrequest.setBasicAuth(user, password);\nrequest.setRequestHeader(\"Accept\", \"application/json\");\nrequest.setRequestHeader(\"Content-Type', 'application/json'); \n//set new group required field values\nrequest.setRequestBody(\" {\\\"name\\\":\\\"Group Name\\\",\\\"manager\\\":\\\"sys_id of the manager\\\",\\\"description\\\":\\\"Creating group with API\\\",\\\"type\\\":\\\"sys_id of the group type (GlideList)\\\"}\"); \nvar response = request.execute();\n\n\n"
  },
  {
    "path": "Integration/RESTMessageV2/API for Automatic Group creation/README.md",
    "content": "1.The after insert business rule on sys_user_group in the source instance, will create a group in target instance when a new group is created in source instance.\n2.It passes the required fields like group name, manager, type of the group to target instance.\n3.End point to create group in target instance is https://instance_name.service now.com/api/now/table/sys_user_group\n4.A HTTP POST method should should be used to create a record in the target instance.\n"
  },
  {
    "path": "Integration/RESTMessageV2/Aadhaar Verification/Readme.md",
    "content": "# Aadhaar Integration - ServiceNow Integration\n\nA **production-ready ServiceNow Script Include** for **Aadhaar verification** and **eKYC**, utilizing a **Connection & Credential Alias** for secure, secret-free integration.\n\n⚠️ **Compliance Warning:** Adherence to **UIDAI regulations**, the **IT Act**, **DPDP**, and your provider's terms is mandatory. Always obtain **explicit user consent** before performing any verification.\n\n---\n\n## 🎯 Key Features\n\nThis script provides a complete flow for secure Aadhaar integration:\n\n| Feature                | Methods                    | Description                                                                      |\n| :--------------------- | :------------------------- | :------------------------------------------------------------------------------- |\n| **OTP eKYC Flow**      | `sendOtp()`, `verifyOtp()` | Multi-step process with built-in session management (expiry, replay protection). |\n| **Demographic Check**  | `verifyDemographic()`      | Verify identity (Name, DOB, Gender) _without_ OTP. Returns a match score.        |\n| **Document Retrieval** | `getDocument()`            | Downloads Aadhaar XML/PDF (supports encrypted documents).                        |\n| **Status Tracking**    | `checkStatus()`            | Queries the status of any ongoing verification session.                          |\n\n### 🔒 Security & Compliance Built-in\n\n- **No Hardcoded Secrets:** Uses **Connection & Credential Alias** (`aadhaar_api`).\n- **Data Masking:** Hides Aadhaar/mobile numbers in logs (e.g., `XXXX-XXXX-1234`).\n- **Audit Logging:** Comprehensive logging of purpose, actor, consent, and outcome.\n- **Consent Tracking:** Persistent tracking of the user's consent statement and timestamp.\n\n---\n\n## 🛠️ Setup & Configuration\n\n1.  **Create Connection & Credential Alias:**\n\n    - **Name:** `aadhaar_api` (or your chosen alias).\n    - **Base URL:** Your provider's base endpoint (`https://api.provider.tld`).\n    - **Auth:** Configure credentials (API Key, OAuth, Basic) as per your provider.\n\n2.  **Configure Endpoints (In Script):** Map your provider's API paths inside the Script Include:\n    ```javascript\n    var endpoints = {\n      otpSend: \"/aadhaar/otp/send\",\n      otpVerify: \"/aadhaar/otp/verify\",\n      // ... and others\n    };\n    ```\n3.  **Set Timeouts & Retries:** Configure `timeoutMs` (default 8000) and `retries` (default 2) to manage reliability.\n\n---\n\n## 🚀 Usage Examples\n\nUse from a **Background Script** or any server-side logic:\n\n```javascript\nvar sa = new SmartAadhaar();\n\n// 1. Send OTP & Get Session ID\nvar s1 = sa.sendOtp({\n  uid: \"123412341234\",\n  purpose: \"eKYC for onboarding\",\n  consent: true,\n});\n\n// 2. Verify OTP (using session_id from s1)\nvar s2 = sa.verifyOtp({\n  uid: \"123412341234\",\n  otp: \"123456\",\n  session_id: s1.data.session_id,\n});\nif (s2.ok) {\n  gs.info(\"eKYC success for: \" + s2.data.name);\n}\n```\n\n## API Reference\n\nAll methods return a normalized envelope:\n`{ ok:Boolean, code:Number, message:String, data:Object }`\n\n| Method                 | Purpose                   | Required Parameters            |\n| :--------------------- | :------------------------ | :----------------------------- |\n| `sendOtp(p)`           | Initiate OTP flow         | `uid`, `purpose`, `consent`    |\n| `verifyOtp(p)`         | Verify OTP and fetch eKYC | `uid`, `otp`, `session_id`     |\n| `verifyDemographic(p)` | Demographic match w/o OTP | `uid`, `name`, `dob`, `gender` |\n"
  },
  {
    "path": "Integration/RESTMessageV2/Aadhaar Verification/script.js",
    "content": "/**\n * Name: Aadhaar\n * Purpose: Complete Aadhaar verification wrapper via REST using Connection & Credential Alias\n *\n * Features:\n *  - Send OTP to Aadhaar-linked mobile\n *  - Verify OTP and retrieve eKYC data\n *  - Demographic verification (name, DOB, gender)\n *  - Document download/retrieval\n *  - Session management\n *  - Rate limiting protection\n *  - Comprehensive error handling\n *\n * Setup:\n *  1. Create Connection & Credential Alias: 'aadhaar_api'\n *  2. Configure base endpoint (e.g., https://api.provider.com/v1)\n *  3. Add API key/token in credential\n *  4. Adjust paths based on your provider's API specification\n *\n * Security Notes:\n *  - Never log sensitive data (OTP, full Aadhaar, personal info)\n *  - Use encrypted fields for storing verification results\n *  - Implement proper ACLs on tables using this script\n *  - Audit all verification attempts\n */\n\nvar Aadhaar = Class.create();\nAadhaar.prototype = {\n  initialize: function () {\n    this.alias = \"aadhaar_api\";\n    this.timeoutMs = 15000;\n    this.retries = 2;\n    this.sessionStore = {};\n\n    // API endpoints - adjust based on your provider\n    this.endpoints = {\n      sendOtp: \"/aadhaar/otp/send\",\n      verifyOtp: \"/aadhaar/otp/verify\",\n      verifyDemo: \"/aadhaar/demographic/verify\",\n      getDocument: \"/aadhaar/document/download\",\n      checkStatus: \"/aadhaar/status\",\n    };\n\n    // Validation patterns\n    this.patterns = {\n      aadhaar: /^[2-9]{1}[0-9]{11}$/,\n      mobile: /^[6-9]\\d{9}$/,\n      otp: /^\\d{6}$/,\n      dob: /^\\d{4}-\\d{2}-\\d{2}$/,\n      gender: /^(M|F|O)$/i,\n    };\n  },\n\n  /**\n   * Step 1: Send OTP to Aadhaar-linked mobile number\n   * @param {Object} params\n   *   - uid: Aadhaar number (12 digits)\n   *   - captcha: Optional captcha if required by provider\n   *   - consentGiven: Boolean, must be true\n   * @returns {Object} { ok, code, message, data: { txnId, mobile, validUntil } }\n   */\n  sendOtp: function (params) {\n    try {\n      params = params || {};\n\n      // Input validation\n      if (!params.uid) {\n        return this._err(\"Aadhaar number is required\", 400);\n      }\n\n      if (!params.consentGiven) {\n        return this._err(\n          \"User consent is required for Aadhaar verification\",\n          403\n        );\n      }\n\n      var uid = this._sanitizeAadhaar(params.uid);\n      if (!this.patterns.aadhaar.test(uid)) {\n        return this._err(\"Invalid Aadhaar number format\", 400);\n      }\n\n      var payload = {\n        aadhaar_number: uid,\n        consent: \"Y\",\n        timestamp: new GlideDateTime().getDisplayValue(),\n      };\n\n      if (params.captcha) {\n        payload.captcha = String(params.captcha);\n      }\n\n      // Make API call with retry logic\n      var result = this._executeWithRetry(\n        \"POST\",\n        this.endpoints.sendOtp,\n        payload\n      );\n\n      if (result.ok) {\n        var responseData = result.data || {};\n\n        // Store session info (transaction ID)\n        var txnId =\n          responseData.transaction_id ||\n          responseData.txnId ||\n          responseData.txn_id;\n        if (txnId) {\n          this._storeSession(txnId, {\n            uid: this._maskAadhaar(uid),\n            createdAt: new GlideDateTime().getNumericValue(),\n            stage: \"otp_sent\",\n          });\n        }\n\n        // Audit log\n        this._auditLog(\"OTP_SENT\", uid, true, txnId);\n\n        return {\n          ok: true,\n          code: result.code,\n          message: \"OTP sent successfully\",\n          data: {\n            txnId: txnId,\n            mobile: this._maskMobile(responseData.mobile || responseData.phone),\n            validUntil: responseData.valid_until || responseData.otpExpiry,\n            message: responseData.message || \"OTP sent to registered mobile\",\n          },\n        };\n      }\n\n      this._auditLog(\"OTP_SEND_FAILED\", uid, false);\n      return result;\n    } catch (e) {\n      gs.error(\"[Aadhaar] sendOtp error: \" + e);\n      return this._err(\"Error sending OTP: \" + e, 500);\n    }\n  },\n\n  /**\n   * Step 2: Verify OTP and retrieve eKYC data\n   * @param {Object} params\n   *   - txnId: Transaction ID from sendOtp\n   *   - otp: 6-digit OTP\n   *   - shareCode: Optional share code for offline verification\n   * @returns {Object} { ok, code, message, data: { verified, kycData, documentUrl } }\n   */\n  verifyOtp: function (params) {\n    try {\n      params = params || {};\n\n      // Input validation\n      if (!params.txnId) {\n        return this._err(\"Transaction ID is required\", 400);\n      }\n\n      if (!params.otp) {\n        return this._err(\"OTP is required\", 400);\n      }\n\n      var otp = String(params.otp).replace(/\\D/g, \"\");\n      if (!this.patterns.otp.test(otp)) {\n        return this._err(\"Invalid OTP format. Must be 6 digits\", 400);\n      }\n\n      // Check session\n      var session = this._getSession(params.txnId);\n      if (!session) {\n        return this._err(\"Invalid or expired transaction\", 404);\n      }\n\n      var payload = {\n        transaction_id: params.txnId,\n        otp: otp,\n        timestamp: new GlideDateTime().getDisplayValue(),\n      };\n\n      if (params.shareCode) {\n        payload.share_code = String(params.shareCode);\n      }\n\n      // Make API call\n      var result = this._executeWithRetry(\n        \"POST\",\n        this.endpoints.verifyOtp,\n        payload\n      );\n\n      if (result.ok) {\n        var responseData = result.data || {};\n\n        // Update session\n        this._updateSession(params.txnId, {\n          stage: \"otp_verified\",\n          verifiedAt: new GlideDateTime().getNumericValue(),\n        });\n\n        // Parse eKYC data\n        var kycData = this._parseKycData(\n          responseData.kyc || responseData.data || responseData\n        );\n\n        // Audit log (without sensitive data)\n        this._auditLog(\"OTP_VERIFIED\", session.uid, true, params.txnId);\n\n        return {\n          ok: true,\n          code: result.code,\n          message: \"OTP verified successfully\",\n          data: {\n            verified: true,\n            txnId: params.txnId,\n            kycData: kycData,\n            documentUrl: responseData.document_url || responseData.documentUrl,\n            verificationTimestamp: new GlideDateTime().getDisplayValue(),\n          },\n        };\n      }\n\n      this._auditLog(\"OTP_VERIFY_FAILED\", session.uid, false, params.txnId);\n      this._clearSession(params.txnId);\n      return result;\n    } catch (e) {\n      gs.error(\"[Aadhaar] verifyOtp error: \" + e);\n      return this._err(\"Error verifying OTP: \" + e, 500);\n    }\n  },\n\n  /**\n   * Alternative: Demographic Verification (without OTP)\n   * Verifies Aadhaar details against provided demographic information\n   * @param {Object} params\n   *   - uid: Aadhaar number\n   *   - name: Full name\n   *   - dob: Date of birth (YYYY-MM-DD)\n   *   - gender: M/F/O\n   *   - pincode: Optional 6-digit pincode\n   * @returns {Object} { ok, code, message, data: { matched, score, details } }\n   */\n  verifyDemographic: function (params) {\n    try {\n      params = params || {};\n\n      // Input validation\n      var validationResult = this._validateDemographicParams(params);\n      if (!validationResult.valid) {\n        return this._err(validationResult.message, 400);\n      }\n\n      var uid = this._sanitizeAadhaar(params.uid);\n\n      var payload = {\n        aadhaar_number: uid,\n        name: this._sanitizeName(params.name),\n        dob: params.dob,\n        gender: params.gender.toUpperCase(),\n        consent: \"Y\",\n        timestamp: new GlideDateTime().getDisplayValue(),\n      };\n\n      if (params.pincode && /^\\d{6}$/.test(params.pincode)) {\n        payload.pincode = params.pincode;\n      }\n\n      // Make API call\n      var result = this._executeWithRetry(\n        \"POST\",\n        this.endpoints.verifyDemo,\n        payload\n      );\n\n      if (result.ok) {\n        var responseData = result.data || {};\n\n        // Audit log\n        this._auditLog(\"DEMO_VERIFIED\", uid, true);\n\n        return {\n          ok: true,\n          code: result.code,\n          message: \"Demographic verification completed\",\n          data: {\n            matched:\n              responseData.match === true || responseData.status === \"matched\",\n            score: responseData.match_score || responseData.score || 0,\n            details: {\n              nameMatch: responseData.name_match,\n              dobMatch: responseData.dob_match,\n              genderMatch: responseData.gender_match,\n              addressMatch: responseData.address_match,\n            },\n            verificationId: responseData.verification_id || responseData.ref_id,\n            timestamp: new GlideDateTime().getDisplayValue(),\n          },\n        };\n      }\n\n      this._auditLog(\"DEMO_VERIFY_FAILED\", uid, false);\n      return result;\n    } catch (e) {\n      gs.error(\"[Aadhaar] verifyDemographic error: \" + e);\n      return this._err(\"Error in demographic verification: \" + e, 500);\n    }\n  },\n\n  /**\n   * Download/Retrieve Aadhaar Document (XML/PDF)\n   * @param {Object} params\n   *   - txnId: Transaction ID from successful verification\n   *   - format: 'xml' or 'pdf' (default: 'xml')\n   *   - password: Optional password for encrypted document\n   * @returns {Object} { ok, code, message, data: { documentId, downloadUrl, format } }\n   */\n  getDocument: function (params) {\n    try {\n      params = params || {};\n\n      if (!params.txnId) {\n        return this._err(\"Transaction ID is required\", 400);\n      }\n\n      var session = this._getSession(params.txnId);\n      if (!session || session.stage !== \"otp_verified\") {\n        return this._err(\"Invalid transaction or OTP not verified\", 403);\n      }\n\n      var format = (params.format || \"xml\").toLowerCase();\n      if (![\"xml\", \"pdf\"].includes(format)) {\n        return this._err('Invalid format. Use \"xml\" or \"pdf\"', 400);\n      }\n\n      var payload = {\n        transaction_id: params.txnId,\n        format: format,\n        timestamp: new GlideDateTime().getDisplayValue(),\n      };\n\n      if (params.password) {\n        payload.password = params.password;\n      }\n\n      var result = this._executeWithRetry(\n        \"POST\",\n        this.endpoints.getDocument,\n        payload\n      );\n\n      if (result.ok) {\n        var responseData = result.data || {};\n\n        this._auditLog(\"DOCUMENT_RETRIEVED\", session.uid, true, params.txnId);\n\n        return {\n          ok: true,\n          code: result.code,\n          message: \"Document retrieved successfully\",\n          data: {\n            documentId: responseData.document_id || responseData.docId,\n            downloadUrl: responseData.download_url || responseData.url,\n            format: format,\n            expiresAt: responseData.expires_at || responseData.expiry,\n            size: responseData.size,\n            checksum: responseData.checksum,\n          },\n        };\n      }\n\n      return result;\n    } catch (e) {\n      gs.error(\"[Aadhaar] getDocument error: \" + e);\n      return this._err(\"Error retrieving document: \" + e, 500);\n    }\n  },\n\n  /**\n   * Check verification status\n   * @param {String} txnId - Transaction ID\n   * @returns {Object} Status information\n   */\n  checkStatus: function (txnId) {\n    try {\n      if (!txnId) {\n        return this._err(\"Transaction ID is required\", 400);\n      }\n\n      var session = this._getSession(txnId);\n      if (!session) {\n        return this._err(\"Transaction not found or expired\", 404);\n      }\n\n      var payload = {\n        transaction_id: txnId,\n      };\n\n      var result = this._executeWithRetry(\n        \"GET\",\n        this.endpoints.checkStatus + \"/\" + txnId,\n        null\n      );\n\n      if (result.ok) {\n        return {\n          ok: true,\n          code: result.code,\n          message: \"Status retrieved\",\n          data: {\n            txnId: txnId,\n            stage: session.stage,\n            status: result.data.status,\n            createdAt: new GlideDateTime(session.createdAt).getDisplayValue(),\n            lastUpdated: result.data.updated_at,\n          },\n        };\n      }\n\n      return result;\n    } catch (e) {\n      gs.error(\"[Aadhaar] checkStatus error: \" + e);\n      return this._err(\"Error checking status: \" + e, 500);\n    }\n  },\n\n  // ========== UTILITY METHODS ==========\n\n  /**\n   * Execute HTTP request with retry logic\n   */\n  _executeWithRetry: function (method, path, bodyObj) {\n    var attempt = 0;\n    var lastError;\n\n    while (attempt <= this.retries) {\n      attempt++;\n\n      try {\n        var response = this._makeRequest(method, path, bodyObj);\n        var code = response.status;\n\n        if (code >= 200 && code < 300) {\n          var body = this._safeParse(response.body);\n          return { ok: true, code: code, message: \"success\", data: body };\n        }\n\n        // Client errors - don't retry\n        if (code >= 400 && code < 500 && ![429].includes(code)) {\n          var errorBody = this._safeParse(response.body);\n          return this._err(\n            errorBody.message || errorBody.error || \"API error\",\n            code,\n            errorBody\n          );\n        }\n\n        // Server errors or rate limit - retry\n        if ([429, 500, 502, 503, 504].includes(code)) {\n          lastError = { code: code, body: response.body };\n          if (attempt > this.retries) {\n            return this._err(\"Service temporarily unavailable\", code);\n          }\n        } else {\n          return this._err(\"Unexpected response code: \" + code, code);\n        }\n      } catch (e) {\n        lastError = e;\n        if (attempt > this.retries) {\n          return this._err(\"Request failed: \" + e, 0);\n        }\n      }\n\n      // Exponential backoff\n      if (attempt <= this.retries) {\n        gs.sleep(this._backoff(attempt));\n      }\n    }\n\n    return this._err(\"Failed after \" + (this.retries + 1) + \" attempts\", 0);\n  },\n\n  /**\n   * Make HTTP request\n   */\n  _makeRequest: function (method, path, bodyObj) {\n    var r = new sn_ws.RESTMessageV2();\n    r.setHttpMethod(method.toUpperCase());\n    r.setEndpoint(this._resolveEndpoint(path));\n    r.setRequestHeader(\"Content-Type\", \"application/json\");\n    r.setRequestHeader(\"Accept\", \"application/json\");\n    r.setEccParameter(\"skip_sensor\", true);\n\n    if (bodyObj && method.toUpperCase() !== \"GET\") {\n      r.setRequestBody(JSON.stringify(bodyObj));\n    }\n\n    r.setHttpTimeout(this.timeoutMs);\n    r.setAuthenticationProfile(\"connection_alias\", this.alias);\n\n    var res = r.execute();\n    return {\n      status: res.getStatusCode(),\n      body: res.getBody(),\n      headers: res.getHeaders(),\n    };\n  },\n\n  /**\n   * Resolve endpoint path\n   */\n  _resolveEndpoint: function (path) {\n    if (!path || path.charAt(0) !== \"/\") {\n      path = \"/\" + (path || \"\");\n    }\n    return path;\n  },\n\n  /**\n   * Validate demographic parameters\n   */\n  _validateDemographicParams: function (params) {\n    if (!params.uid) {\n      return { valid: false, message: \"Aadhaar number is required\" };\n    }\n\n    if (!params.name || params.name.trim().length < 2) {\n      return { valid: false, message: \"Valid name is required\" };\n    }\n\n    if (!params.dob || !this.patterns.dob.test(params.dob)) {\n      return { valid: false, message: \"Valid DOB required (YYYY-MM-DD)\" };\n    }\n\n    if (!params.gender || !this.patterns.gender.test(params.gender)) {\n      return { valid: false, message: \"Valid gender required (M/F/O)\" };\n    }\n\n    var uid = this._sanitizeAadhaar(params.uid);\n    if (!this.patterns.aadhaar.test(uid)) {\n      return { valid: false, message: \"Invalid Aadhaar number format\" };\n    }\n\n    return { valid: true };\n  },\n\n  /**\n   * Parse and standardize KYC data\n   */\n  _parseKycData: function (rawData) {\n    if (!rawData) return null;\n\n    return {\n      name: rawData.name || rawData.full_name,\n      dob: rawData.dob || rawData.date_of_birth,\n      gender: rawData.gender || rawData.sex,\n      address: {\n        line1: rawData.address_line1 || rawData.house,\n        line2: rawData.address_line2 || rawData.street,\n        city: rawData.city || rawData.vtc,\n        district: rawData.district || rawData.dist,\n        state: rawData.state || rawData.state_name,\n        pincode: rawData.pincode || rawData.zip,\n        country: rawData.country || \"India\",\n      },\n      photo: rawData.photo || rawData.photo_base64,\n      email: rawData.email,\n      mobile: rawData.mobile || rawData.phone,\n      aadhaarLastFour: rawData.aadhaar_last_4 || rawData.uid_last_4,\n    };\n  },\n\n  /**\n   * Session management\n   */\n  _storeSession: function (txnId, data) {\n    var key = \"aadhaar_session_\" + txnId;\n    gs.getSession().putProperty(key, JSON.stringify(data));\n    this.sessionStore[txnId] = data;\n  },\n\n  _getSession: function (txnId) {\n    if (this.sessionStore[txnId]) {\n      return this.sessionStore[txnId];\n    }\n    var key = \"aadhaar_session_\" + txnId;\n    var stored = gs.getSession().getProperty(key);\n    if (stored) {\n      try {\n        return JSON.parse(stored);\n      } catch (e) {\n        return null;\n      }\n    }\n    return null;\n  },\n\n  _updateSession: function (txnId, data) {\n    var session = this._getSession(txnId);\n    if (session) {\n      Object.keys(data).forEach(function (key) {\n        session[key] = data[key];\n      });\n      this._storeSession(txnId, session);\n    }\n  },\n\n  _clearSession: function (txnId) {\n    var key = \"aadhaar_session_\" + txnId;\n    gs.getSession().clearProperty(key);\n    delete this.sessionStore[txnId];\n  },\n\n  /**\n   * Audit logging\n   */\n  _auditLog: function (action, uid, success, txnId) {\n    var log = new GlideRecord(\"sys_audit\");\n    log.initialize();\n    log.tablename = \"aadhaar_verification\";\n    log.documentkey = txnId || \"N/A\";\n    log.fieldname = \"verification_action\";\n    log.oldvalue = action;\n    log.newvalue = success ? \"SUCCESS\" : \"FAILED\";\n    log.user = gs.getUserID();\n    log.insert();\n\n    // Also log to system log (without sensitive data)\n    gs.info(\n      \"[Aadhaar] \" +\n        action +\n        \" - \" +\n        (success ? \"Success\" : \"Failed\") +\n        \" - TxnId: \" +\n        (txnId || \"N/A\")\n    );\n  },\n\n  /**\n   * Data sanitization\n   */\n  _sanitizeAadhaar: function (uid) {\n    return String(uid).replace(/\\D/g, \"\").substring(0, 12);\n  },\n\n  _sanitizeName: function (name) {\n    return String(name)\n      .trim()\n      .replace(/[^a-zA-Z\\s.]/g, \"\")\n      .substring(0, 100);\n  },\n\n  _maskAadhaar: function (uid) {\n    if (!uid || uid.length < 12) return \"XXXX\";\n    return \"XXXX-XXXX-\" + uid.substring(8);\n  },\n\n  _maskMobile: function (mobile) {\n    if (!mobile || mobile.length < 10) return \"XXXXXX\";\n    return \"XXXXXX\" + mobile.substring(mobile.length - 4);\n  },\n\n  /**\n   * Common utilities\n   */\n  _backoff: function (n) {\n    return 300 * Math.pow(2, n - 1) + Math.floor(Math.random() * 100);\n  },\n\n  _safeParse: function (s) {\n    if (!s) return {};\n    try {\n      return JSON.parse(s);\n    } catch (e) {\n      return { raw: s };\n    }\n  },\n\n  _err: function (msg, code, data) {\n    return {\n      ok: false,\n      code: code || 0,\n      message: msg,\n      data: data || {},\n      timestamp: new GlideDateTime().getDisplayValue(),\n    };\n  },\n\n  type: \"Aadhaar\",\n};\n\n/* ========== USAGE EXAMPLES ==========\n\n// Example 1: Complete OTP-based verification flow\nvar aadhaar = new Aadhaar();\n\n// Step 1: Send OTP\nvar otpResult = aadhaar.sendOtp({\n  uid: '123456789012',\n  consentGiven: true\n});\n\nif (otpResult.ok) {\n  gs.info('OTP sent to: ' + otpResult.data.mobile);\n  var txnId = otpResult.data.txnId;\n  \n  // Step 2: Verify OTP (after user provides OTP)\n  var verifyResult = aadhaar.verifyOtp({\n    txnId: txnId,\n    otp: '123456'\n  });\n  \n  if (verifyResult.ok) {\n    var kycData = verifyResult.data.kycData;\n    gs.info('Name: ' + kycData.name);\n    gs.info('DOB: ' + kycData.dob);\n    \n    // Step 3: Download document (optional)\n    var docResult = aadhaar.getDocument({\n      txnId: txnId,\n      format: 'xml'\n    });\n    \n    if (docResult.ok) {\n      gs.info('Document URL: ' + docResult.data.downloadUrl);\n    }\n  }\n}\n\n// Example 2: Demographic verification (no OTP)\nvar demoResult = aadhaar.verifyDemographic({\n  uid: '123456789012',\n  name: 'John Doe',\n  dob: '1990-01-15',\n  gender: 'M'\n});\n\nif (demoResult.ok && demoResult.data.matched) {\n  gs.info('Demographic verification successful');\n  gs.info('Match score: ' + demoResult.data.score);\n}\n\n// Example 3: Check status\nvar statusResult = aadhaar.checkStatus(txnId);\nif (statusResult.ok) {\n  gs.info('Current stage: ' + statusResult.data.stage);\n}\n\n*/\n"
  },
  {
    "path": "Integration/RESTMessageV2/Auth2 client credentials token cache with auto-refresh/OAuthClientCredsHelper.js",
    "content": "/**\n * Script Include: OAuthClientCredsHelper\n * Purpose: Perform OAuth 2.0 client-credentials token acquisition and caching,\n *          and wrap RESTMessageV2 calls with automatic token injection and refresh.\n *\n * SECURITY: Store clientSecret in a secure location (Credentials or encrypted property).\n */\nvar OAuthClientCredsHelper = Class.create();\nOAuthClientCredsHelper.prototype = {\n  initialize: function() {},\n\n  /**\n   * Execute an API request with Bearer token and one-shot auto-refresh on 401.\n   * Returns an object {status, body, headers, refreshed:Boolean}\n   */\n  request: function(options) {\n    var token = this.getToken(options); // may fetch or use cached\n\n    var res = this._call(options, token);\n    if (res.status !== 401) return res;\n\n    // If 401, refresh token once and retry\n    var refreshed = this.getToken(this._forceRefresh(options));\n    var retry = this._call(options, refreshed);\n    retry.refreshed = true;\n    return retry;\n  },\n\n  /**\n   * Get a cached token or fetch a new one if expired/near-expiry.\n   * Returns the access_token string.\n   */\n  getToken: function(options) {\n    this._assert(['tokenUrl', 'clientId', 'clientSecret', 'propPrefix'], options);\n    var now = new Date().getTime();\n\n    var tokenKey = options.propPrefix + '.access_token';\n    var expiryKey = options.propPrefix + '.expires_at';\n\n    var cached = gs.getProperty(tokenKey, '');\n    var expiresAt = parseInt(gs.getProperty(expiryKey, '0'), 10) || 0;\n\n    // 60-second safety buffer\n    var bufferMs = 60 * 1000;\n    if (cached && expiresAt > (now + bufferMs)) {\n      return cached;\n    }\n\n    // Need a fresh token\n    var fresh = this._fetchToken(options);\n    gs.setProperty(tokenKey, fresh.access_token);\n    gs.setProperty(expiryKey, String(fresh.expires_at));\n    return fresh.access_token;\n  },\n\n  // ------------------ internals ------------------\n\n  _call: function(options, accessToken) {\n    var r = new sn_ws.RESTMessageV2();\n    r.setEndpoint(options.resource);\n    r.setHttpMethod((options.method || 'GET').toUpperCase());\n    r.setRequestHeader('Authorization', 'Bearer ' + accessToken);\n    // Extra headers\n    Object.keys(options.headers || {}).forEach(function(k) {\n      r.setRequestHeader(k, options.headers[k]);\n    });\n\n    if (options.body && /^(POST|PUT|PATCH)$/i.test(options.method || 'GET')) {\n      r.setRequestBody(typeof options.body === 'string' ? options.body : JSON.stringify(options.body));\n      // set content type if caller didn't\n      if (!options.headers || !options.headers['Content-Type']) {\n        r.setRequestHeader('Content-Type', 'application/json');\n      }\n    }\n\n    var resp = r.execute();\n    return {\n      status: resp.getStatusCode(),\n      body: resp.getBody(),\n      headers: this._collectHeaders(resp),\n      refreshed: false\n    };\n  },\n\n  _fetchToken: function(options) {\n    // RFC 6749 client-credentials: POST x-www-form-urlencoded\n    var r = new sn_ws.RESTMessageV2();\n    r.setEndpoint(options.tokenUrl);\n    r.setHttpMethod('POST');\n    r.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n\n    var params = [\n      'grant_type=client_credentials',\n      'client_id=' + encodeURIComponent(options.clientId),\n      'client_secret=' + encodeURIComponent(options.clientSecret)\n    ];\n    if (options.scope) params.push('scope=' + encodeURIComponent(options.scope));\n    if (options.audience) params.push('audience=' + encodeURIComponent(options.audience));\n\n    r.setRequestBody(params.join('&'));\n\n    var resp = r.execute();\n    var status = resp.getStatusCode();\n    var body = resp.getBody();\n    if (status < 200 || status >= 300) {\n      throw 'Token endpoint HTTP ' + status + ': ' + body;\n    }\n\n    var json;\n    try { json = JSON.parse(body); }\n    catch (e) { throw 'Invalid token JSON: ' + e.message; }\n\n    var access = json.access_token;\n    var ttlSec = Number(json.expires_in || 3600);\n    if (!access) throw 'Token response missing access_token';\n\n    var now = new Date().getTime();\n    var expiresAt = now + (ttlSec * 1000);\n    return { access_token: access, expires_at: expiresAt };\n  },\n\n  _collectHeaders: function(resp) {\n    var map = {};\n    var names = resp.getAllHeaders();\n    for (var i = 0; i < names.size(); i++) {\n      var name = String(names.get(i));\n      map[name] = resp.getHeader(name);\n    }\n    return map;\n  },\n\n  _forceRefresh: function(options) {\n    // Nudge cache by setting expiry in the past\n    var expiryKey = options.propPrefix + '.expires_at';\n    gs.setProperty(expiryKey, '0');\n    return options;\n  },\n\n  _assert: function(keys, obj) {\n    keys.forEach(function(k) {\n      if (!obj || typeof obj[k] === 'undefined' || obj[k] === null || obj[k] === '')\n        throw 'Missing option: ' + k;\n    });\n  },\n\n  type: 'OAuthClientCredsHelper'\n};\n"
  },
  {
    "path": "Integration/RESTMessageV2/Auth2 client credentials token cache with auto-refresh/README.md",
    "content": "# OAuth 2.0 client-credentials token cache with auto-refresh\n\n## What this solves\nWhen integrating with external APIs, teams often re-implement the OAuth 2.0 client-credentials flow and forget to cache tokens or handle 401 refreshes. This helper:\n- Requests an access token from your token endpoint\n- Caches the token in a system property with an expiry timestamp\n- Adds the Bearer token to RESTMessageV2 requests\n- If the call returns 401 (expired token), refreshes once and retries\n\n## Where to use\nScript Include in global or scoped apps. Call from Business Rules, Scheduled Jobs, Flow Actions, or Background Scripts.\n\n## How it works\n- `getToken(options)` fetches or retrieves a cached token; stores `access_token` and `expires_at` (epoch ms) in system properties.\n- `request(options)` executes a resource call with Authorization header; on HTTP 401 it refreshes the token and retries once.\n- Token expiry has a 60-second buffer to avoid race on near-expiry tokens.\n\n## Security notes\n- For production, store `client_secret` in a secure location (Credentials table or encrypted system property) and **do not** hardcode secrets in scripts.\n- This snippet reads/writes system properties under a chosen prefix. Ensure only admins can read/write them.\n\n## Options\nFor `getToken` and `request`:\n- `tokenUrl`: OAuth token endpoint URL\n- `clientId`: OAuth client id\n- `clientSecret`: OAuth client secret\n- `scope`: optional scope string\n- `audience`: optional audience parameter (some providers require it)\n- `propPrefix`: system property prefix for cache (e.g. `x_acme.oauth.sample`)\n- `resource` (request only): target API URL\n- `method` (request only): GET/POST/etc (default GET)\n- `headers` (request only): object of extra headers\n- `body` (request only): request body for POST/PUT/PATCH\n\n## References\n- RESTMessageV2 API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/RESTMessageV2/concept/c_RESTMessageV2API.html\n- Direct RESTMessageV2 example  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/RESTMessageV2/reference/r_DirectRESTMessageV2Example.html\n- OAuth 2.0 profiles in ServiceNow (concept)  \n  https://www.servicenow.com/docs/bundle/zurich-integrate-applications/page/integrate/outbound-rest/concept/c_oauth2-authentication.html\n"
  },
  {
    "path": "Integration/RESTMessageV2/Auth2 client credentials token cache with auto-refresh/example_background_usage.js",
    "content": "// Background Script example: call an API with automatic OAuth bearer handling\n(function() {\n  var helper = new OAuthClientCredsHelper();\n\n  var options = {\n    // Token settings\n    tokenUrl: 'https://auth.example.com/oauth2/token',\n    clientId: 'YOUR_CLIENT_ID',\n    clientSecret: 'YOUR_CLIENT_SECRET', // store securely in real environments\n    scope: 'read:things',               // optional\n    audience: '',                       // optional (for some IdPs)\n    propPrefix: 'x_acme.oauth.sample',  // system property prefix for cache\n\n    // Resource request\n    resource: 'https://api.example.com/v1/things?limit=25',\n    method: 'GET',\n    headers: { 'Accept': 'application/json' }\n  };\n\n  var res = helper.request(options);\n  gs.info('Status: ' + res.status + ', refreshed=' + res.refreshed);\n  gs.info('Body: ' + res.body);\n})();\n"
  },
  {
    "path": "Integration/RESTMessageV2/AzureDevOps/README.md",
    "content": "This rest message is used to create an issue in azure devops from servicenow using basic authentication\n"
  },
  {
    "path": "Integration/RESTMessageV2/AzureDevOps/azure.js",
    "content": "//creating issues in azure devops from servicenow\n\ntry{\n    var content = {\"op\": \"add\", \"path\": \"/fields/System.Title\", \"from\": null, \"value\": \"Sample Task\"};\n\n\n    var request = new sn_ws.RESTMessageV2();\n\n    //replace the orgName and projectName with your own values\n    request.setEndPoint('https://dev.azure.com/orgName/projectName/_apis/wit/workitems/$issue?api-version=6.0');\n\n    //use the record in basic auth table\n    request.setAuthenticationProfile('basic', 'test');\t\n    request.setRequestHeader('Content-Type', 'application/json-patch+json');\n\trequest.setRequestBody(JSON.stringify(content));\n\n    var response = request.execute();\n    if(response.getStatusCode() == 200){\n        gs.info('successful creation of issue');\n    }\n}\ncatch(ex) {\n    gs.debug('Failing due to: ' + ex.message);\n}"
  },
  {
    "path": "Integration/RESTMessageV2/Currency Conversion - Using CurrencyFreaks API/README.md",
    "content": "# Currency Conversion- Using CurrencyFreaks API\n## Overview\nThis API allows to convert an amount from USD to any selected currency in real-time using live exchange rates fetched from the CurrencyFreaks API.\n\n## Configuration Steps\n### Get Your CurrencyFreaks API Key\n1. Go to https://currencyfreaks.com\n2. Sign up for a free account.\n3. Navigate to Dasboard ->API Keys.\n4. Copy your API key - you'll need it in ServiceNow.\n\n### Create a REST Message in ServiceNow\n- Name: CurrencyFreaks API\n- Endpoint: https://api.currencyfreaks.com/v2.0/rates/latest?apikey=${apikey}&symbols=${symbols}\n- HTTP Method: GET\n\n### Example Response\n```json\n{\"date\":\"2025-10-30 00:00:00+00\",\"base\":\"USD\",\"rates\":{\"EUR\":\"0.861846\",\"SAR\":\"3.7502\",\"KWD\":\"0.30678\",\"INR\":\"88.4075\"}}\n"
  },
  {
    "path": "Integration/RESTMessageV2/Currency Conversion - Using CurrencyFreaks API/script.js",
    "content": "var symbols =\"INR,EUR,KWD,SAR\"; //Enter symbol name like SAR,AED\nvar apiKey =\"\"; // Paste your CurrencyFreaks APIKEY here\ngetExchangeReate(apiKey, symbols);\n\nfunction getExchangeReate(apiKey,symbols){\n\ttry { \n\t\n var r = new sn_ws.RESTMessageV2('ExchangeRate API', 'Default GET');\n r.setStringParameterNoEscape('symbols', symbols);\n r.setStringParameterNoEscape('apikey', apiKey);\n\n\n var response = r.execute();\n var responseBody = response.getBody();\n var httpStatus = response.getStatusCode();\n gs.print(\"Status: \" +httpStatus);\n gs.print(\"Result:\" +responseBody); //It will show conversion from USD the selected currency \n}\ncatch(ex) {\n var message = ex.message;\n}\n}\n"
  },
  {
    "path": "Integration/RESTMessageV2/DynamicOutboundEnpoints/README.md",
    "content": "This is a server-side Script Include that contains the core logic. It reads the endpoint configurations from a System Property, parses the JSON, and returns the appropriate URL based on the current instance's name.\n\nSystem Property: x_my_scope.api.endpoints\nThis property stores a JSON object containing the endpoint URLs for each environment. It must be created and populated in each instance that uses the utility.\n\nSample JSON object:\n{\n  \"dev\": \"https://dev-instance.example.com/api\",\n  \"test\": \"https://test-instance.example.com/api\",\n  \"prod\": \"https://prod-instance.example.com/api\"\n}\n\nUsage:\nvar endpointConfig = new EndpointConfig();\nvar endpointUrl = endpointConfig.getEndpoint();    \nif (endpointUrl) \n{\ngs.info(\"Endpoint URL: \" + endpointUrl);  \n//Use the endpointUrl in your REST call\n  var request = new sn_ws.RESTMessageV2();\n  request.setEndpoint(endpointUrl);\n// ... rest of your integration logic        \n} else \n{\ngs.error(\"Failed to retrieve endpoint URL.\");\n}\n    \n"
  },
  {
    "path": "Integration/RESTMessageV2/DynamicOutboundEnpoints/scriptinclude.js",
    "content": "\n//Create a sample system Property called x_my_scope.api.endpoints having below object as example. make sure your company instance includes those key such as dev,prod,test or modify it with your instance name\n\n// {\n//   \"dev\": \"https://dev-instance.example.com/api\",\n//   \"test\": \"https://test-instance.example.com/api\",\n//   \"prod\": \"https://prod-instance.example.com/api\"\n// }\n\nvar EndpointConfig = Class.create();\nEndpointConfig.prototype = {\n    initialize: function() {\n        // No hardcoded object here. It will be fetched from the System Property.\n    },\n\n    getEndpoint: function() {\n        var propertyName = 'x_my_scope.api.endpoints';\n        var endpointObjectStr = gs.getProperty(propertyName);  \n        if (gs.nil(endpointObjectStr)) {\n            gs.error(\"EndpointConfig: System property '\" + propertyName + \"' not found or is empty.\");\n            return null;\n        }\n\n        try {\n            var endpoints = JSON.parse(endpointObjectStr);\n            var instanceName = gs.getProperty('instance_name');\n            var environmentKey;\n\n            if (instanceName.includes('dev')) {\n                environmentKey = 'dev';\n            } else if (instanceName.includes('test') || instanceName.includes('uat')) {\n                environmentKey = 'test';\n            } else if (instanceName.includes('prod')) {\n                environmentKey = 'prod';\n            } else {\n                gs.error(\"EndpointConfig: Could not determine environment for instance '\" + instanceName + \"'.\");\n                return null;\n            }\n\n            if (endpoints.hasOwnProperty(environmentKey)) {\n                return endpoints[environmentKey];\n            } else {\n                gs.error(\"EndpointConfig: Configuration not found for environment '\" + environmentKey + \"'.\");\n                return null;\n            }\n\n        } catch (ex) {\n            gs.error(\"EndpointConfig: Failed to parse JSON from system property '\" + propertyName + \"'. Exception: \" + ex);\n            return null;\n        }\n    },\n\n    type: 'EndpointConfig'\n};\n"
  },
  {
    "path": "Integration/RESTMessageV2/External ML Model Integration/Call ML Prediction API/README.md",
    "content": "# Integrate ServiceNow with External ML Model API\n\n## Overview\nCall an external ML API from ServiceNow to get AI predictions for incidents and auto-update records.\n\n## What It Does\n- Sends incident data to external ML API via REST call\n- Receives predictions (resolution time, category, priority, etc.)\n- Automatically updates incident record with predictions\n- Includes error handling and logging\n\n## Use Cases\n- Predict how long an incident will take to resolve\n- Auto-suggest the right category/priority\n- Recommend best assignment group\n- Get risk scores for changes\n\n## Files\n- `ml_prediction_script_include.js` - Script Include that calls ML API\n\n## How to Use\n1. Create Script Include in ServiceNow named `MLPredictionClient`\n2. Copy code from `ml_prediction_script_include.js`\n3. Update `ML_API_URL` and `API_KEY` with your ML service details\n4. Call it from a Business Rule or Client Script to get predictions\n5. Store results back in incident fields\n\n## Example Usage\n```javascript\nvar mlClient = new MLPredictionClient();\nvar prediction = mlClient.predictIncident({\n    description: incident.description,\n    category: incident.category,\n    priority: incident.priority\n});\n\nincident.estimated_resolution_time = prediction.predicted_resolution_time;\nincident.update();\n```\n\n## Requirements\n- ServiceNow instance\n- External ML API endpoint (REST)\n- API key or token\n"
  },
  {
    "path": "Integration/RESTMessageV2/External ML Model Integration/Call ML Prediction API/ml_prediction_script_include.js",
    "content": "// Script Include: MLPredictionClient\n// Calls external ML API to get incident predictions\n\nvar MLPredictionClient = Class.create();\nMLPredictionClient.prototype = {\n    initialize: function() {\n        this.ML_API_URL = 'https://your-ml-api.com/predict';\n        this.API_KEY = 'your-api-key-here';\n    },\n    \n    predictIncident: function(incidentData) {\n        try {\n            var request = new RESTMessageV2();\n            request.setEndpoint(this.ML_API_URL);\n            request.setHttpMethod('POST');\n            request.setRequestHeader('Authorization', 'Bearer ' + this.API_KEY);\n            request.setRequestHeader('Content-Type', 'application/json');\n            \n            // Send incident details to ML API\n            var payload = {\n                description: incidentData.description,\n                category: incidentData.category,\n                priority: incidentData.priority\n            };\n            request.setRequestBody(JSON.stringify(payload));\n            \n            // Get prediction from external ML service\n            var response = request.execute();\n            var result = JSON.parse(response.getBody());\n            \n            return {\n                estimated_hours: result.estimated_hours,\n                predicted_category: result.category,\n                confidence: result.confidence\n            };\n        } catch (error) {\n            gs.log('ML API Error: ' + error, 'MLPredictionClient');\n            return null;\n        }\n    },\n    \n    type: 'MLPredictionClient'\n};\n"
  },
  {
    "path": "Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/README.md",
    "content": "# RESTMessageV2 GET with backoff, telemetry, and simple pagination\n\n## What this solves\nExternal APIs frequently throttle with HTTP 429 or intermittently return 5xx. This helper retries safely, honours Retry-After, logs simple telemetry, and follows a links.next pagination model.\n\n## Where to use\nScript Include can be called from Scheduled Jobs, Flow Actions, Business Rules, or Background Scripts.\n\n## How it works\n- Executes RESTMessageV2 requests\n- On 429 or 5xx, sleeps using Retry-After or exponential backoff\n- Collects minimal telemetry about attempts and total sleep time\n- Appends items from json.items and follows json.links.next\n\n## References\n- RESTMessageV2 API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/RESTMessageV2/concept/c_RESTMessageV2API.html\n- Direct RESTMessageV2 example  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/RESTMessageV2/reference/r_DirectRESTMessageV2Example.html\n- Inbound rate limiting and Retry-After header  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/integrate/inbound-rest/concept/inbound-REST-API-rate-limiting.html\n"
  },
  {
    "path": "Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/RestGetWithBackoff.js",
    "content": "/**\n * Script Include: RestGetWithBackoff\n * Purpose: Safely perform RESTMessageV2 GET requests with retry handling,\n *          exponential backoff, and simple pagination support.\n * \n * Example usage (Background Script):\n *    var helper = new RestGetWithBackoff();\n *    var data = helper.getAll({\n *      endpoint: 'https://api.example.com/v1/items',\n *      headers: { 'Authorization': 'Bearer ${token}' },\n *      maxRetries: 4,\n *      baseDelayMs: 750\n *    });\n *    gs.info('Fetched ' + data.length + ' records');\n */\n\nvar RestGetWithBackoff = Class.create();\nRestGetWithBackoff.prototype = {\n  initialize: function() {},\n\n  /**\n   * Main entry point to fetch all pages of results.\n   * Handles retries, pagination, and aggregates results.\n   * @param {Object} options - endpoint, headers, maxRetries, baseDelayMs\n   * @returns {Array} all items combined from paginated responses\n   */\n  getAll: function(options) {\n    var url = options.endpoint;                  // Initial API endpoint\n    var headers = options.headers || {};         // Optional request headers\n    var maxRetries = options.maxRetries || 5;    // Maximum retry attempts per page\n    var baseDelayMs = options.baseDelayMs || 500;// Base delay for exponential backoff\n\n    var items = [];             // Array to collect all items across pages\n    var attempts = 0;           // Total number of REST calls\n    var totalSleepMs = 0;       // Total delay time across retries\n\n    // Continue fetching until there are no more pages (links.next = null)\n    while (url) {\n      // Execute the REST call (with internal retry logic)\n      var res = this._execute('get', url, headers, maxRetries, baseDelayMs);\n      attempts += res.attempts;       // Count total attempts made\n      totalSleepMs += res.sleptMs;    // Sum total sleep time used in retries\n\n      // If non-success HTTP code, throw to stop execution\n      if (res.status < 200 || res.status >= 300)\n        throw 'HTTP ' + res.status + ' for ' + url + ': ' + res.body;\n\n      // Parse and validate JSON body\n      var json = this._safeJson(res.body);\n\n      // If body contains an 'items' array, append to results\n      if (Array.isArray(json.items)) items = items.concat(json.items);\n\n      // Get next page link if available (standard 'links.next' pattern)\n      url = json && json.links && json.links.next ? json.links.next : null;\n    }\n\n    // Log a completion summary\n    gs.info('REST helper complete. items=' + items.length +\n            ', attempts=' + attempts +\n            ', sleptMs=' + totalSleepMs);\n\n    return items;\n  },\n\n  /**\n   * Executes a REST call with retry and exponential backoff.\n   * Retries on HTTP 429 (Too Many Requests) or 5xx errors.\n   * @returns {Object} status, body, attempts, sleptMs\n   */\n  _execute: function(method, url, headers, maxRetries, baseDelayMs) {\n    var attempt = 0;\n    var sleptMs = 0;\n\n    while (true) {\n      attempt++;\n\n      // Build the RESTMessageV2 object\n      var r = new sn_ws.RESTMessageV2();\n      r.setEndpoint(url);\n      r.setHttpMethod(method.toUpperCase());\n\n      // Apply custom headers (for example, auth tokens or content type)\n      Object.keys(headers).forEach(function(k) { r.setRequestHeader(k, headers[k]); });\n\n      // Execute the request\n      var resp = r.execute();\n      var status = resp.getStatusCode();\n      var body = resp.getBody();\n\n      // Success range (2xx)\n      if (status >= 200 && status < 300) {\n        return { status: status, body: body, attempts: attempt, sleptMs: sleptMs };\n      }\n\n      // Handle 429 (rate limit) or transient 5xx server errors\n      if (status === 429 || status >= 500) {\n        // Stop retrying if max reached\n        if (attempt >= maxRetries) {\n          return { status: status, body: body, attempts: attempt, sleptMs: sleptMs };\n        }\n\n        // Honour Retry-After header if present; otherwise exponential delay\n        var retryAfter = Number(resp.getHeader('Retry-After')) || 0;\n        var delayMs = retryAfter > 0 ? retryAfter * 1000 : Math.pow(2, attempt) * baseDelayMs;\n\n        // Log retry details for visibility in system logs\n        gs.info('Retrying ' + url + ' after ' + delayMs +\n                ' ms due to HTTP ' + status + ' (attempt ' + attempt + ')');\n\n        gs.sleep(delayMs); // Wait before retrying\n        sleptMs += delayMs;\n        continue;\n      }\n\n      // Non-retryable failure (e.g., 4xx not including 429)\n      return { status: status, body: body, attempts: attempt, sleptMs: sleptMs };\n    }\n  },\n\n  /**\n   * Safe JSON parser that throws descriptive error on invalid JSON.\n   * @param {String} body - raw HTTP response text\n   * @returns {Object} parsed JSON\n   */\n  _safeJson: function(body) {\n    try {\n      return JSON.parse(body || '{}');\n    } catch (e) {\n      throw 'Invalid JSON: ' + e.message;\n    }\n  },\n\n  type: 'RestGetWithBackoff'\n};\n"
  },
  {
    "path": "Integration/RESTMessageV2/GET with backoff, telemetry, and simple pagination/example_background_usage.js",
    "content": "// Background Script usage example for RestGetWithBackoff\n(function() {\n  var helper = new RestGetWithBackoff();\n  var data = helper.getAll({\n    endpoint: 'https://api.example.com/v1/things?limit=100',\n    headers: { 'Authorization': 'Bearer ${token}' },\n    maxRetries: 4,\n    baseDelayMs: 750\n  });\n  gs.info('Fetched ' + data.length + ' items total');\n})();\n"
  },
  {
    "path": "Integration/RESTMessageV2/Google-Chat/README.md",
    "content": "How we can use RESTMessageV2 to send notification in Google chat using Google chat webhook.\n\nReference link is given in the code on how we can get the Google chat webhook URL.\n"
  },
  {
    "path": "Integration/RESTMessageV2/Google-Chat/sendgchatmessage.js",
    "content": "//Link to know how we can get Google chat webhook URL - https://developers.google.com/chat/how-tos/webhooks\n// gchat_webhook_api defined below is sample one.\nvar gchat_webhook_api = \"https://chat.googleapis.com/v1/spaces/AAAAB3/messages?key=AIzahuySyDdI0hCZtEySjMm-WEfRq3CPzqKqcghnHI&token=c2uhYt6VxQohfckyoG9G6XMBIEMczuxFu\";\nvar message = \"My message to send in google chat\";\n\nsendGChatMessage(gchat_webhook_api, message);\n\nfunction sendGChatMessage(gchat_webhook_api, message) {\n\n    var http_timeout = 10000;\n    var message_tosend = {};\n    message_tosend.text = message;\n\n    //Get RESTMessageV2 object\n    var request = new sn_ws.RESTMessageV2();\n\n    //Set Request Method to POST\n    request.setHttpMethod(\"POST\");\n\n    //Set Google chat webhook end point\n    request.setEndpoint(gchat_webhook_api);\n\n    //Set the message to send as JSON in the request body\n    request.setRequestBody(JSON.stringify(message_tosend));\n\n    //set the request timeout\n    request.setHttpTimeout(http_timeout);\n\n    //Post the message to google chat webhook\n    var response = request.execute();\n\n    if (response.getErrorCode() != 0) {\n        //If any error, it will print the error message code and message.\n        gs.print(\"Message FAILED to send : Response code : \" + response.getErrorCode() + \" | ERROR : \" + response.getErrorMessage());\n    } else {\n        //If message sent successfully, it will print the reqponse message received.\n        gs.print(\"Message sent - Response \" + response.getBody());\n    }\n}\n"
  },
  {
    "path": "Integration/RESTMessageV2/Integration Between 2 Instance/IntegrationBetweenTwoInstancesWithReturnoftheCreatedRecordNumber.js",
    "content": "//The Below code is to integrate two instances, where I integrated two ServiceNow PDIs using outbound REST Message and used HTTP POST method.\n//whenever I create any incident in the source incident it will create the same incident in the Target incident using the REST message.\n//once the incident is created in the target instance that incident record's number and sys_id will be populated in the Correlation Display and Correlation ID field's of the source instance incident respectively.\n\n//I automated the process using a business rule(after, insert(checked))\n//below is the code that I used to automate the process for integrating 2 instances through business rule\n\n(function executeRule(current, previous /*null when async*/ ) {\n\n    try {\n        var r = new sn_ws.RESTMessageV2('Gupta Integration for two PDIs', 'Incident Creation By GUPTA');\n        //here \"Gupta Integration for two PDIs\" is the name of the REST Message record and \"Incident Creation By GUPTA\" is the HTTP POST record name respectively\n        r.setStringParameterNoEscape('caller_id', current.caller_id);\n        r.setStringParameterNoEscape('short_desc', current.short_description);\n        r.setStringParameterNoEscape('desc', current.description);\n        r.setStringParameterNoEscape('state', current.state);\n        r.setStringParameterNoEscape('ugency', current.urgency);\n        r.setStringParameterNoEscape('impact', current.impact);\n        r.setStringParameterNoEscape('configurationItem', current.cmdb_ci);\n        var response = r.execute();\n        var responseBody = response.getBody();\n        var responseBodyObj = JSON.parse(responseBody);//using JSON.parse\n        var targetSysId = responseBodyObj.result.sys_id;//getting the target record sys_id and storing in the varaible\n        var targetIncidentNumber = responseBodyObj.result.number;// getting the target incident record number and storing in the variable\n        //assigning those stored values to current record's correlation ID& Display field's respectively\n        current.correlation_id = targetSysId;\n        current.correlation_display = targetIncidentNumber;\n        current.update();\n\n        var httpStatus = response.getStatusCode();\n        //logging an info message so that we can check the logs for clarity\n        gs.info(\"Incident is created in target using REST\" + JSON.stringify(responseBody));\n\n    } catch (ex) {\n        var message = ex.message;\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Integration/RESTMessageV2/Integration Between 2 Instance/README.md",
    "content": "This code helps in integrating two ServiceNow instances using Rest Message - in that HTTP Post method, where I also included in the code that, once an incident is created in the source instance same record will be created in the target instance and the target record's sys_id and incident number will be auto-populated in the source instance's incident record's Correlation ID& Display field's respectively.\n"
  },
  {
    "path": "Integration/RESTMessageV2/Jira/README.md",
    "content": "# JIRA Task Creation via ServiceNow Script\n\nThis script demonstrates how to create a JIRA task using the JIRA REST API from ServiceNow.\n\n## Requirements\n\n- Valid JIRA instance URL.\n- JIRA API token."
  },
  {
    "path": "Integration/RESTMessageV2/Jira/createJiraTask.js",
    "content": "// JIRA REST API Endpoint\nvar jiraEndpoint = 'https://your-jira-instance.atlassian.net/rest/api/latest/issue';\n\n// Authentication\nvar user = 'your_email@example.com'; \nvar token = 'your_api_token';\nvar auth = 'Basic ' + gs.base64Encode(user + ':' + token);  // Scoped Base64 Encode\n// var auth = 'Basic ' + GlideStringUtil.base64Encode(user + ':' + token); // Global Base64 Encode\n\n\n// JIRA Payload\nvar requestBody = {\n    \"fields\": {\n        \"project\": {\n            \"key\": \"PROJECT_KEY\"  // Project Key in JIRA\n        },\n        \"summary\": \"Task Summary\",\n        \"description\": \"Task Description\",\n        \"issuetype\": {\n            \"name\": \"Task\"  // Issue type\n        }\n    }\n};\n\n// REST API Call\nvar request = new sn_ws.RESTMessageV2();\nrequest.setHttpMethod('POST');\nrequest.setEndpoint(jiraEndpoint);\nrequest.setRequestHeader('Authorization', auth);\nrequest.setRequestHeader('Content-Type', 'application/json');\nrequest.setRequestBody(JSON.stringify(requestBody));\n\n// Execute the request\nvar response = request.execute();\nvar responseBody = response.getBody();\nvar httpStatus = response.getStatusCode();\n\n// Log response for debugging\ngs.info('JIRA Response Status: ' + httpStatus);\ngs.info('JIRA Response Body: ' + responseBody);\n"
  },
  {
    "path": "Integration/RESTMessageV2/Reusable RESTMessageV2 retry pattern/README.md",
    "content": "# Function: `retry(func, retries, delayTime)`\n\nRetries a given function multiple times with a delay between each attempt.\n\n## Parameters\n\n- **`func`** (`Function`): The function to be executed and potentially retried on failure.\n- **`retries`** (`number`): The number of times to retry the function if it fails or returns a null/undefined result.\n- **`delayTime`** (`number`): The delay time between retries, in milliseconds.\n\n## Returns\n\n- **`any`**: Returns the result of the function if successful within the allowed retries; otherwise, returns the last result (which may be `null` or `undefined`).\n\n## Description\n\nThe function attempts to execute the provided `func()` and checks for a non-null result. If the result is null or an exception occurs, it retries the function up to the specified `retries`, with a delay of `delayTime` milliseconds between each attempt.\n\n## Example Usage\n\n```javascript\nfunction getToken() {\n    // Simulated operation that might fail\n    var request = new sn_ws.RESTMessageV2(\"token\", \"GET\");\n    var response = request.execute();\n    var statusCode = response.getStatusCode();\n    var result = null;\n    switch(statusCode) {\n        case 200:\n            result = JSON.parse(response.getBody());\n            break;\n        default:\n            throw new Error(\"request failed, http status code: \" + statusCode);\n    }\n    return result;\n}\n\nvar result = retry(getToken, 3, 2000);  // Try 3 times, with a 2-second delay between each attempt\ngs.info(\"Operation result: \" + result);\n```"
  },
  {
    "path": "Integration/RESTMessageV2/Reusable RESTMessageV2 retry pattern/ReusableRESTMesaageV2Retry.js",
    "content": "/**\n * Retries a given function multiple times with a delay between each attempt.\n *\n * @param {Function} func - The function to be executed and potentially retried on failure.\n * @param {number} retries - The number of times to retry the function if it fails or returns a null/undefined result.\n * @param {number} delayTime - The delay time between retries, in milliseconds.\n * \n * @returns {any} - Returns the result of the function if successful within the allowed retries; \n *                  otherwise, returns the last result (which may be null or undefined).\n *\n * The function attempts to execute the provided `func()` and checks for a non-null result. If the result is null\n * or an exception occurs, it retries the function up to the specified `retries`, with a delay of `delayTime`\n * milliseconds between each attempt.\n *\n * Example usage:\n * \n * function getToken() {\n *     // Simulated operation that might fail\n *     var request = new sn_ws.RESTMessageV2(\"token\", \"GET\");\n *     var response = request.execute();\n *     var statusCode = response.getStatusCode();\n *     var result = null;\n *     switch(statusCode) {\n *         case 200:\n *             result = JSON.parse(response.getBody());\n *             break;\n *         default:\n *             throw new Error(\"request failed, http status code: \" + statusCode);\n *     }\n *     return result;\n * }\n * \n * var result = retry(getToken, 3, 2000);  // Try 3 times, with a 2-second delay between each attempt\n * gs.info(\"Operation result: \" + result);\n */\nfunction retry(func, retires, delayTime) {\n    var result = null;\n    for(var i = 0; i < retires; i++) {\n        try {\n            result = func();\n            //error handling could depending on the implementation of func\n            if(!gs.nil(result)) return result;\n        } catch (error) {\n            gs.error(error);\n        }\n        gs.sleep(delayTime);\n    }\n    return result;\n}\n\n\n"
  },
  {
    "path": "Integration/RESTMessageV2/Smart Incident Categorizer AI/README.md",
    "content": "# Smart Incident Categorizer using AI\n\n## Description\nAutomatically categorizes incidents using OpenAI GPT-3.5 based on description content.\n\n## Use Case\n- Auto-assigns category when incidents are created without category\n- Reduces manual categorization effort\n- Improves consistency in incident classification\n\n## Setup\n1. Create system property: `openai.api.key` with your OpenAI API key\n2. Create Business Rule on `incident` table\n3. Set to run `before insert` when category is empty\n\n## Categories\nReturns one of: network, hardware, software, database, security, email\n\n## Testing\nCreate incident without category - verify auto-assignment in work notes.\n"
  },
  {
    "path": "Integration/RESTMessageV2/Smart Incident Categorizer AI/smart-incident.js",
    "content": "// Business Rule: Smart Incident Categorizer AI\n// Table: incident\n// When: before, insert\n// Filter Conditions: Category is empty\n\n(function executeRule(current, previous) {\n    if (current.isNewRecord() && !current.category) {\n        var categorizer = new SmartIncidentCategorizer();\n        var suggestedCategory = categorizer.categorizeIncident(current.short_description + ' ' + current.description);\n        \n        if (suggestedCategory) {\n            current.category = suggestedCategory;\n            current.work_notes = 'Category auto-assigned by AI: ' + suggestedCategory;\n        }\n    }\n})(current, previous);\n\nvar SmartIncidentCategorizer = Class.create();\nSmartIncidentCategorizer.prototype = {\n    categorizeIncident: function(description) {\n        try {\n            var openai = new sn_ws.RESTMessageV2();\n            openai.setHttpMethod('POST');\n            openai.setEndpoint('https://api.openai.com/v1/chat/completions');\n            openai.setRequestHeader('Authorization', 'Bearer ' + gs.getProperty('openai.api.key'));\n            openai.setRequestHeader('Content-Type', 'application/json');\n\n            var payload = {\n                model: \"gpt-3.5-turbo\",\n                messages: [{\n                    role: \"system\",\n                    content: \"You are an IT service desk categorizer. Return only one of these categories: network, hardware, software, database, security, email\"\n                }, {\n                    role: \"user\", \n                    content: \"Categorize this incident: \" + description\n                }],\n                max_tokens: 10,\n                temperature: 0.1\n            };\n\n            openai.setRequestBody(JSON.stringify(payload));\n            var response = openai.execute();\n            \n            if (response.getStatusCode() == 200) {\n                var result = JSON.parse(response.getBody());\n                return result.choices[0].message.content.trim().toLowerCase();\n            }\n        } catch (e) {\n            gs.error('AI Categorizer Error: ' + e.message);\n        }\n        return null;\n    }\n};\n"
  },
  {
    "path": "Integration/RESTMessageV2/UPS Tracking/README.md",
    "content": "This script calls the UPS tracking API.\n\nUPS Developer Account:\nSign up at https://developer.ups.com\nCreate an App to get credentials\n1. Client ID\n2. Client Secret\n\nHow to use:\n1. Replace YOUR_CLIENT_ID and YOUR_CLIENT_SECRET with your UPS credentials.\n2. Use the sandbox URL (wwwcie.ups.com) for testing and production URL (onlinetools.ups.com) for live data.\n3. You can move this logic into a Script Include and call it from a Flow, Business Rule, or Catalog Client Script.\n4. For security, store credentials in a Connection & Credential Alias and reference them in the script instead of hardcoding.\n"
  },
  {
    "path": "Integration/RESTMessageV2/UPS Tracking/trackUPS.js",
    "content": "(function executeUPSLookup() {\n    try {\n        var trackingNumber = '1Z12345E1512345676'; // replace or pass as a variable\n\n        // Step 1: Get OAuth token from UPS\n        var tokenRequest = new sn_ws.RESTMessageV2();\n        tokenRequest.setEndpoint('https://wwwcie.ups.com/security/v1/oauth/token');\n        tokenRequest.setHttpMethod('POST');\n        tokenRequest.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');\n        tokenRequest.setBasicAuth('YOUR_CLIENT_ID', 'YOUR_CLIENT_SECRET');\n        tokenRequest.setRequestBody('grant_type=client_credentials');\n\n        var tokenResponse = tokenRequest.execute();\n        var tokenBody = tokenResponse.getBody();\n        var tokenObj = JSON.parse(tokenBody);\n        var accessToken = tokenObj.access_token;\n\n        gs.info('UPS OAuth token retrieved successfully.');\n\n        // Step 2: Use token to request tracking info\n        var trackingRequest = new sn_ws.RESTMessageV2();\n        trackingRequest.setEndpoint('https://wwwcie.ups.com/api/track/v1/details/' + trackingNumber);\n        trackingRequest.setHttpMethod('GET');\n        trackingRequest.setRequestHeader('Authorization', 'Bearer ' + accessToken);\n        trackingRequest.setRequestHeader('transId', gs.generateGUID());\n        trackingRequest.setRequestHeader('transactionSrc', 'ServiceNow');\n\n        var trackingResponse = trackingRequest.execute();\n        var trackingBody = trackingResponse.getBody();\n        var trackingObj = JSON.parse(trackingBody);\n\n        gs.info('UPS Tracking Info: ' + JSON.stringify(trackingObj, null, 2));\n\n        // Example: log current status\n        if (trackingObj.trackResponse && trackingObj.trackResponse.shipment) {\n            var shipment = trackingObj.trackResponse.shipment[0];\n            var status = shipment.package[0].activity[0].status.description;\n            gs.info('Current Status: ' + status);\n        }\n    } catch (ex) {\n        gs.error('Error pulling UPS tracking info: ' + ex.message);\n    }\n})();\n"
  },
  {
    "path": "Integration/RESTMessageV2/Web Scraping REST Message/README.md",
    "content": "# Web Scraper REST Message\n\nThis snippet shows how you would use RESTMessageV2 to scrape HTML from a website using a GET HTTP request."
  },
  {
    "path": "Integration/RESTMessageV2/Web Scraping REST Message/code.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    var search = gs.urlEncode(current.text.replace(/!xkcd/, '').trim());\n    var rm = new sn_ws.RESTMessageV2();\n    rm.setHttpMethod('GET');\n    rm.setEndpoint('https://www.explainxkcd.com/wiki/index.php?search=' + search + '&title=Special%3ASearch&go=Go');\n    rm.setRequestHeader('User-Agent', 'servicenow');\n    var response = rm.execute();\n    var body = response.getBody();\n    var result = body.match(/(?:<a href=\"\\/wiki\\/index.php\\/)[0-9]+/gm)[0].replace(/<a href=\"\\/wiki\\/index.php\\//g, '');\n    if (parseInt(result)) {\n        var rm2 = new sn_ws.RESTMessageV2();\n        rm2.setHttpMethod('GET');\n        rm2.setEndpoint('https://xkcd.com/' + result + '/info.0.json');\n        rm2.setRequestHeader('User-Agent', 'servicenow');\n        var response2 = rm2.execute();\n        var body2 = JSON.parse(response2.getBody());\n        var safe_title = body2.safe_title;\n        var img = body2.img;\n        var alt = body2.alt;\n    }\n})(current, previous);\n"
  },
  {
    "path": "Integration/RESTMessageV2/youtubeclient/README.md",
    "content": "Example of how you can utilize the older style rest message v2 to interact with youtube data client.\nHas been replaced with the newer youtube spoke, but is fun to have around and reference how it used to be done.\n"
  },
  {
    "path": "Integration/RESTMessageV2/youtubeclient/youtubeclient.js",
    "content": "var YouTubeDataClient = Class.create();\nYouTubeDataClient.prototype = {\n    initialize: function() {\n\t\tthis.channelId = 'YESWAI'; \n\t\tthis.apiKey    = 'NOWAI'; \n\t\tthis.endpoint  = 'https://www.googleapis.com/youtube/v3'; \n    },\n\t\n\tgetMyChannelInfo: function() {\n\t\tgs.debug('getMyChannelInfo()'); \n\t\tvar rm = new sn_ws.RESTMessageV2();\n\t\trm.setHttpMethod('GET');\n\t\trm.setEndpoint(this.endpoint + '/channels'); \n\t\trm.setQueryParameter('part', 'contentDetails'); \n\t\trm.setQueryParameter('id', this.channelId); \n\t\t\n\t\tthis._prepareRequest(rm); \n\t\t\n\t\tvar response = rm.execute(); \n\t\tvar body = response.getBody(); \n\n\t\tgs.debug('body -> ' + body); \n\t\t\n\t\treturn JSON.parse(body); \n\t},\n\t\n\tprocessMyUploads: function() {\n\t\tvar channelInfo = this.getMyChannelInfo(); \n\t\tvar uploadPlaylistId = channelInfo.items[0].contentDetails.relatedPlaylists.uploads; \n\t\tvar nextPageToken = 'INIT'; \n\t\t\n\t\tvar response, items; \n\t\twhile (nextPageToken == 'INIT' || nextPageToken != '') {\n\t\t\tgs.debug('Next page token: ' + nextPageToken); \n\t\t\t\n\t\t\tif (nextPageToken == 'INIT') {\n\t\t\t\tresponse = this._getMyUploads(uploadPlaylistId); \n\t\t\t} else {\n\t\t\t\tresponse = this._getMyUploads(uploadPlaylistId, nextPageToken); \n\t\t\t}\n\t\t\t\n\t\t\tnextPageToken = response.nextPageToken || ''; \n\t\t\titems = response.items;\n\t\t\tthis._processVideoItems(items); \n\t\t}\n\t},\n\t\n\t_processVideoItems: function(items) {\n\t\tvar self = this; \n\t\titems.forEach(function(item) {\n\t\t\tself._createOrUpdateVideoRecordFromItem(item); \n\t\t});\n\t}, \n\t\n\t_createOrUpdateVideoRecordFromItem: function(item) {\n\t\tvar videoId = item.snippet.resourceId.videoId; \n\t\tvar video = new GlideRecord('x_snc_artifact_mgr_youtube_video'); \n\t\tvideo.addQuery('video_id', videoId); \n\t\tvideo.query(); \n\t\t\n\t\tvar url = 'https://www.youtube.com/watch?v=' + videoId; \n\n\t\tif (!video.next()) {\n\t\t\tgs.debug('Video does not exist, creating a new record'); \n\t\t\tvideo.initialize(); \n\t\t\tvideo.id = item.id; \n\t\t\tvideo.video_id = videoId; \n\t\t\tvideo.title = item.snippet.title; \n\t\t\tvideo.description = item.snippet.description; \n\t\t\tvideo.url = url;  \n\t\t\tvideo.published_at = new GlideDateTime(item.snippet.publishedAt.replace('T', ' ')); \n\t\t\tvideo.channel_id = item.snippet.channelId; \n\t\t\tvideo.insert(); \n\t\t} else {\n\t\t\tgs.debug('Video exists, updating it'); \n\t\t\tvideo.id = item.id; \n\t\t\tvideo.video_id = videoId; \n\t\t\tvideo.title = item.snippet.title; \n\t\t\tvideo.description = item.snippet.description;\n\t\t\tvideo.url = url; \n\t\t\tvideo.published_at = new GlideDateTime(item.snippet.publishedAt.replace('T', ' ')); \n\t\t\tvideo.channel_id = item.snippet.channelId; \n\t\t\tvideo.update(); \n\t\t}\n\t}, \n\t\n\t_getMyUploads: function(uploadPlaylistId, nextPageToken) {\n\t\tgs.debug('getMyUploads()'); \n\t\t\n\t\tgs.debug('Upload Playlist ID: ' + uploadPlaylistId); \n\t\t\n\t\tvar rm = new sn_ws.RESTMessageV2();\n\t\trm.setHttpMethod('GET');\n\t\trm.setEndpoint(this.endpoint + '/playlistItems'); \n\t\trm.setQueryParameter('part', 'id,snippet,contentDetails,status'); \n\t\trm.setQueryParameter('playlistId', uploadPlaylistId); \n\t\t\n\t\tif (nextPageToken) {\n\t\t\trm.setQueryParameter('pageToken', nextPageToken); \n\t\t}\n\t\t\n\t\tthis._prepareRequest(rm); \n\t\t\n\t\tvar response = rm.execute(); \n\t\tvar body = response.getBody(); \n\t\t\n\t\t// gs.debug('body -> ' + body); \n\t\t\n\t\treturn JSON.parse(body); \n\t},\n\t\n\t_prepareRequest: function(rm) {\n\t\trm.setQueryParameter('key', this.apiKey); \n\t}, \n\n    type: 'YouTubeDataClient'\n};"
  },
  {
    "path": "Integration/Rest Integration Send Attachment Payload/Send attachment payload via REST/DataLoaderFromIntuneAPI.js",
    "content": "getDevices: function(importSetTable) {\n        var endPoint = null;\n        var isEndofDevices = false;\n\n        while (!isEndofDevices) {\n            // Call REST Message Method\n            var url = new sn_ws.RESTMessageV2('Servicenow-Rest', 'GetDevice');\n\n            if (endPoint !== null) {\n                url.setEndpoint(endPoint); // Set the endpoint from REST Message method\n            }\n\n            var response = url.execute(); // Execute endpoint\n            var responseBody = response.getBody(); // Capture the response body\n            var httpStatus = response.getStatusCode(); // Capture response status code (200 for success)\n\n            // Parse the response to JSON format\n            var parsedResponse = JSON.parse(responseBody);\n            var deviceData = parsedResponse.value;\n\n            // Loop through each record and insert data into import set table\n            for (var i = 0; i < deviceData.length; i++) {\n                importSetTable.insert(deviceData[i]);\n            }\n\n            if (parsedResponse[\"@odata.nextLink\"]) { // Pagination : Check if response has next link \n                // Copy next data link to endPoint variable\n                endPoint = parsedResponse[\"@odata.nextLink\"];\n            } else {\n                isEndofDevices = true;\n            }\n        } // End of while loop\n    }, \nCalling from datasource\n\n var data = new Utils().getDevices(import_set_table);\n"
  },
  {
    "path": "Integration/Rest Integration Send Attachment Payload/Send attachment payload via REST/DataloaderFromIntuneAPI_README.md",
    "content": "The getDevices() function automates the process of:\nCalling a REST Message defined in ServiceNow.\nRetrieving device data in JSON format.\nHandling pagination using the @odata.nextLink attribute.\nInserting each record into a specified Import Set table for further transformation.\n"
  },
  {
    "path": "Integration/Rest Integration Send Attachment Payload/Send attachment payload via REST/README.md",
    "content": "Overview\n\nThis integration script is designed to send both a data payload and any attachments related to a record from ServiceNow to an external system via REST API. It uses ServiceNow’s RESTMessageV2 API to construct the message and send the data, making it adaptable for various external integrations.\n\nPrerequisites\n\n1.\tServiceNow Instance: Ensure you have access to a ServiceNow instance with the necessary permissions to create and run REST integrations.\n2.\tREST Message Setup: Configure a REST message in ServiceNow by navigating to System Web Services > Outbound > REST Message. This setup will define the target external system, API endpoint, and the HTTP method (e.g., POST, GET).\n\nSteps\n\n1. Create a REST Message in ServiceNow\n\n•\tGo to System Web Services > Outbound > REST Message.\n•\tCreate a new REST Message with a descriptive name.\n•\tDefine the endpoint URL (where the external API is hosted) and the HTTP method (e.g., POST, PUT).\n•\tSave the REST Message.\n\n2. Set Up the Script\n\n•\tCopy and paste the provided script into the appropriate area in ServiceNow (e.g., a Business Rule, Script Include, or Scheduled Job).\n•\tReplace the placeholder values for the REST message name and method with the actual name and method defined in Step 1.\nExample:\no\tREST Message Name: The name of the REST message you just created (e.g., My_Rest_Integration).\no\tMethod: The HTTP method for the REST call (e.g., POST).\n\n3. Modify Payload Data\n\n•\tThe script constructs a dynamic payload by retrieving key fields such as sys_id, number, table_name, and other fields from the current record.\n•\tIf you need to include additional fields in the payload (e.g., custom fields), you can add them to the payload generation logic in the script.\nExample fields:\no\tcurrent.short_description\no\tcurrent.priority\nAdjust these fields based on what data you need to send to the external system.\n\n4. Attachment Handling\n\n•\tThe script queries the sys_attachment table to retrieve any attachments related to the current record.\n•\tFor each attachment, it fetches details such as:\no\tcontent_type (e.g., PDF, JPEG)\no\tfile_name (the name of the file)\no\tsize_bytes (file size)\no\tsys_id (unique identifier for the attachment)\n•\tThese details are included in the JSON payload that is sent to the external system.\n\n5. Sending the REST Message\n\n•\tThe script sends the constructed payload along with any attachment details to the external system by executing the REST message.\n•\tIt uses sm.execute() to trigger the REST API call, where sm is the RESTMessageV2 object.\n\n6. Debugging and Logging\n\n•\tTo assist with debugging, a debug flag is included in the script. When set to true, the script will log:\no\tThe payload being sent.\no\tThe response from the external system.\no\tThe status code of the REST call (e.g., 200 for success, 500 for error).\n•\tEnable or disable logging based on your needs by toggling the debug variable.\n\n7. Error Handling\n\n•\tIf the REST call fails for any reason, the script captures the error message and assigns a status code of 500 (indicating an internal error).\n•\tThe error message can be logged or handled further as needed.\n________________________________________\nUsage\n1.\tTrigger: Attach this script to a Business Rule, Script Include, or any other automated process that triggers when a record is created or updated. For example, you might want the REST integration to run when a new incident is acknowledged.\n\n2.\tExecution: When triggered, the script will:\n\no\tQuery for any attachments related to the current record.\no\tConstruct a payload with relevant data and the attachments.\no\tSend this payload to the configured REST API endpoint.\n\n3.\tCustomization: You can easily customize the fields included in the payload or adjust the way attachments are handled by modifying the script.\n________________________________________\nCustomization Options\n1.\tREST Message: Change the REST message name and method to match the specific external system you are integrating with.\n\n2.\tPayload Fields: Modify the fields included in the payload by adding or removing fields from the current record.\n\n3.\tAttachment Filtering: If you want to filter or exclude certain types of attachments, you can modify the query on the sys_attachment table.\n________________________________________\nTroubleshooting\n•\tMissing Attachments: If attachments are not being sent, ensure that the sys_attachment query is correctly filtering by the sys_id and table_name of the current record.\n\n•\tError Status Codes: If you receive a 500 error status, check the external API endpoint and any firewall or network restrictions between ServiceNow and the external system.\n\n•\tDebug Mode: Enable debug mode to log the request body and response, which can help you diagnose any issues with the payload or API response.\n\n"
  },
  {
    "path": "Integration/Rest Integration Send Attachment Payload/Send attachment payload via REST/attachment_payload_script.js",
    "content": "// Set up variables and utility functions\ngs.info(\"Initiating REST integration for sending payload\");\n\nvar debug = true;  // Enable debug logs\nvar arrayUtil = new ArrayUtil();\nvar requestBody, responseBody, status, sm;\nvar payload = '';\nvar attachmentsJson = '';\n\n// Check if StringUtil is available, otherwise use Java string utilities\nif (typeof GlideStringUtil != 'undefined')\n    var StringUtil = GlideStringUtil;\nelse\n    var StringUtil = Packages.com.glide.util.StringUtil;\n\n// Query for attachments related to the current record\nvar gr = new GlideRecord('sys_attachment');\ngr.addQuery('table_sys_id', current.sys_id);  // Filter by the current record's sys_id\ngr.addQuery('table_name', current.getTableName());  // Filter by the current table name\ngr.query();  // Run the query\n\n// Begin the payload construction process\ntry {\n    // Define the REST message and method\n    sm = new sn_ws.RESTMessageV2(\"REST_Message_Name\", \"method\");  // Replace \"REST_Message_Name\" and \"method\" accordingly\n\n    // Add various elements to the payload\n    payload += addToPayload(\"table_name\", current.getTableName());\n    payload += addToPayload(\"number\", current.number);\n    payload += addToPayload(\"sys_id\", current.sys_id);\n    payload += addToPayload(\"correlation_id\", current.correlation_id);\n\n    // Loop through attachments if any and add them to the JSON payload\n    while (gr.next()) {\n        attachmentsJson += '{ \"content_type\":\"' + gr.content_type + '\",';\n        attachmentsJson += '\"file_name\":\"' + JSUtil.escapeText(gr.file_name) + '\",';\n        attachmentsJson += '\"size_bytes\":\"' + gr.size_bytes + '\",';\n        attachmentsJson += '\"sys_id\":\"' + gr.sys_id + '\"';\n        attachmentsJson += '},';\n    }\n\n    // If there are attachments, format the payload and execute the REST call\n    if (attachmentsJson !== '') {\n        sm.setStringParameterNoEscape(\"payload\", payload);\n        sm.setStringParameterNoEscape('attachments', attachmentsJson.substring(0, attachmentsJson.length - 1)); // Trim trailing comma\n\n        // Execute the REST message\n        var response = sm.execute();\n        status = response.getStatusCode();\n\n        // Log details if debugging is enabled\n        if (debug) {\n            gs.info(\"Payload sent: \" + sm.getRequestBody());\n            gs.info(\"Response: \" + response.getBody());\n            gs.info(\"Status: \" + status);\n        }\n    }\n\n} catch (ex) {\n    // Handle any errors that occur during execution\n    responseBody = ex.getMessage();\n    status = '500';  // Error status\n} finally {\n    // Capture the request body for further inspection\n    requestBody = sm ? sm.getRequestBody() : null;\n}\n\n// Helper function to add elements to the payload in a JSON format\nfunction addToPayload(element, value) {\n    var jsonline = '';\n    jsonline += '\"' + element + '\": ' + global.JSON.stringify(value + \"\") + ',\\n';\n    return jsonline;\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/Approval APIs/ApprovalRestResource.js",
    "content": "  /**\n         * Description : Approval API to approve the given approval record\n\t * Resource Path : /api/swre/sr_approvals/{id}/approve\n\t * HTTP method : POST\n\t * @id : SysID of the record to be approved\n\t * @comment : Comment to be updtaed on the approval record\n\t * @return JSON response\n   **/\n\n(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    var Id = request.pathParams.id; // SysID of the approval record\n    var bodyParams = request.body.data;\n    var comment = bodyParams.comment.toString(); // Comments to be updated on approval record\n    return new SRApprovalsAPI(request, response).taskApproved(Id, comment);\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/Approval APIs/README.md",
    "content": "Approval API to Approve / Reject the Approval Record \n\n* Approve API\n\nRequest :\nHTTP Method / URI\nPOST https://<instance name>.service-now.com/api/sr_approvals/<approval record sysid>/approve\n  \nHeaders :\nAcceptapplication/json\nContent-Typeapplication/json\n  \nRequest Body : \n{\n'comment' : 'Please approve this record'\n}\n  \nResponse :\nStatus code : 200 OK \n  \n Response Body\n{\n  \"result\": \"Record has been Approved!\"\n}\n  \n* Reject API\n  \nRequest :\nHTTP Method / URI\nPOST https://<instance name>.service-now.com/api/sr_approvals/<approval record sysid>/reject\n  \nHeaders : \nAcceptapplication/json\nContent-Typeapplication/json\n  \nRequest Body : \n{\n'comment' : 'Please reject this record'\n}\n  \nResponse :\nStatus code : 200 OK \n  \n Response Body\n{\n  \"result\": \"Record has been Rejected!\"\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/Approval APIs/RejectRestResource.js",
    "content": "  /**\n   * Description : Approval API to reject the given approval record\n\t * Resource Path : /api/swre/sr_approvals/{id}/reject\n\t * HTTP method : POST\n\t * @id : SysID of the record to be rejected\n\t * @comment : Comment to be updated on the approval record\n\t * @return JSON response\n   **/\n\n(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    var Id = request.pathParams.id;\n    var bodyParams = request.body.data;\n    var comment = bodyParams.comment.toString();\n    return new SRApprovalsAPI(request, response).taskRejected(Id, comment);\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/Approval APIs/SRApprovalsAPI.js",
    "content": "/**\n * APIs for Approval/Rejection on sysapproval_approver table\n * \n * @class \n * @author Amit Gujarathi\n * @memberof global.module:sys_script_include\n */\n\nvar SRApprovalsAPI = Class.create();\nSRApprovalsAPI.prototype = {\n    initialize: function() {},\n\n    /**\n     * Approves the record pending for Approval\n     * \n     * @param {any} sysId Sys ID of the record to be approved\n     * @returns {any} \n     */\n\n    taskApproved: function(sysID, comment) {\n\n        if (gs.nil(sysID)) {\n            return sn_ws_err.NotFoundError('Please enter a Valid Record');\n        }\n\n        if (!this.verifySysId(sysID)) {\n\n            return sn_ws_err.NotFoundError('Please enter a valid record!');\n\n        }\n\n        if (!this.verifyRecordAction(sysID)) {\n\n            var uID = gs.getUserID();\n            var recApprover = new GlideRecordSecure(\"sysapproval_approver\");\n\n            recApprover.addQuery('state', 'requested');\n            recApprover.addQuery('sys_id', sysID);\n            recApprover.addEncodedQuery('approver=' + uID + '^ORapprover.sys_idIN' + this.getUser());\n            recApprover.query();\n            if (recApprover.next()) {\n                recApprover.comments.setJournalEntry(comment);\n                recApprover.setValue('state', 'approved');\n                recApprover.update();\n                return ('Record has been Approved!');\n            } else {\n                {\n                    var serverError = new sn_ws_err.ServiceError();\n                    serverError.setStatus(403);\n                    serverError.setMessage('You are not authorized to perform this action');\n                    return serverError;\n\n                }\n            }\n        } else {\n            var recordActionError = new sn_ws_err.ServiceError();\n            recordActionError.setStatus(403);\n            recordActionError.setMessage('This record has been actioned previously!');\n            return recordActionError;\n\n        }\n\n    },\n\n\n    /**\n     * Gets the users who has appointed the current user as their delegates\n     */\n\n    getUser: function() {\n\n        var appr = [];\n        var del = new GlideRecord('sys_user_delegate');\n        del.addQuery('delegate', gs.getUserID());\n        del.addEncodedQuery('approvals=true^ends>=javascript:gs.beginningOfToday()');\n        del.query();\n        while (del.next()) {\n            appr.push(del.user.sys_id);\n\n        }\n        return appr.toString();\n\n    },\n\n    /**\n     * Verifies if the sys_id a valid in the sysapproval_approver table\n     */\n\n\n    verifySysId: function(sysid) {\n\n        var recApprover = new GlideRecord(\"sysapproval_approver\");\n        var result = false;\n        if (recApprover.get(sysid)) {\n            result = true;\n        }\n        return result;\n\n    },\n\n    /**\n     * Verifies if the record is actioned -  stateINapproved,rejected\n     */\n\n\n    verifyRecordAction: function(sysid) {\n\n        var result = false;\n        var recAction = new GlideRecord(\"sysapproval_approver\");\n        recAction.addEncodedQuery('state!=requested^sys_id=' + sysid);\n        recAction.query();\n        if (recAction.next()) {\n            result = true;\n        }\n        return result;\n\n    },\n\n    /**\n     * Rejects the record pending for Approval\n     * \n     * @param {any} sysId Sys ID of the record to be approved and comment text for rejection\n     * @returns {any} \n     */\n\n    taskRejected: function(sysID, comment) {\n        if (gs.nil(sysID)) {\n            return sn_ws_err.NotFoundError('Record not Found!');\n        }\n\n        if (gs.nil(comment)) {\n\n\n            var rejectComment = new sn_ws_err.ServiceError();\n            rejectComment.setStatus(403);\n            rejectComment.setMessage('Rejection comments are mandatory!');\n            return rejectComment;\n\n\n\n        }\n\n        if (!this.verifySysId(sysID)) {\n\n            return sn_ws_err.BadRequestError('Please enter a valid record!');\n\n        }\n\n        if (!this.verifyRecordAction(sysID)) {\n            var uID = gs.getUserID();\n            var recApprover = new GlideRecordSecure(\"sysapproval_approver\");\n\n\n            recApprover.addQuery('state', 'requested');\n            recApprover.addQuery('sys_id', sysID);\n            recApprover.addEncodedQuery('approver=' + uID + '^ORapprover.sys_idIN' + this.getUser());\n            recApprover.query();\n\n            if (recApprover.next()) {\n\n                recApprover.comments.setJournalEntry(comment);\n                recApprover.setValue('state', 'rejected');\n                recApprover.update();\n\n                return ('Record has been Rejected!');\n            } else {\n\n                {\n                    var serverError = new sn_ws_err.ServiceError();\n                    serverError.setStatus(403);\n                    serverError.setMessage('You are not authorized to perform this action');\n                    return serverError;\n\n                }\n            }\n\n        } else {\n            var recordActionError = new sn_ws_err.ServiceError();\n            recordActionError.setStatus(403);\n            recordActionError.setMessage('This record has been actioned previously!');\n            return recordActionError;\n\n        }\n\n    },\n\n    type: 'SRApprovalsAPI'\n};\n"
  },
  {
    "path": "Integration/Scripted REST Api/Approval on Behalf/README.md",
    "content": "# Approve On Behalf - Scripted REST API\n\n## Overview\nThis REST API allows authorized users to approve or reject tasks on behalf of another user. The script handles impersonation, performs action on approval records, and returns appropriate responses based on the success or failure of the request.\n\n### API Definition\n- **Name**: Approve On Behalf\n- **Application**: Global\n- **Active**: Yes\n- **HTTP Method**: POST\n- **Relative Path**: /\n- **Resource Path**: /api/aueis/approve_on_behalf\n\n## Request Format\nThe API accepts `application/json` as the input format.\n\n### Sample Request\n```json\n{\n    \"approvalRecId\": \"1234567890abcdef\",\n    \"userId\": \"user.name\",\n    \"action\": \"approve\",\n    \"comments\": \"Approving on behalf of the user\"\n}\n\n\n### Sample Success Response\njson\nCopy code\n{\n    \"success\": true,\n    \"message\": \"Action 'approve' performed successfully on approval record.\"\n}\n### Sample Error Response\njson\nCopy code\n{\n    \"success\": false,\n    \"message\": \"Invalid approval record ID: 1234567890abcdef\"\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/Approval on Behalf/approval_on_behalf.js",
    "content": "/**\n * Approve On Behalf Scripted REST API\n * This API allows authorized users to approve or reject tasks on behalf of another user.\n * It handles impersonation, performs actions on approval records, and returns appropriate responses.\n *\n * @param {RESTAPIRequest} request - The request object containing data from the client\n * @param {RESTAPIResponse} response - The response object to send data back to the client\n */\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n    try {\n        // Parse request data\n        var reqData = JSON.parse(request.body.dataString);\n        var reqId = reqData.approvalRecId;\n        var user = reqData.userId;\n        var action = reqData.action;\n        var comments = reqData.comments;\n\n        // Validate input\n        if (!reqId || !user || !action) {\n            return respondWithError(response, \"Missing required fields: approvalRecId, userId, or action.\");\n        }\n\n        // Check impersonation rights\n        var canImpers = new GlideImpersonate().canImpersonate(user);\n        if (!canImpers) {\n            return respondWithError(response, \"Cannot impersonate user \" + user);\n        }\n\n        // Impersonate the user\n        var impUser = new GlideImpersonate();\n        impUser.impersonate(user);\n\n        // Fetch the approval record\n        var approvalGR = new GlideRecord('sysapproval_approver');\n        if (!approvalGR.get(reqId)) {\n            return respondWithError(response, \"Invalid approval record ID: \" + reqId);\n        }\n\n        // Perform action based on the request (approve/reject)\n        if (action.toLowerCase() === 'approve') {\n            approvalGR.state = 'approved';\n        } else if (action.toLowerCase() === 'reject') {\n            approvalGR.state = 'rejected';\n        } else {\n            return respondWithError(response, \"Invalid action specified. Valid actions are 'approve' or 'reject'.\");\n        }\n\n        // Add comments if provided\n        if (comments) {\n            approvalGR.comments = comments;\n        }\n\n        // Update the record\n        approvalGR.update();\n\n        // Response success\n        response.setStatus(200);\n        response.setHeader('Content-Type', 'application/json');\n        response.setBody({ \"success\": true, \"message\": \"Action '\" + action + \"' performed successfully on approval record.\" });\n\n    } catch (e) {\n        // Handle errors and respond\n        respondWithError(response, \"An error occurred: \" + e.message);\n    }\n\n    /**\n     * Helper function to respond with error\n     * Sends a consistent error response to the client with a status of 400.\n     *\n     * @param {RESTAPIResponse} response - The response object to send data back to the client\n     * @param {string} message - The error message to respond with\n     */\n    function respondWithError(response, message) {\n        response.setStatus(400);\n        response.setHeader('Content-Type', 'application/json');\n        response.setBody({ \"success\": false, \"message\": message });\n    }\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/CmdbApi.js",
    "content": "/**\n * APIs to manage CIs\n * \n * @class \n * @author Amit Gujrathi\n */\n\nvar CmdbApi = Class.create();\nCmdbApi.prototype = {\ninitialize: function() {\n},\n\n/**\n\t * Create a CI\n\t * Mapped to POST /api/978841/cis/{ci_type}\n\t * \n\t * @returns {Object} JSON response of CI created or Error details\n */\ncreateCi: function() {\n\tvar self = this;\n\n\tvar ciType = self.getPathParam('ci_type', '');\n\n\tvar payload = self.body;\n\n\tif (!self._isValidCiType(ciType)) {\n\t\treturn sn_ws_err.BadRequestError('Invalid CI Type provided:' + ciType);\n\t}\n\n\tvar ciRec = new GlideRecord(ciType);\n\tciRec.initialize();\n\n\ttry {\n\t\tfor (var key in payload) {\n\t\t\tif (ciRec.isValidField(key) && key.indexOf('sys_') != 0) {\n\t\t\t\tvar value = payload[key];\n\t\t\t\tvar el = ciRec.getElement(key);\n\n\t\t\t\tvar descriptor = el.getED();\n\t\t\t\tvar choice = descriptor.choice;\n\t\t\t\tvar type = descriptor.getInternalType();\n\n\t\t\t\tif (choice != '0') {\n\t\t\t\t\tciRec[key] = self._getChoiceValue(ciType, key, value);\n\t\t\t\t} else if (type == 'reference') {\n\t\t\t\t\tciRec[key] = self._getReferenceValue(ciType, key, value);\n\t\t\t\t} else {\n\t\t\t\t\tciRec[key] = payload[key];\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (key === 'sys_id') {\n\t\t\t\tciRec.setNewGuidValue(payload[key]);\n\t\t\t}\n\t\t}\n\t\tvar sysId = ciRec.insert();\n\n\t\tvar errMsg = ciRec.getLastErrorMessage();\n\t\tif (!gs.nil(errMsg)) {\n\t\t\treturn new sn_ws_err.BadRequestError(errMsg);\n\t\t}\n\n\t\treturn self._getGrResultStream(ciType, sysId, {});\n\n\t} catch (e) {\n\t\tvar serverError = new sn_ws_err.ServiceError();\n\t\tserverError.setStatus(500);\n\t\tserverError.setMessage('Operation Failed');\n\t\tserverError.setDetail(e);\n\t\treturn serverError;\n\n\t}\n},\n\n\n/**\n\t * \n\t * Creates a Configuration Item (CI) relationship in ServiceNow.\n\t * Mapped to POST /api/relationships\n\t * @returns {Object} JSON response of CI Relationship created or Error details\n\t * \n * */\ncreateCiRelationship: function() {\n\tvar self = this;\n\n\tvar payload = self.body;\n\n\t//Check for mandatory keys in Payload\n\tvar missingPayloadAttributes = ['parent', 'type', 'child'].filter(function(key) {\n\t\tif (payload.hasOwnProperty(key)) {\n\t\t\treturn gs.nil(payload[key]);\n\t\t}\n\t\treturn true;\n\t});\n\n\tif (missingPayloadAttributes.length > 0) {\n\t\treturn new sn_ws_err.BadRequestError(gs.getMessage('Missing required attributes: {0}', [missingPayloadAttributes.join()]));\n\t}\n\n\tvar parentCiSysId = payload.parent || '';\n\tvar relationshipTypeSysId = payload.type || '';\n\tvar childCiSysId = payload.child || '';\n\n\tif (!self._isValidRecord('cmdb_ci', parentCiSysId)) {\n\t\treturn new sn_ws_err.BadRequestError('Parent CI not found with id: ' + parentCiSysId);\n\t}\n\n\tif (!self._isValidRecord('cmdb_ci', childCiSysId)) {\n\t\treturn new sn_ws_err.BadRequestError('Child CI not found with id: ' + childCiSysId);\n\t}\n\n\tif (!self._isValidRecord('cmdb_rel_type', relationshipTypeSysId)) {\n\t\treturn new sn_ws_err.BadRequestError('Relationship type not found with id: ' + relationshipTypeSysId);\n\t}\n\n\tif (self._isValidRecord('cmdb_rel_ci', '', gs.getMessage('parent={0}^child={1}^type={2}', [parentCiSysId, childCiSysId, relationshipTypeSysId]))) {\n\t\treturn new sn_ws_err.BadRequestError('Relationship already exists');\n\t}\n\n\tvar ciRelation = new GlideRecord('cmdb_rel_ci');\n\tciRelation.initialize();\n\tciRelation.parent = parentCiSysId;\n\tciRelation.child = childCiSysId;\n\tciRelation.type = relationshipTypeSysId;\n\tciRelation.insert();\n\n\tvar insertError = ciRelation.getLastErrorMessage();\n\tif (!gs.nil(insertError)) {\n\t\treturn new sn_ws_err.BadRequestError(insertError);\n\t}\n\n\treturn self._getGrResultStream('cmdb_rel_ci', ciRelation.getValue('sys_id'));\n},\n\n\n/**\n\t * \n\t * Deletes a Configuration Item (CI) based on the provided CI type and sys_id.\n\t * Mapped to DELETE /cis/{ci_type}/{sys_id}\n\t * @returns \n\t * \n * */\n\tdeleteCi: function () {\n\t\tvar self = this;\n\n        var ciType = self.getPathParam('ci_type', '');\n\n        var ciSysId = self.getPathParam('sys_id');\n\n        var payload = self.body;\n\t\t\n\t\tvar isCmdbExtension = (function (tableName) {\n\t\t\tvar absoluteBase = new TableUtils(tableName).getAbsoluteBase();\n            return (absoluteBase == 'cmdb_ci');\n\t\t})(ciType);\n\t\t\n\t\tvar isRootTable = (function(tableName) {\n            var rootTables = gs.getProperty('sr-cmdb-api.root-tables.delete', '');\n            return (rootTables.indexOf(tableName) > -1);\n        })(ciType);\n\t\t\n\t\tif (!isCmdbExtension && !isRootTable) {\n            return sn_ws_err.BadRequestError('Invalid CI Type provided:' + ciType);\n\t\t}\n\t\t\n\t\tvar honorAcl = isCmdbExtension;\n\t\t// Honor ACL security for tables extending cmdb_ci. For CMDB extended tables, the security can be configured in the metadata record\n\t\t// Do not honor ACL security for standalone tables. Delete access for such tables can be controlled by property 'sr-cmdb-api.root-tables.delete'\n\n\t\tvar ciRec = honorAcl ? new GlideRecordSecure(ciType) : new GlideRecord(ciType);\n        if (ciRec.get(ciSysId)) {\n\t\t\t\n\t\t\tif (honorAcl) {\n\t\t\t\tif (!ciRec.canDelete()) {\n\t\t\t\t\tvar unauthorizedError = new sn_ws_err.ServiceError();\n\t\t\t\t\tunauthorizedError.setStatus(403);\n\t\t\t\t\tunauthorizedError.setMessage('Operation Failed');\n\t\t\t\t\tunauthorizedError.setDetail('ACL Exception Delete Failed due to security constraints');\n\t\t\t\t\treturn unauthorizedError;\n\t\t\t\t}\t\n\t\t\t}\n\t\t\t\n\t\t\ttry {\n                ciRec.deleteRecord();\n\n                var errMsg = ciRec.getLastErrorMessage();\n                if (!gs.nil(errMsg)) {\n                    return new sn_ws_err.BadRequestError(errMsg);\n                }\n\n                return self.response.setStatus(204);\n\n            } catch (e) {\n                var serverError = new sn_ws_err.ServiceError();\n                serverError.setStatus(500);\n                serverError.setMessage('Operation Failed');\n                serverError.setDetail(e);\n                return serverError;\n            }\n        } else {\n            return new sn_ws_err.NotFoundError('No record found');\n        }\n\t},\t\n\n /**\n     * Deletes a CI (Configuration Item) relationship based on the provided sys_id.\n     * Mapped to DELETE /cis/relationships/{sys_id}\n     * @throws {sn_ws_err.BadRequestError} Throws an error if the sys_id is null or not provided.\n     * @throws {sn_ws_err.NotFoundError} Throws an error if a CI relationship with the given sys_id is not found.\n     * @returns {void} Returns nothing if the deletion is successful, otherwise throws an error.\n */\t\n    deleteCiRelationship: function() {\n        var self = this;\n\n        var relSysId = self.getPathParam('sys_id', '');\n\n        if (gs.nil(relSysId)) {\n            return new sn_ws_err.BadRequestError('sys_id cannot be null');\n        }\n\n        var ciRelation = new GlideRecord('cmdb_rel_ci');\n        ciRelation.addQuery('sys_id', relSysId);\n        ciRelation.query();\n        if (ciRelation.next()) {\n            ciRelation.deleteRecord();\n\n            self.response.setStatus(204);\n            return;\n        } else {\n            return new sn_ws_err.NotFoundError('CI Relationship not found with sys_id: ' + relSysId);\n        }\n    },\n/**\n\t * Get the details of a Configuration Item (CI) Group in the CMDB.\n  \t * Mapped to GET /cis/group/{id}\n\t * @function getCiGroup\n\t * @returns {Object} - Returns a response object containing the details of the CI and its associated groups.\n **/\n getCiGroup: function() {\n        var response = {};\n        var self = this;\n        var ciId = self.getPathParam('id', '');\n        if (gs.nil(ciId)) {\n            return new sn_ws_err.BadRequestError('Parameter id cannot be null');\n        }\n        var CI_details = [];\n        var arrGroupList = [];\n        var arrStatusDetails = [];\n        var ciDetailsObj = {};\n        var ci_name = '';\n        var ci = new GlideRecord(\"cmdb_ci\");\n        switch (this.getSourceIDtype(ciId)) {\n            case 'srobjUID':\n                ci.addQuery(\"u_sr_object_uid\", ciId);\n                break;\n            case 'sysID':\n                ci.addQuery(\"sys_id\", ciId);\n                break;\n            case 'configId':\n                ci.addQuery(\"name\", ciId);\n                break;\n        }\n        ci.query();\n        if (ci.next()) {\n            ciDetailsObj['name'] = ci.name.toString();\n            ciDetailsObj['sys_class_name'] = ci.sys_class_name.getDisplayValue();\n            ciDetailsObj['operational_status'] = ci.operational_status.getDisplayValue();\n            ciDetailsObj['short_description'] = ci.short_description.toString();\n            arrStatusDetails.push(ciDetailsObj);\n            ci_name = ci.sys_id;\n            var gr = new GlideRecord(\"cmdb_rel_group\");\n            gr.addQuery(\"ci\", ci_name);\n            gr.query();\n            while (gr.next()) {\n                var retObj = {};\n                retObj['type'] = gr.type.name.toString();\n                retObj['u_default'] = gr.u_default.toString();\n                retObj['group'] = gr.group.name.toString();\n                arrGroupList.push(retObj);\n            }\n        } else {\n            return new sn_ws_err.NotFoundError('No group found for CI with ID: ' + ciId);\n        }\n        ciDetailsObj.ciGroups = arrGroupList;\n        CI_details.push(ciDetailsObj);\n        response['ciDetails'] = CI_details;\n        return response;\n    },\n\t\n/**\n * Retrieves a list of Configuration Item (CI) Relationship Types from the ServiceNow instance.\n * Mapped to GET /cis/relationshiptypes\n * @function\n * @name getCiRelationshipTypes\n * @returns {GlideRecord} A GlideRecord object representing the list of CI Relationship Types.\n * @throws {Error} If there is an issue with the retrieval process.\n **/\n getCiRelationshipTypes: function() {\n        return this._getGrResultStream('cmdb_rel_type', null, {\n            sysparm_limit: 100\n        });\n    },\n\n/**\n * Retrieves Configuration Items (CIs) based on the specified CI type.\n * Mapped to GET /cis/{ci_type}\n * @returns {GlideRecordStream} A stream of GlideRecord objects representing the CIs.\n * @throws {BadRequestError} If an invalid CI type is provided.\n */\n getCis: function() {\n        var self = this;\n\n        var ciType = self.getPathParam('ci_type', '');\n\n        if (!self._isValidCiType(ciType)) {\n            return sn_ws_err.BadRequestError('Invalid CI Type provided:' + ciType);\n        }\n\n        return self._getGrResultStream(ciType, null, {\n            sysparm_limit: 100\n        });\n\n    },\n\n/**\n * Update a Configuration Item (CI).\n * Mapped to PATCH /cis/{ci_type}/{sys_id}\n *\n * This function updates a CI based on its type and system ID.\n *\n * @returns {Object} JSON response containing the updated CI or error details.\n * @throws {BadRequestError} If an invalid CI type is provided.\n * @throws {ServiceError} If the operation fails for any reason.\n * @throws {NotFoundError} If no record is found for the provided system ID.\n */\n    updateCi: function() {\n        var self = this;\n\n        var ciType = self.getPathParam('ci_type', '');\n\n        var ciSysId = self.getPathParam('sys_id');\n\n        var payload = self.body;\n\n        if (!self._isValidCiType(ciType)) {\n            return sn_ws_err.BadRequestError('Invalid CI Type provided:' + ciType);\n        }\n\n        var ciRec = new GlideRecord(ciType);\n        if (ciRec.get(ciSysId)) {\n\n            try {\n                for (var key in payload) {\n                    if (ciRec.isValidField(key) && key.indexOf('sys_') != 0) {\n                        var value = payload[key];\n                        var el = ciRec.getElement(key);\n\n                        var descriptor = el.getED();\n                        var choice = descriptor.choice;\n                        var type = descriptor.getInternalType();\n\n                        if (choice != '0') {\n                            ciRec[key] = self._getChoiceValue(ciType, key, value);\n                        } else if (type == 'reference') {\n                            ciRec[key] = self._getReferenceValue(ciType, key, value);\n                        } else {\n                            ciRec[key] = payload[key];\n                        }\n                    }\n                }\n                ciRec.update();\n\n                var errMsg = ciRec.getLastErrorMessage();\n                if (!gs.nil(errMsg)) {\n                    return new sn_ws_err.BadRequestError(errMsg);\n                }\n\n                return self._getGrResultStream(ciType, ciRec.getValue('sys_id'), {});\n\n            } catch (e) {\n                var serverError = new sn_ws_err.ServiceError();\n                serverError.setStatus(500);\n                serverError.setMessage('Operation Failed');\n                serverError.setDetail(e);\n                return serverError;\n            }\n        } else {\n            return new sn_ws_err.NotFoundError('No record found');\n        }\n    },\n/**\n * Retrieve Configuration Item (CI) Relationships.\n * Mapped to GET /cis/relationships\n *\n * This function retrieves CI relationships\n *\n * @returns {Object} JSON response containing the CI relationships or error details.\n */\n getRelationships: function() {\n        var self = this;\n\n        return self._getGrResultStream('cmdb_rel_ci', null, {\n            sysparm_limit: 100\n        });\n\n    },\n\t\n/**\n\t * Determine the type of source ID.\n\t * @function getSourceIDtype\n\t * @param {string} parm - The parameter that needs to be tested against various ID types.\n\t * @returns {string} - Returns a string representing the type of the source ID ('srobjUID', 'sysID', or 'configId').\n */\n    getSourceIDtype: function(parm) {\n        var UUIDregEx = new RegExp(/^[0-9A-F]{8}-[0-9A-F]{4}-[4][0-9A-F]{3}-[89AB][0-9A-F]{3}-[0-9A-F]{12}$/i); //regular expression to validate the input string in uuid\n        var reguuidPattern = new RegExp(UUIDregEx);\n        if (UUIDregEx.test(parm)) {\n            return \"srobjUID\";\n        }\n        var sysRegex = /[a-z0-9]{32}/;\n        var regexsysidPattern = new RegExp(sysRegex);\n        if (regexsysidPattern.test(parm)) {\n            return \"sysID\";\n        }\n        return \"configId\";\n    },\n /**\n     * Check if the Table is part of CMDB. The table should be an extension of cmdb_ci or a root table defined in property sr-cmdb-api.root-tables\n     * \n     * @param {String} tableName\n     * @returns {Boolean}\n */\n    _isValidCiType: function(tableName) {\n        if (gs.nil(tableName)) {\n            return false;\n        }\n\n        if (!gs.tableExists(tableName)) {\n            return false;\n        }\n\n        var isRootTable = (function() {\n            var rootTables = gs.getProperty('sr-cmdb-api.root-tables', ''); // Comma seperated root table names\n            return (rootTables.indexOf(tableName) > -1);\n        })();\n\n        if (isRootTable) {\n            return true;\n        } else {\n            var absoluteBase = new TableUtils(tableName).getAbsoluteBase();\n\n            return (absoluteBase == 'cmdb_ci');\n        }\n    },\n\n /**\n     * Get the valid Choice value of the field\n     * @param {String} tableName\n     * @param {String} fieldName\n     * @param {String} payloadValue\n     * @returns {String} choiceValue\n*/\n    _getChoiceValue: function(tableName, fieldName, payloadValue) {\n\n        if (gs.nil(tableName) || gs.nil(fieldName) || gs.nil(payloadValue)) {\n            return '';\n        }\n\n        var choiceValue = null;\n\n        var gr = new GlideRecord(tableName);\n        gr.initialize();\n\n        var el = gr.getElement(fieldName);\n\n        var descriptor = el.getED();\n        var choice = descriptor.choice;\n\n        if (choice == '0') {\n            return '';\n        }\n\n        if (el.getChoices().indexOf(payloadValue + '') != -1) {\n            return payloadValue;\n        } else {\n            var tables = new TableUtils(tableName).getHierarchy();\n\n            choiceValue = j2js(tables).reduce(function(out, table) {\n                if (out !== null)\n                    return out;\n\n                var gcs = new GlideRecord('sys_choice');\n                gcs.addQuery('name', table);\n                gcs.addQuery('element', fieldName);\n                gcs.addQuery('label', payloadValue);\n                gcs.addQuery('inactive', false);\n                gcs.query();\n                if (gcs.next()) {\n                    out = gcs.value;\n                }\n                return out;\n            }, null);\n\n            if (choiceValue == null) {\n                throw gs.getMessage('Invalid value [{0}] for field [{1}]', [payloadValue, fieldName]);\n            }\n        }\n        return choiceValue;\n    },\n\t\n/**\n     * Get the sys_id of the Reference field\n     * \n     * @param {String} tableName\n     * @param {String} fieldName\n     * @param {String} payloadValue\n     * @returns {String} refValue\n*/\n    _getReferenceValue: function(tableName, fieldName, payloadValue) {\n\n        if (gs.nil(tableName) || gs.nil(fieldName) || gs.nil(payloadValue)) {\n            return '';\n        }\n\n        var gr = new GlideRecord(tableName);\n        gr.initialize();\n\n        var el = gr.getElement(fieldName);\n        var descriptor = el.getED();\n        var type = descriptor.getInternalType();\n\n        if (type != 'reference') {\n            return '';\n        }\n\n        var refTable = el.getReferenceTable();\n\n        if (gs.nil(refTable)) {\n            throw gs.getMessage('Invalid reference field [{0}]; Reference table not defined', [fieldName]);\n        }\n\n        if (!gs.tableExists(refTable)) {\n            throw gs.getMessage('Reference table [{0}] does not exist for field [{1}]', [refTable, fieldName]);\n        }\n\n        var refTableDisplayField = (function() {\n            var grRef = new GlideRecord(refTable);\n            grRef.initialize();\n            return grRef.getDisplayName();\n        })();\n\n        var refValue = (function() {\n            var grRefTable = new GlideRecord(refTable);\n            if (grRefTable.get(payloadValue)) {\n                return grRefTable.getValue('sys_id');\n            }\n            return '';\n        })();\n\n        return refValue;\n    },\n\n\n/**\n\t * Check if the record is a valid record\n\t * \n\t * @param {any} tableName\n\t * @param {any} sysId\n\t * @param {any} query\n\t * @returns Boolean\n */\n_isValidRecord: function(tableName, sysId, query) {\n\tif (gs.nil(tableName)) {\n\t\treturn false;\n\t}\n\n\tif (!gs.tableExists(tableName)) {\n\t\treturn false;\n\t}\n\n\tif (gs.nil(sysId) && gs.nil(query)) {\n\t\treturn false;\n\t}\n\n\tgs.getSession().setStrictQuery(true);\n\n\tvar gr = new GlideRecord(tableName);\n\tif (!gs.nil(sysId)) {\n\t\tgr.addQuery('sys_id', sysId);\n\t} else {\n\t\tgr.addQuery(query);\n\t}\n\n\tgr.query();\n\n\treturn gr.hasNext();\n},\n\n/**\n     * Write to response stream\n     * \n     * @param {any} tableName\n     * @param {any} sysId\n     * @param {any} defaultParams\n     * @returns {undefined}\n */\n    _getGrResultStream: function(tableName, sysId, defaultParams) {\n\n        var self = this;\n\n        defaultParams = defaultParams || {};\n        var singleObject = Boolean(sysId);\n        if (singleObject) {\n            defaultParams.sysparm_suppress_pagination_header = 'true';\n        }\n\n        var query = defaultParams.sysparm_query || self.getQueryParam('sysparm_query');\n        var fields = defaultParams.sysparm_fields || self.getQueryParam('sysparm_fields');\n        fields = (fields) ? fields.split(',') : [];\n\n        var offset = parseInt(self.getQueryParam('sysparm_offset', 0), 10);\n        var limit = parseInt(self.getQueryParam('sysparm_limit', defaultParams.sysparm_limit || 10000), 10);\n\n        var displayValue = self.getQueryParam('sysparm_display_value', 'false');\n        var category = self.getQueryParam('sysparm_query_category');\n\n        var suppressPaginationLink = defaultParams.sysparm_suppress_pagination_header || self.getQueryParam('sysparm_suppress_pagination_header', 'false');\n\n        var excludeRefLink = self.getQueryParam('sysparm_exclude_reference_link', 'false');\n        var view = self.getQueryParam('sysparm_view');\n\n\n        // query the table\n        var gr = new GlideRecord(tableName);\n\n        // init so gr has all fields\n        gr.initialize();\n\n        // in case no fields specified, use all (only possible after .next())\n        if (fields.length === 0) {\n            fields = Object.keys(gr);\n        }\n\n        // allow query fields to be in url. e.g. active=true\n        Object.keys(self.request.queryParams).forEach(function(key) {\n            if (key.indexOf('sysparm_') === 0 || gr[key] === undefined)\n                return;\n            query = ((query) ? query.concat('^') : '').concat(key, '=', self.getQueryParam(key));\n        });\n\n        if (sysId) {\n            gr.addQuery('sys_id', sysId);\n        } else if (query) {\n            gr.addQuery(query);\n        }\n\n        if (category)\n            gr.setCategory(category);\n\n        var onPage = Math.ceil((offset + 1) / limit),\n            thisOffset = offset + limit;\n\n        // set window\n        gr.chooseWindow(offset, thisOffset, true);\n        //gr.setLimit(nextOffset);\n        gr._query();\n\n        var totalRows = gr.getRowCount();\n\n        // send 404 in case no row match\n        if (totalRows === 0) {\n            return []; //new sn_ws_err.NotFoundError('No Record found. Query: '.concat(query));\n        }\n\n        var totalPage = Math.ceil(totalRows / limit),\n            prevOffset = offset - limit,\n            nextOffset = Math.min(thisOffset, (totalPage - 1) * limit),\n            lastOffset = (totalPage - 1) * limit;\n\n        self.response.setContentType('application/json');\n\n        var links = [];\n        if ('true' != suppressPaginationLink) {\n            links.push(self._createLink(limit, 0, 'first'));\n            if (onPage > 1) {\n                links.push(self._createLink(limit, prevOffset, 'prev'));\n            }\n            if (onPage < totalPage) {\n                links.push(self._createLink(limit, nextOffset, 'next'));\n            }\n            links.push(self._createLink(limit, lastOffset, 'last'));\n            // append to header\n            self.response.setHeader(\"Link\", links.join(','));\n        }\n\n        self.response.setStatus(200);\n\n        // get the writer\n        var writer = response.getStreamWriter();\n        // start the result\n        writer.writeString('{\"result\":');\n        if (!singleObject) {\n            writer.writeString('[');\n        }\n        //writer.writeString(JSON.stringify(self.request.queryParams));\n\n        var append = false;\n        // stream row by row\n        while (gr._next()) {\n\n            if (append) {\n                writer.writeString(',');\n            } else {\n                append = true;\n            }\n\n            var out = {};\n            fields.forEach(function(fieldName) {\n                fieldName = fieldName.trim();\n\n                if (!gr.isValidField(fieldName.split('.')[0]))\n                    return;\n\n                var element = gr.getElement(fieldName);\n                var ed = element.getED(),\n                    value = null;\n                /*\n                .nil() is also true if a filed has length 0 !!\n                if (element.nil()) {\n                    value = null;\n                } else\n                */\n\n                if (ed.isBoolean()) {\n                    value = JSUtil.toBoolean(element.toString());\n                } else if (ed.isTrulyNumber()) {\n                    value = parseInt(element.toString(), 10);\n                } else {\n                    value = element.toString();\n                }\n\n                if ('all' == displayValue.toLowerCase()) {\n                    out[fieldName] = {\n                        display_value: element.getDisplayValue(),\n                        value: value\n                    };\n                } else if ('true' == displayValue.toLowerCase()) {\n                    out[fieldName] = element.getDisplayValue();\n                } else {\n                    out[fieldName] = value;\n                }\n            });\n\n            writer.writeString(JSON.stringify(out));\n\n        }\n        if (!singleObject) {\n            writer.writeString(']');\n        }\n\n        if (self.getQueryParam('sysparm_meta', 'false') == 'true') {\n            // append meta information\n            var meta = {\n                query: query,\n                queryParams: self.request.queryParams,\n                sysId: sysId,\n                fields: fields,\n                offsetWindowStart: offset,\n                offsetWindowEnd: thisOffset,\n                limit: limit,\n                totalRows: totalRows,\n                totalPage: totalPage,\n                prevOffset: prevOffset,\n                nextOffset: nextOffset,\n                lastOffset: lastOffset,\n                displayValue: displayValue,\n                category: category,\n                links: links\n            };\n            writer.writeString(',\"__meta\":');\n            writer.writeString(JSON.stringify(meta));\n        }\n\n        // close the result\n        writer.writeString('}');\n\n    },\n\ntype: 'CmdbApi'\n};\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/CreateCIs.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).createCi();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/CreateCiRelationship.js",
    "content": "\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).createCiRelationship();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/DeleteCI.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).deleteCi();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/DeleteCiRelationship.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).deleteCiRelationship();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/README.md",
    "content": "CMDB API Documentation\nWelcome to the CMDB API documentation. This document provides detailed information about the APIs created for CMDB.\n\nTable of Contents\n1. Create CIs\n2. Create CI Relationship\n3. Delete CI\n4. Delete CI Relationship\n5. Retrieve CI Group\n6. Retrieve CI Relationship types\n7. Retrieve CIs\n8. Update CI\n9. Retrieve CI Relationships\n\n1. Create CIs\n  This API is designed to create Configuration Items (CIs).\n  Request Details:\n    Type: HTTP\n    Method: POST\n    URI: https://<service-now-domain>.service-now.com/api/cis/{ci_type}\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n  Request Body:\n    The request body should contain a JSON key-value pair for the required fields.\n  Response:\n    Status Code: 200 OK\n    Response Body: JSON with required fields\n\n2. Create Ci Relationship\n   Creates a Configuration Item (CI) relationship in ServiceNow\n    Request Details:\n    Type: HTTP\n    Method: POST\n    URI: https://<service-now-domain>.service-now.com/api/relationships\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n    Request Body:\n      {\n       \"parent\": \"parentCiSysId\",\n       \"type\": \"relationshipTypeSysId\",\n       \"child\": \"childCiSysId\"\n      }\n    Response:\n      Status Code: 200 OK\n      Response Body: JSON with required fields for CI Relationship\n\n   3. Delete CI\n   Deletes a Configuration Item (CI) based on the provided CI type and sys_id.\n    Request Details:\n    Type: HTTP\n    Method: DELETE\n    URI: https://<service-now-domain>.service-now.com/api/cis/{ci_type}/{sys_id}\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n    Request Body: NA\n    Response:\n      Status Code: 204 OK\n      Response Body:\n\n   4. Delete CI Relationship\n   Deletes a CI (Configuration Item) relationship based on the provided sys_id.\n    Request Details:\n    Type: HTTP\n    Method: DELETE\n    URI: https://<service-now-domain>.service-now.com/api/cis/relationships/{sys_id}\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n    Request Body: NA\n    Response:\n      Status Code: 204 OK\n      Response Body:\n\n   5. Retrieve CI Group\n   Scripted REST API Resource is designed to retrieve Configuration Item (CI) Group details from a CMDB (Configuration Management Database). The main functionality includes fetching details about a specific CI and its   \n   associated groups based on the given identifier.\n    Request Details:\n    Type: HTTP\n    Method: GET\n    URI: https://<service-now-domain>.service-now.com/api/cis/group/{id}\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n    Request Body: NA\n    Response:\n      Status Code: 200 OK\n      Response Body: Details of CI and its group\n\n   6. Retrieve CI Relationship types\n   Scripted REST API Resource is designed to retrieve a list of Configuration Item (CI) Relationship Types from the ServiceNow instance.\n    Request Details:\n    Type: HTTP\n    Method: GET\n    URI: https://<service-now-domain>.service-now.com/api/cis/relationshiptypes\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n    Request Body: NA\n    Response:\n      Status Code: 200 OK\n      Response Body: Details of CI Relationship types\n\n    7. Retrieve CIs\n   Scripted REST API Resource contains a ServiceNow script that implements a function for retrieving Configuration Items (CIs) based on a specified CI type.\n    Request Details:\n    Type: HTTP\n    Method: GET\n    URI: https://<service-now-domain>.service-now.com/api/cis/{ci_type}\n    Headers:\n     Accept: application/json\n     Content-Type: application/json\n    Request Body: NA\n    Response:\n      Status Code: 200 OK\n      Response Body: JSON Details of CIs based on the given type\n\n  8. Update CIs\n    This API is designed to Update a Configuration Item (CI).\n    Request Details:\n      Type: HTTP\n      Method: PATCH\n      URI: https://<service-now-domain>.service-now.com/api/cis/{ci_type}/{sys_id}\n      Headers:\n       Accept: application/json\n       Content-Type: application/json\n    Request Body:\n      The request body should contain a JSON key-value pair for the required fields.\n    Response:\n      Status Code: 200 OK\n      Response Body: JSON with required fields\n\n9. Retrieve CI relationships\n    This API is designed to retrieve  Configuration Item (CI) relationships.\n    Request Details:\n      Type: HTTP\n      Method: GET\n      URI: https://<service-now-domain>.service-now.com/api/cis/relationships\n      Headers:\n       Accept: application/json\n       Content-Type: application/json\n    Request Body:\n      NA\n    Response:\n      Status Code: 200 OK\n      Response Body: JSON with CI relationships required details\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/RetrieveCiGroup.js",
    "content": "/**\n * @summary Handles the Scripted REST API Resource for retrieving CI Group from the CMDB API.\n * @description This function is a part of a scripted REST API resource. It acts as an endpoint handler, \n *              processing incoming REST API requests and producing appropriate responses. In this case, \n *              it handles requests to retrieve Configuration Item (CI) Groups from the Configuration \n *              Management Database (CMDB) API. \n */\n(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    return new CmdbApi(request, response).getCiGroup();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/RetrieveCis.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).getCis();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/Retrieve_CI_Relationships.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).getRelationships();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/RetriveCiRelationshipTypes.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).getCiRelationshipTypes();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CMDB API/UpdateCi.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\treturn new CmdbApi(request, response).updateCi();\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/CURL Script to create incident via tableAPI/README.md",
    "content": "## Use the attached script to create a incident via CURL script execution \n\nYou need to update the username, password and instancename in the URL section of the script -> username:password https://instance_name.service-now.com/api/now/table/incident\n\nAlso, This script can be modified to work for any table in ServiceNow by changing the table_name in the URL/Endpoint and updating the required attributtes as per the table in the payload.\n\n\n"
  },
  {
    "path": "Integration/Scripted REST Api/CURL Script to create incident via tableAPI/script.curl",
    "content": "curl -v -H \"Accept: application/json\" -H \"Content-Type: application/json\" -X POST --data \"{\\\"short_description\\\" : \\\"This is a test incident via CURL script\\\",\\\"description\\\" : \\\"Description\\\",\\\"caller_id\\\" : \\\"Ishaan\\\",\\\"impact\\\" : \\\"1\\\",\\\"urgency\\\":\\\"1\\\"}\" -u username:password https://instance_name.service-now.com/api/now/table/incident\n"
  },
  {
    "path": "Integration/Scripted REST Api/CopyAI Generative AI example/README.md",
    "content": "- Make sure you have a key stored in a system property named `openai.key`\n- Quick script to do a `gpt-3.5-turbo` chatgpt call\n- If you set `show_tokens` to true then it will also show the estimated cost of the prompt+response\n\nExample usage:\n![Alt text](image.png)"
  },
  {
    "path": "Integration/Scripted REST Api/CopyAI Generative AI example/script.js",
    "content": "var prompt = 'hello there!';\nvar show_tokens = false;\nvar chatReq = new sn_ws.RESTMessageV2();\nchatReq.setEndpoint('https://api.openai.com/v1/chat/completions');\nchatReq.setHttpMethod(\"POST\");\nchatReq.setRequestHeader(\"Authorization\", \"Bearer \" + gs.getProperty(\"openai.key\"));\nchatReq.setRequestHeader('Content-Type', \"application/json\");\nchatReq.setRequestHeader('User-Agent', \"ServiceNow\");\nchatReq.setRequestHeader(\"Accept\", \"*/*\");\nvar body = {\n  \"model\": \"gpt-3.5-turbo\",\n  \"messages\": [{\"role\": \"user\", \"content\": prompt}],\n//  \"max_tokens\": 250\n};\nchatReq.setRequestBody(JSON.stringify(body));\nvar chatResponse = chatReq.execute();\ngs.info(chatResponse.getBody());\nvar chatResponseBody = JSON.parse(chatResponse.getBody());\n\nvar token_cost = show_tokens ? \"> tokens: \" + chatResponseBody.usage.total_tokens + \" ($\" + (parseInt(chatResponseBody.usage.total_tokens) * 0.000002).toFixed(6) + \")\\n\" : \"\";\nvar response = token_cost + chatResponseBody.choices[0].message.content;\n\ngs.info(response);"
  },
  {
    "path": "Integration/Scripted REST Api/Create Catalog Items Dynamically/README.md",
    "content": "\n# ServiceNow Catalog Builder API  \n**Automate Catalog Item Creation with a Single REST Call**\n\n---\n\n## Overview\n\nThe **ServiceNow Catalog Builder API** is a custom **Scripted REST API** that dynamically creates Service Catalog Items in your instance — including variables and choices — from a simple JSON payload.  \n\nThis API eliminates the repetitive manual work of configuring Catalog Items one by one, and makes it possible to **automate catalog creation programmatically** or **integrate it with CI/CD pipelines, GitHub workflows, or external systems**.\n\n---\n\n## Key Features\n\nAutomatically create **Catalog Items** in `sc_cat_item`  \nDynamically generate **Variables** and **Choices**  \nSupports **category mapping** and **item ownership**  \nExtensible design for **flows, icons, and attachments**  \nDeveloper-friendly — fully JSON-driven  \n\n---\n\n## Use Case\n\nThis API is perfect for:\n- **Admin Automation:** Auto-build standard catalog forms during environment setup.  \n- **RPA / CI Pipelines:** Integrate with DevOps or GitHub Actions to deploy catalog definitions.  \n- **Dynamic Service Portals:** Allow external apps or portals to create items on demand.  \n\nExample:  \nA company wants to auto-create 10 new service catalog items from a GitHub configuration file.  \nUsing this API, they simply call one REST endpoint for each definition — no manual clicks needed.\n\n---\n\n## Scripted REST API Details\n\n| Property | Value |\n|-----------|--------|\n| **Name** | Catalog Builder API |\n| **API ID** | `x_demo.catalog_creator` |\n| **Resource Path** | `/create` |\n| **Method** | POST |\n| **Authentication** | Basic Auth / OAuth |\n| **Tables Used** | `sc_cat_item`, `item_option_new`, `question_choice` |\n\n---\n\n## Logic Flow\n\n1. **Receive JSON input** with item name, category, and variables.  \n2. **Create a new record** in `sc_cat_item`.  \n3. **Loop through variables** and create them in `item_option_new`.  \n4. If the variable type is `select_box`, create **choices** automatically.  \n5. Return a JSON response with the new item’s `sys_id` and success message.  \n\n---\n\n## Example Input (POST Body)\n\n```json\n{\n  \"name\": \"Request New Laptop\",\n  \"category\": \"Hardware\",\n  \"short_description\": \"Laptop provisioning request form\",\n  \"description\": \"Allows employees to request a new laptop with model and RAM options.\",\n  \"owner\": \"admin\",\n  \"variables\": [\n    {\n      \"name\": \"Laptop Model\",\n      \"type\": \"select_box\",\n      \"choices\": \"Dell,HP,Lenovo\"\n    },\n    {\n      \"name\": \"RAM Size\",\n      \"type\": \"select_box\",\n      \"choices\": \"8GB,16GB,32GB\"\n    },\n    {\n      \"name\": \"Business Justification\",\n      \"type\": \"multi_line_text\"\n    }\n  ]\n}\n\n\n## Example Output:\n{\n  \"catalog_sys_id\": \"b2f6329cdb6d0010355b5fb4ca9619e2\",\n  \"message\": \"Catalog item created successfully!\"\n}\nAfter the API call:\nA new Catalog Item appears under Maintain Items.\nThe item contains:\nShort Description: Laptop provisioning form\nVariables: Laptop Model, RAM Size, Business Justification\nChoices: Auto-populated for select boxes\nThe item is active and ready to use in the catalog.\n"
  },
  {
    "path": "Integration/Scripted REST Api/Create Catalog Items Dynamically/catalog.js",
    "content": "// Scenario : As a ServiceNow Admin or Developer managing dozens of similar request forms (like “Request Laptop”, “Request Mobile”, “Request Access”, etc.).\n// Manually creating each catalog item is repetitive.\n\n// This code will Automate Catalog Item Creation with a Single REST Call\n//Script: POST /api/x_demo/catalog_creator/create\n\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    var body = request.body.data;\n    var result = {};\n\n    try {\n        // 1. Create Catalog Item\n        var catItem = new GlideRecord('sc_cat_item');\n        catItem.initialize();\n        catItem.name = body.name;\n        catItem.short_description = body.short_description || '';\n        catItem.description = body.description || '';\n        catItem.category = getCategorySysId(body.category);\n        catItem.owning_group = getOwner(body.owner);\n        catItem.active = true;\n        var catSysId = catItem.insert();\n\n        result.catalog_sys_id = catSysId;\n\n        // 2. Create Variables\n        if (body.variables && body.variables.length > 0) {\n            for (var i = 0; i < body.variables.length; i++) {\n                var v = body.variables[i];\n\n                var variable = new GlideRecord('item_option_new');\n                variable.initialize();\n                variable.cat_item = catSysId;\n                variable.name = v.name.toLowerCase().replace(/ /g, '_');\n                variable.question_text = v.name;\n                variable.type = getType(v.type);\n                variable.order = (i + 1) * 100;\n                var varSysId = variable.insert();\n\n                // Add choices for select box variables\n                if (v.choices && v.choices.length > 0) {\n                    var choices = v.choices.split(',');\n                    for (var j = 0; j < choices.length; j++) {\n                        var choice = new GlideRecord('question_choice');\n                        choice.initialize();\n                        choice.question = varSysId;\n                        choice.value = choices[j].trim();\n                        choice.label = choices[j].trim();\n                        choice.insert();\n                    }\n                }\n            }\n        }\n\n        result.message = \"Catalog item created successfully!\";\n        response.setStatus(201);\n\n    } catch (e) {\n        gs.error(\"Error creating catalog item: \" + e);\n        result.message = e.toString();\n        response.setStatus(500);\n    }\n\n    response.setBody(result);\n\n\n    function getCategorySysId(categoryName) {\n        var cat = new GlideRecord('sc_category');\n        cat.addQuery('title', categoryName);\n        cat.query();\n        if (cat.next()) return cat.sys_id;\n        return null;\n    }\n\n    function getOwner(ownerName) {\n        var usr = new GlideRecord('sys_user');\n        usr.addQuery('user_name', ownerName);\n        usr.query();\n        if (usr.next()) return usr.sys_id;\n        return gs.getUserID();\n    }\n\n    function getType(typeName) {\n        var map = {\n            \"single_line_text\": 1,\n            \"multi_line_text\": 2,\n            \"select_box\": 3,\n            \"reference\": 8,\n            \"checkbox\": 5\n        };\n        return map[typeName] || 1;\n    }\n\n})(request, response);\n\n//Example JSON\n//{\n  \"name\": \"Request New Laptop\",\n  \"category\": \"Hardware\",\n  \"short_description\": \"Laptop provisioning form\",\n  \"description\": \"Allows employees to request a new laptop.\",\n  \"owner\": \"admin\",\n  \"variables\": [\n    {\n      \"name\": \"Laptop Model\",\n      \"type\": \"select_box\",\n      \"choices\": \"Dell,HP,Lenovo\"\n    },\n    {\n      \"name\": \"RAM Size\",\n      \"type\": \"select_box\",\n      \"choices\": \"8GB,16GB,32GB\"\n    },\n    {\n      \"name\": \"Business Justification\",\n      \"type\": \"multi_line_text\"\n    }\n  ]\n}\n\n"
  },
  {
    "path": "Integration/Scripted REST Api/Difference between two users/README.md",
    "content": "This is a scripted rest api that finds the differences between two user field and the value for the fields that are different between two users will print that in result.\n\nQuery parameters:-\nuser1Email    abc@example.com\nuser2Email    xyz@example.com\n"
  },
  {
    "path": "Integration/Scripted REST Api/Difference between two users/output.txt",
    "content": "Response body:-\n{\n  \"result\": [\n    {\n      \"field\": \"Updates\",\n      \"user1Value\": \"5\",\n      \"user2Value\": \"3\"\n    },\n    {\n      \"field\": \"Department\",\n      \"user1Value\": \"IT\",\n      \"user2Value\": \"Development\"\n    },\n    {\n      \"field\": \"User ID\",\n      \"user1Value\": \"abc.xyz\",\n      \"user2Value\": \"xyz.abc\"\n    },\n    {\n      \"field\": \"Updated\",\n      \"user1Value\": \"2023-06-26 04:26:36\",\n      \"user2Value\": \"2023-06-26 04:26:37\"\n    },\n    {\n      \"field\": \"Last name\",\n      \"user1Value\": \"xyz\",\n      \"user2Value\": \"abc\"\n    },\n    {\n      \"field\": \"Name\",\n      \"user1Value\": \"abc xyz\",\n      \"user2Value\": \"xyz abc\"\n    },\n    \n  ]\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/Difference between two users/script.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n    var user1Email = request.queryParams.user1Email; // taking email of user1 as query parameter\n    var user2Email = request.queryParams.user2Email; // taking email of user2 as query parameter\n\n    var user1GR = new GlideRecord('sys_user'); //querying over user record\n    user1GR.addQuery('email', user1Email);\n\t  user1GR.query();\n\t  user1GR.next();\n\n    var user2GR = new GlideRecord('sys_user');\n    user2GR.addQuery('email', user2Email);\n\t  user2GR.query();\n\t  user2GR.next();\n\n    var differences = [];\n\n    var fieldsGR = new GlideRecord('sys_dictionary'); //querying over dictionary record\n    fieldsGR.addQuery('name', 'sys_user'); //querying over particular user table in dictionary record\n    fieldsGR.query();\n    while (fieldsGR.next()) {\n        var fieldName = fieldsGR.element.getDisplayValue();\n        if (user1GR[fieldName] && user2GR[fieldName]) {\n            var user1Value = user1GR.getDisplayValue(fieldName);  //fetching the value of field for user1\n            var user2Value = user2GR.getDisplayValue(fieldName);  //fetching the value of field for user2\n            if (user1Value !== user2Value) { // checking the difference\n                differences.push({\n                    field: fieldsGR.column_label.getDisplayValue(),\n                    user1Value: user1Value,\n                    user2Value: user2Value\n                }); //stroing different value between two user in differences array\n            }\n        }\n    }\n    response.setStatus(200);\n    response.setBody(differences);  //setting the differences array in response body\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/DomainSeperation/README.md",
    "content": "# ServiceNow Scripted REST API for creating incdents in the correct company/domain\r\n\r\n## Overview\r\n\r\nThe API allows authenticated users to create new **Incident** records within their own domain and company context.\r\n\r\n> **DISCLAIMER**  \r\n> This script was developed and tested on a **ServiceNow Personal Developer Instance (PDI)**.  \r\n> It is intended for **educational and demonstration purposes only**.  \r\n> Please **test thoroughly in a dedicated development environment** before deploying to production.\r\n\r\n---\r\n\r\n## Features\r\n\r\n- Creates a new Incident record for the currently logged-in user.\r\n- Automatically assigns the user's domain and company to the incident.\r\n- Returns the generated incident number and domain in the response.\r\n\r\n---\r\n\r\n## Prerequisites & Dependencies\r\n\r\nBefore using or testing this Scripted REST API, ensure the following conditions are met:\r\n\r\n1. **Domain Separation Plugin**\r\n\r\n   - The **Domain Separation** plugin must be activated on your instance.\r\n   - This enables `sys_domain` references and ensures incidents are created within the correct domain context.\r\n\r\n2. **Core Data Setup**\r\n\r\n   - Ensure valid entries exist in the **core_company** table.\r\n   - Each company should have an associated **domain** record in the **sys_domain** table.\r\n   - These relationships are critical for correct domain assignment during incident creation.\r\n\r\n3. **User Configuration**\r\n\r\n   - The user invoking this API must:\r\n     - Belong to a specific domain.\r\n     - Have the **snc_platform_rest_api_access** role to access Scripted REST APIs.\r\n   - Users must also have ACL permissions to:\r\n     - **Read** from the `sys_user` table.\r\n     - **Insert** into the `incident` table.\r\n\r\n4. **Instance Configuration**\r\n   - Tested and validated on a **ServiceNow Personal Developer Instance (PDI)**.\r\n   - Other environments should be configured with equivalent domain and company data for consistent results.\r\n\r\n---\r\n\r\n## Information\r\n\r\n- **Author**: Anasuya Rampalli ([anurampalli](https://github.com/anurampalli))\r\n- **Version**: 1.0\r\n- **Date**: 2025-10-08\r\n- **Context**: Scripted REST API (`create` function)\r\n- **Tested On**: ServiceNow Personal Developer Instance (PDI)\r\n\r\n---\r\n\r\n## Expected Request Format\r\n\r\n```json\r\nPOST /api/your_namespace/your_endpoint\r\nContent-Type: application/json\r\n\r\n{\r\n  \"short_description\": \"Issue description text\"\r\n}\r\n```\r\n````\r\n\r\n---\r\n\r\n## Response Examples\r\n\r\n### Success\r\n\r\n```json\r\n{\r\n  \"status\": \"success\",\r\n  \"incident_id\": \"INC0012345\",\r\n  \"domain\": \"TOP/Child Domain\"\r\n}\r\n```\r\n\r\n### Error\r\n\r\n```json\r\n{\r\n  \"error\": {\r\n    \"message\": \"User Not Authenticated\",\r\n    \"detail\": \"Required to provide Auth information\"\r\n  },\r\n  \"status\": \"failure\"\r\n}\r\n```\r\n\r\n---\r\n\r\n## How It Works\r\n\r\n1. Extracts the `short_description` from the incoming JSON payload.\r\n2. Identifies the authenticated user via `gs.getUserID()`.\r\n3. Retrieves the user's domain and company using `sys_user`.\r\n4. Creates a new `incident` record with the user's domain, company, and description.\r\n5. Returns the incident number and domain in the response.\r\n\r\n---\r\n\r\n## Testing Tips\r\n\r\n- Use a valid ServiceNow PDI with Scripted REST API enabled.\r\n- Ensure the user is authenticated before making requests.\r\n- Check the `incident` table for newly created records.\r\n\r\n---\r\n\r\n"
  },
  {
    "path": "Integration/Scripted REST Api/DomainSeperation/create.js",
    "content": "/**\r\n *\r\n * This script is provided for **educational and demonstration purposes only**.\r\n * Please thoroughly **test in a dedicated development environment**\r\n * before deploying to production.\r\n *\r\n * -----------------------------------------------------------------------------\r\n * Script Purpose:\r\n * Creates a new Incident record under the same domain and company as the\r\n * currently logged-in user. Returns the generated incident number and domain.\r\n * -----------------------------------------------------------------------------\r\n *\r\n * @author  Anasuya Rampalli (anurampalli)\r\n * @version 1.0\r\n * @date    2025-10-08\r\n * @tested  On ServiceNow PDI (Personal Developer Instance)\r\n * @context Scripted REST API (process function)\r\n */\r\n\r\n/**\r\n * Processes the incoming REST API request and creates an Incident\r\n * for the authenticated user within their domain.\r\n *\r\n * @param {RESTAPIRequest} request  - The incoming REST API request object containing JSON payload.\r\n * @param {RESTAPIResponse} response - The response object used to send results back to the client.\r\n *\r\n * Expected JSON Body:\r\n * {\r\n *   \"short_description\": \"Issue description text\"\r\n * }\r\n *\r\n * Response Example (Success):\r\n * {\r\n *   \"status\": \"success\",\r\n *   \"incident_id\": \"INC0012345\",\r\n *   \"domain\": \"TOP/Child Domain\"\r\n * }\r\n *\r\n * Response Example (Error):\r\n * {\r\n *   \"error\": {\r\n *     \"message\": \"User Not Authenticated\",\r\n *     \"detail\": \"Required to provide Auth information\"\r\n *   },\r\n *   \"status\": \"failure\"\r\n * }\r\n */\r\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\r\n  var body = request.body.data;\r\n  var companyName = body.company;\r\n  var shortDesc = body.short_description;\r\n  //gs.info(gs.getUserID());\r\n  var userSysId = gs.getUserID();\r\n  var result = {};\r\n\r\n  try {\r\n    // looup user\r\n    var grUser = new GlideRecord(\"sys_user\");\r\n    grUser.addQuery(\"sys_id\", userSysId.toString());\r\n    grUser.query();\r\n    if (grUser.next()) {\r\n      var domain = grUser.sys_domain;\r\n      // Create new incident\r\n      var grIncident = new GlideRecord(\"incident\");\r\n      grIncident.initialize();\r\n      grIncident.short_description = shortDesc;\r\n      grIncident.caller_id = userSysId;\r\n      gs.info(\"COMPANY: \" + grUser.company.getDisplayValue());\r\n      grIncident.company = grUser.company;\r\n      grIncident.sys_domain = grUser.sys_domain; // domain reference comes from core_company\r\n      grIncident.insert();\r\n\r\n      let correlationId = grIncident.number;\r\n      gs.info(\r\n        \"Domain Indcident API: inserted incident number: \" + correlationId\r\n      );\r\n      result.status = \"success\";\r\n      result.incident_id = correlationId;\r\n      result.domain = grUser.sys_domain.getDisplayValue();\r\n    } else {\r\n      response.setStatus(404);\r\n      result.status = \"error\";\r\n      result.message = \"User not found: \" + companyName;\r\n    }\r\n  } catch (e) {\r\n    response.setStatus(500);\r\n    result.status = \"error\";\r\n    result.message = e.message;\r\n  }\r\n\r\n  response.setBody(result);\r\n})(request, response);\r\n\r\n"
  },
  {
    "path": "Integration/Scripted REST Api/Get_Choices/README.md",
    "content": "# This code will help to query choice table and get the data according to filter. Modify this according to requirement\n# References \n- Below is the reference of PDI, what is the configuration of choices\n![scripted_rest_resource.png](scripted_rest_resource.png)\n- Below is the referecnce of PDI resources of Script\n![script.png](script.png)"
  },
  {
    "path": "Integration/Scripted REST Api/Get_Choices/script.js",
    "content": "(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    var queryParams = request.queryParams;\n\n    var grChoice = new GlideRecord('sys_choice');\n    grChoice.addQuery('name', queryParams.name);\n    grChoice.addQuery('element', queryParams.element);\n    grChoice.query();\n\n    var filterList = [];\n    while (grChoice.next()) {\n        var label = grChoice.getValue(\"label\");\n        var value = grChoice.getValue(\"value\");\n        var object = {\n            code: value,\n            country: label\n        };\n        filterList.push(object);\n    }\n\n    return filterList;\n})(request, response);"
  },
  {
    "path": "Integration/Scripted REST Api/Group Membership API/README.md",
    "content": "# Group Membership API- Scripted REST API\n## Overview\nThis API provides a simple, secure way to reterive all members of a specified user group in ServiceNow. It allows integrations, Service Portal widgets, or external systems to query group membership without giving direct access to user tables\n\n### API Details\n- **API Name**: Group Membership API\n- **API ID**: group_membership_api\n- **ResourceName**: Members\n- **Relative Path**: /members\n- **HTTP Method**: GET\n- **Query Parameter**: groupName (required)\n\n## Request Format\n\n### Example Request\nGET https://<instance>.service-now.com/api/1819147/group_membership_api/members?groupName=Hardware\n\n### Example Response\n```json\n{\n   {\n  \"result\": {\n    \"groupName\": \"Hardware\",\n    \"totalMembers\": 7,\n    \"member\": [\n      {\n        \"userName\": \"beth.anglin\",\n        \"displayName\": \"Beth Anglin\",\n        \"email\": \"beth.anglin@example.com\",\n        \"active\": \"true\"\n      },\n      {\n        \"userName\": \"itil\",\n        \"displayName\": \"ITIL User\",\n        \"email\": \"itil@example.com\",\n        \"active\": \"true\"\n      },\n      {\n        \"userName\": \"bow.ruggeri\",\n        \"displayName\": \"Bow Ruggeri\",\n        \"email\": \"bow.ruggeri@example.com\",\n        \"active\": \"true\"\n      },\n      {\n        \"userName\": \"david.dan\",\n        \"displayName\": \"David Dan\",\n        \"email\": \"david.dan@example.com\",\n        \"active\": \"true\"\n      },\n      {\n        \"userName\": \"david.loo\",\n        \"displayName\": \"David Loo\",\n        \"email\": \"david.loo@example.com\",\n        \"active\": \"true\"\n      },\n      {\n        \"userName\": \"don.goodliffe\",\n        \"displayName\": \"Don Goodliffe\",\n        \"email\": \"don.goodliffe@example.com\",\n        \"active\": \"true\"\n      },\n      {\n        \"userName\": \"fred.luddy\",\n        \"displayName\": \"Fred Luddy\",\n        \"email\": \"fred.luddy@example.com\",\n        \"active\": \"true\"\n      }\n    ]\n  }\n}\n\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/Group Membership API/group_membership.js",
    "content": "(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n    var groupName = request.queryParams.groupName;\n    var members = [];\n    if (!groupName) {\n        response.setStatus(400);\n        return {\n            error: \"groupName query parameter is required\"\n        };\n    }\n    var grGrp = new GlideRecord('sys_user_group');\n    grGrp.addQuery('name', groupName);\n    grGrp.query();\n    if (!grGrp.next()) {\n        response.setStatus(400);\n        return {\n            error: \"Group name doesn't found\"\n        };\n    }\n    var grGrpMem = new GlideRecord('sys_user_grmember');\n    grGrpMem.addQuery(\"group.name\", groupName);\n    grGrpMem.query();\n    while (grGrpMem.next()) {\n        members.push({\n            userName: grGrpMem.user.user_name.toString(),\n            displayName: grGrpMem.user.name.toString(),\n            email: grGrpMem.user.email.toString(),\n\t\t\tactive: grGrpMem.user.active.toString()\n        });\n    }\n    return {\n        groupName: groupName.toString(),\n        totalMembers: members.length,\n        member: members\n    };\n\n\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/MID Server status JSON endpoint/README.md",
    "content": "# MID Server status JSON endpoint\n\n## What this solves\nOperations teams often need a quick machine-readable view of MID Server health for dashboards and monitors. This Scripted REST API returns a compact JSON array of MID Servers with their status, last update time, and a simple \"stale\" flag if the record has not changed recently.\n\n## Where to use\nCreate a Scripted REST API with a single Resource and paste this script as the Resource Script. Call it from monitoring tools, dashboards, or widgets.\n\n## How it works\n- Queries `ecc_agent` for active MID Servers\n- Returns `name`, `status`, `sys_id`, `sys_updated_on`, and a computed `stale` boolean based on a configurable `minutes_stale` query parameter (default 15)\n- Uses `gs.dateDiff` to compute minutes since last update\n\n## Configure\n- Pass `minutes_stale` as a query parameter to override the default, for example `...?minutes_stale=30`\n- Extend the payload as needed (for example add `version`, `ip_address`) if available in your instance\n\n## References\n- Scripted REST APIs  \n  https://www.servicenow.com/docs/bundle/zurich-application-development/page/build/applications/task/create-scripted-rest-api.html\n- MID Server overview  \n  https://www.servicenow.com/docs/bundle/zurich-servicenow-platform/page/product/mid-server/concept/c_MIDServer.html\n- GlideRecord API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideRecord/concept/c_GlideRecordAPI.html\n- GlideDateTime and dateDiff  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html\n"
  },
  {
    "path": "Integration/Scripted REST Api/MID Server status JSON endpoint/mid_server_status_api.js",
    "content": "// Scripted REST API Resource Script: MID Server status JSON endpoint\n// Method: GET\n// Path: /mid/status\n\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n  try {\n    // Configurable staleness threshold in minutes via query param\n    var q = request.queryParams || {};\n    var minutesStale = parseInt((q.minutes_stale && q.minutes_stale[0]) || '15', 10);\n    if (!isFinite(minutesStale) || minutesStale <= 0) minutesStale = 15;\n\n    var now = new GlideDateTime();\n\n    var out = [];\n    var gr = new GlideRecord('ecc_agent'); // MID Server table\n    gr.addActiveQuery();\n    gr.orderBy('name');\n    gr.query();\n\n    while (gr.next()) {\n      var updated = String(gr.getValue('sys_updated_on') || '');\n      var minutesSince = 0;\n      if (updated) {\n        // gs.dateDiff returns seconds when third arg is true\n        minutesSince = Math.floor(gs.dateDiff(updated, now.getValue(), true) / 60);\n      }\n\n      out.push({\n        sys_id: gr.getUniqueValue(),\n        name: gr.getDisplayValue('name') || gr.getValue('name'),\n        status: gr.getDisplayValue('status') || gr.getValue('status'), // Up, Down, etc.\n        sys_updated_on: gr.getDisplayValue('sys_updated_on'),\n        minutes_since_update: minutesSince,\n        stale: minutesSince >= minutesStale\n      });\n    }\n\n    response.setStatus(200);\n    response.setBody(out);\n  } catch (e) {\n    response.setStatus(500);\n    response.setBody({ error: String(e) });\n  }\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/Promise API Call/README.md",
    "content": "Allows you to make async/await promise API Calls using Request\n"
  },
  {
    "path": "Integration/Scripted REST Api/Promise API Call/promiseApiCall.js",
    "content": "module.exports.makeAPICalls = (options) => {\n  return new Promise((resolve, reject) => {\n    request(options, (error, response, body) => {\n      if (error) {\n        return resolve({\n          status: false,\n          error: error\n        });\n      }\n\n      try {\n        body = JSON.parse(body)\n      } catch (err) {\n      }\n      return resolve({\n        status: true,\n        data: body\n      });\n    });\n  });\n};\n"
  },
  {
    "path": "Integration/Scripted REST Api/Retrieve all variables from RITM/CHVarUtils_ScriptInclude.js",
    "content": "var CHVarUtils = Class.create();\nCHVarUtils.prototype = {\n    initialize: function() {},\n    getTaskVarsById: function(taskId) {\n        \n        //Given a System ID, find all variables and their internal and display values.\n        var resultJson = {};\n        var varArr = [];\n        var mrvArr = [];\n        var varCount = 0;\n        var mrvCount = 0;\n        var taskTable = \"\";\n        var info = \"\";\n\n        if (taskId) {\n\n            var ts = new GlideRecord(\"task\");\n            ts.get(\"sys_id\", taskId);\n\n            if (ts.sys_id) {\n                if (ts.sys_class_name) {\n                    taskTable = ts.sys_class_name;\n\n                    //Get regular variables for SCTASKs and RITMs\n                    if (taskTable == \"sc_req_item\" || taskTable == \"sc_task\") {\n                        var item = \"\";\n                        var itemName = \"\";\n                        var itemRec = \"\";\n\n                        var ci = new GlideRecord(taskTable);\n                        ci.get(\"sys_id\", taskId);\n\n                        if (taskTable == \"sc_req_item\") {\n                            item = ci.cat_item.sys_id;\n                            itemRec = ci.sys_id;\n                            itemName = ci.cat_item.name;\n                        } else {\n                            item = ci.request_item.cat_item.sys_id;\n                            itemRec = ci.request_item.sys_id;\n                            itemName = ci.request_item.cat_item.name;\n                        }\n\n                        var vars = ci.variables.getElements(true);\n\n                        for (var x = 0; x < vars.length; x++) {\n\n                            var v = vars[x];\n\n                            if (!v.isMultiRow()) {\n                                info = v.toString();\n                                var varData = {};\n                                varData.name = v.getLabel();\n                                varData.value = v.getValue();\n                                varData.displayValue = v.getDisplayValue();\n                                varCount += 1;\n                                varArr.push(varData);\n                            } else {\n                                var mrVarTable = {};\n                                mrVarTable.name = v.getLabel();                                \n                                mrvCount += 1;\n                                var rows = v.getRows();\n\t\t\t\t\t\t\t\tmrVarTable.rowCount = rows.length;\n                                var mrRowArr = [];\n\t\t\t\t\t\t\t\tvar mrRowCollection = [];\n\n                                for (var j = 0; j < rows.length; j++) {\n                                    var mrRowData = [];\n                                    var cells = rows[j].getCells();\n                                   for (var k = 0; k < cells.length; k++) {\n                                        var mrCellData = {};\n                                        mrCellData.name = cells[k].getLabel();\n                                        mrCellData.value = cells[k].getCellValue();\n                                        mrCellData.displayValue = cells[k].getCellDisplayValue();\n                                        mrRowData.push(mrCellData);\n                                   }\n\t\t\t\t\t\t\t\t\tmrRowCollection.push(mrRowData);\n                                }\n                                mrVarTable.rowCollection = mrRowCollection || \"(no data)\";\n\n                                mrvArr.push(mrVarTable);\n\n                            }\n\n                        }\n\n                    }\n                }\n\n                resultJson.taskId = taskId;\n                resultJson.taskNumber = ts.number;\n                resultJson.state = ts.state.getDisplayValue();\n                resultJson.stage = ts.stage.getDisplayValue();\n                resultJson.approval = ts.approval.getDisplayValue();\n                resultJson.taskType = taskTable;\n                resultJson.taskShortDescription = ts.short_description;\n                resultJson.additionalInfo = info;\n                resultJson.variableCount = varCount;\n                resultJson.multiRowVariableCount = mrvCount;\n                resultJson.variables = varArr;\n                resultJson.multiRowVariables = mrvArr;\n\n                return resultJson;\n            }\n        }\n    },\n    type: 'CHVarUtils'\n};\n\n​\n"
  },
  {
    "path": "Integration/Scripted REST Api/Retrieve all variables from RITM/README.md",
    "content": "# Retrieve all variables (including multi-row) from any Request Item or Catalog Task - and push JSON for use in a Scripted REST API\nIt's really not that hard to do using existing APIs, but if you want to pull a list of variables and their values, it gets a little messy. \nIf you want values from a Multi-Row Variable Set, it gets even messier.  So, we set about building a Scripted REST API our partners can use. \n\n 1. [Build a Scripted REST API- resource configuration in the comments of the script](scripted_rest_api.js) \n 2. [Build a script include (the one referenced in the resource script above)](CHVarUtils_ScriptInclude.js)\n 3. [Sample Output return in JSON format](output_example.js)\n\n"
  },
  {
    "path": "Integration/Scripted REST Api/Retrieve all variables from RITM/output_example.js",
    "content": "{\n  \"result\": {\n    \"taskId\": \"6dea3cdeec974462a7e751f414235ec6\",\n    \"taskNumber\": \"RITM0106176\",\n    \"state\": \"Open\",\n    \"stage\": null,\n    \"approval\": \"Requested\",\n    \"taskType\": \"sc_req_item\",\n    \"taskShortDescription\": \"Request the creation, modification, or removal of a network firewall rule or AWS Security Group rule\",\n    \"additionalInfo\": \"Testing our RITM Variable API\",\n    \"variableCount\": 4,\n    \"multiRowVariableCount\": 2,\n    \"variables\": [\n      {\n        \"name\": \"Rule Type\",\n        \"value\": \"both\",\n        \"displayValue\": \"Both\"\n      },\n      {\n        \"name\": \"Application Group Impacted\",\n        \"value\": \"16de082f69ab40bd95999cb6d691e2ea\",\n        \"displayValue\": \"My Application\"\n      },\n      {\n        \"name\": \"Project or Effort\",\n        \"value\": \"API Testing\",\n        \"displayValue\": \"API Testing\"\n      },\n      {\n        \"name\": \"Description\",\n        \"value\": \"Testing our RITM Variable API\",\n        \"displayValue\": \"Testing our RITM Variable API\"\n      }\n    ],\n    \"multiRowVariables\": [\n      {\n        \"name\": \"AWS Security Group Rules\",\n        \"rowCount\": 3,\n        \"rowCollection\": [\n          [\n            {\n              \"name\": \"Action\",\n              \"value\": \"add\",\n              \"displayValue\": \"Add Rule\"\n            },\n            {\n              \"name\": \"Rule Type\",\n              \"value\": \"inbound\",\n              \"displayValue\": \"Inbound\"\n            },\n            {\n              \"name\": \"AWS Account\",\n              \"value\": \"some_account\",\n              \"displayValue\": \"Some Account\"\n            },\n            {\n              \"name\": \"Security Group\",\n              \"value\": \"sg-123456\",\n              \"displayValue\": \"sg-123456\"\n            },\n            {\n              \"name\": \"Protocol\",\n              \"value\": \"tcp\",\n              \"displayValue\": \"TCP\"\n            },\n            {\n              \"name\": \"IP Address\",\n              \"value\": \"100.100.100.1\",\n              \"displayValue\": \"100.100.100.1\"\n            },\n            {\n              \"name\": \"Port(s)\",\n              \"value\": \"500\",\n              \"displayValue\": \"500\"\n            },\n            {\n              \"name\": \"Temporary\",\n              \"value\": \"false\",\n              \"displayValue\": \"false\"\n            },\n            {\n              \"name\": \"Expiration Date\",\n              \"value\": \"\",\n              \"displayValue\": \"\"\n            }\n          ],\n          [\n            {\n              \"name\": \"Action\",\n              \"value\": \"add\",\n              \"displayValue\": \"Add Rule\"\n            },\n            {\n              \"name\": \"Rule Type\",\n              \"value\": \"outbound\",\n              \"displayValue\": \"Outbound\"\n            },\n            {\n              \"name\": \"AWS Account\",\n              \"value\": \"my_account\",\n              \"displayValue\": \"My Account\"\n            },\n            {\n              \"name\": \"Security Group\",\n              \"value\": \"sg-blahblahblah\",\n              \"displayValue\": \"sg-blahblahblah\"\n            },\n            {\n              \"name\": \"Protocol\",\n              \"value\": \"tcp\",\n              \"displayValue\": \"TCP\"\n            },\n            {\n              \"name\": \"IP Address\",\n              \"value\": \"200.200.100.1\",\n              \"displayValue\": \"200.200.100.1\"\n            },\n            {\n              \"name\": \"Port(s)\",\n              \"value\": \"500,600\",\n              \"displayValue\": \"500,600\"\n            },\n            {\n              \"name\": \"Temporary\",\n              \"value\": \"true\",\n              \"displayValue\": \"true\"\n            },\n            {\n              \"name\": \"Expiration Date\",\n              \"value\": \"2021-11-02\",\n              \"displayValue\": \"2021-11-02\"\n            }\n          ],\n          [\n            {\n              \"name\": \"Action\",\n              \"value\": \"remove\",\n              \"displayValue\": \"Remove Rule\"\n            },\n            {\n              \"name\": \"Rule Type\",\n              \"value\": \"inbound\",\n              \"displayValue\": \"Inbound\"\n            },\n            {\n              \"name\": \"AWS Account\",\n              \"value\": \"R & D\",\n              \"displayValue\": \"R & D\"\n            },\n            {\n              \"name\": \"Security Group\",\n              \"value\": \"sg-blahblahblah\",\n              \"displayValue\": \"sg-blahblahblah\"\n            },\n            {\n              \"name\": \"Protocol\",\n              \"value\": \"tcp\",\n              \"displayValue\": \"TCP\"\n            },\n            {\n              \"name\": \"IP Address\",\n              \"value\": \"200.100.100.3\",\n              \"displayValue\": \"200.100.100.3\"\n            },\n            {\n              \"name\": \"Port(s)\",\n              \"value\": \"89\",\n              \"displayValue\": \"89\"\n            },\n            {\n              \"name\": \"Temporary\",\n              \"value\": \"false\",\n              \"displayValue\": \"false\"\n            },\n            {\n              \"name\": \"Expiration Date\",\n              \"value\": \"\",\n              \"displayValue\": \"\"\n            }\n          ]\n        ]\n      },\n      {\n        \"name\": \"Firewall Rules\",\n        \"rowCount\": 2,\n        \"rowCollection\": [\n          [\n            {\n              \"name\": \"Action\",\n              \"value\": \"add\",\n              \"displayValue\": \"Add Rule\"\n            },\n            {\n              \"name\": \"Rule Type\",\n              \"value\": \"permit\",\n              \"displayValue\": \"Permit\"\n            },\n            {\n              \"name\": \"Protocol\",\n              \"value\": \"tcp\",\n              \"displayValue\": \"TCP\"\n            },\n            {\n              \"name\": \"Port(s)\",\n              \"value\": \"100\",\n              \"displayValue\": \"100\"\n            },\n            {\n              \"name\": \"Source IP Address\",\n              \"value\": \"100.100.100.1\",\n              \"displayValue\": \"100.100.100.1\"\n            },\n            {\n              \"name\": \"Destination IP Address\",\n              \"value\": \"100.200.100.2\",\n              \"displayValue\": \"100.200.100.2\"\n            },\n            {\n              \"name\": \"Temporary\",\n              \"value\": \"true\",\n              \"displayValue\": \"true\"\n            },\n            {\n              \"name\": \"Expiration Date\",\n              \"value\": \"2021-11-03\",\n              \"displayValue\": \"2021-11-03\"\n            }\n          ],\n          [\n            {\n              \"name\": \"Action\",\n              \"value\": \"add\",\n              \"displayValue\": \"Add Rule\"\n            },\n            {\n              \"name\": \"Rule Type\",\n              \"value\": \"permit\",\n              \"displayValue\": \"Permit\"\n            },\n            {\n              \"name\": \"Protocol\",\n              \"value\": \"tcp\",\n              \"displayValue\": \"TCP\"\n            },\n            {\n              \"name\": \"Port(s)\",\n              \"value\": \"80, 443\",\n              \"displayValue\": \"80, 443\"\n            },\n            {\n              \"name\": \"Source IP Address\",\n              \"value\": \"200.100.100.1\",\n              \"displayValue\": \"200.100.100.1\"\n            },\n            {\n              \"name\": \"Destination IP Address\",\n              \"value\": \"200.200.100.2\",\n              \"displayValue\": \"200.200.100.2\"\n            },\n            {\n              \"name\": \"Temporary\",\n              \"value\": \"false\",\n              \"displayValue\": \"false\"\n            },\n            {\n              \"name\": \"Expiration Date\",\n              \"value\": \"\",\n              \"displayValue\": \"\"\n            }\n          ]\n        ]\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/Retrieve all variables from RITM/scripted_rest_api.js",
    "content": "/*\nHTTP Method: GET\nRelative Path: /request_item/{sys_id} (we added /request_item because we'll probably end up building something similar for record producers \nthe script include we built is already set up to determine whether the provided sys_id is for a Request Item or a Catalog Task).\n*/\n\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    var taskId = decodeURI(request.pathParams.sys_id);\n\tvar result = [];\n\tvar varHandler = new CHVarUtils();\n\tresult = varHandler.getTaskVarsById(taskId);\n\tif(!result)\n\t\treturn sn_ws_err.NotFoundError(\"No variables found, please ensure you are passing a valid ServiceNow task record System ID (sys_id)\");\n\t\t\n\treturn result;\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/Tag API/README.md",
    "content": "This utility contains a scripted REST API which helps to insert tags in records with required parameters.\n\nBelow is the REST endpoint to access this SRAPI.\n\nhttps://<<YOUR_INSTANCE_NAME>>>.service-now.com/api/gmi/insert_tags\n\nSample Code to call this SRAPI is below:\n\n```r\nvar request = new sn_ws.RESTMessageV2();\nrequest.setEndpoint('https://<<YOUR_INSTANCE_NAME>>>.service-now.com/api/gmi/insert_tags');\nrequest.setHttpMethod('POST');\n\n//Eg. UserName=\"admin\", Password=\"admin\" for this code sample.\nvar user = 'admin';\nvar password = 'admin';\n\nrequest.setBasicAuth(user,password);\nrequest.setRequestHeader(\"Accept\",\"application/json\");\nrequest.setRequestHeader('Content-Type','application/json');\nrequest.setRequestBody(\"{\n    \\\"title\\\": \\\"my test7\\\",\n     \\\"read\\\": \\\"yes\\\",\n    \\\"table\\\" : \\\"cmdb_ci_computer\\\",\n     \\\"table_key\\\" : \\\"aac0b1213784200044e0bfc8bcbe5de3\\\"\n    \n\n}\");\nvar response = request.execute();\ngs.log(response.getBody());\n\n```\n\nSample Payload is below:\n\n```r\n\n{\n    \"title\": \"my test7\",\n     \"read\": \"yes\",\n    \"table\" : \"cmdb_ci_computer\",\n     \"table_key\" : \"aac0b1213784200044e0bfc8bcbe5de3\"\n    \n\n}\n\n```\n"
  },
  {
    "path": "Integration/Scripted REST Api/Tag API/insert-tag.js",
    "content": "(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n\n    // implement resource here\n\n    var t = request.body.data.title;\n    var l = request.body.data.label;\n    var url = request.body.data.url;\n    var read = request.body.data.read;\n    var table_key = request.body.data.table_key;\n    var table = request.body.data.table;\n\n    var t = request.body.data.title;\n\n    var l = new GlideRecord('label');\n    l.initialize();\n    l.name = t;\n\tl.owner = '6816f79cc0a8016401c5a33be04be441';// sys_id of system admin user\n\tl.type = 'standard';\n\tl.viewable_by = 'everyone';\n\tvar id = l.insert();\n\n\n\n    var entry = new GlideRecord('label_entry');\n    entry.initialize();\n    entry.title = t;\n    entry.label = id;\n    entry.table = table;\n    entry.table_key = table_key;\n    entry.read = read;\n    entry.url = table + \".do?sys_id=\" + table_key;\n    entry.setWorkflow(false);\n    var le = entry.insert();\n\n    response.setBody({\n        'URL': gs.getProperty('glide.servlet.uri') + 'label_entry_list.do?sysparm_query=sys_id=' + le,\n        'sys_id': le\n\n\n\n    });\n\n\n\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/Update the variables or fields in sc_task or RITM/README.md",
    "content": "This is one of the most commonly used scenarios in many integrations to close the sc_task or to update the variables(including multi-row) and fields in the sc_task or request item form.\n[Scripted Rest API](https://github.com/thejasr110/code-snippets/blob/tjr_CodeSnippets/Scripted%20REST%20Api/Update%20the%20variables%20or%20fields%20in%20sc_task%20or%20RITM/Scripted_rest_ap.js)\n"
  },
  {
    "path": "Integration/Scripted REST Api/Update the variables or fields in sc_task or RITM/Scripted_rest_api.js",
    "content": "/*\nhttp method-PUT\nResource path-/api/x/Update_sc_task\nRelative path-/task_update\nAPI- https://xx.service-now.com/api/x/Update_sc_task/task_update\nRequest body-\n{\n\"sys_id\":\"\",\n\"mrvsStatus\":\"\",\n\"Worknotes\":\"\",\n\"mrvsvariable:\"\"\n}\n*/\n\n(function process( /*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\nvar response = {}; // Initialize the response object to store the outcome of the operation\nvar requestBody = request.body; // Get the request body from the incoming request\nvar requestData = requestBody.data; // Extract the data object from the request body\n\nvar sys_id = requestData.sys_id; // Getting the sys_id from the request body\nvar Status = requestData.mrvsStatus; // Getting the status value from the request body\nvar Worknotes = requestData.Worknotes; // Getting worknotes details from the request body\nvar mrvs_variable1 = requestData.mrvsvariable; // One of the rows of MRVS having a unique value\n\n// To update the MRVS and fields of Sc_task\nvar task = new GlideRecord('sc_task'); \ntask.addQuery('sys_id', sys_id); // Add a query to find the task by its sys_id\ntask.query();\n\nif (task.next()) { \n    var mrvs = task.variables.mrvsname; // Get the MRVS variable from the task record\n    var mrvsparsed = JSON.parse(mrvs); // Parse the MRVS JSON string into an object\n\n    // Iterate through the parsed MRVS array to find the correct row to update\n    for (var i = 0; i < mrvsparsed.length; i++) {\n        // Finding the right row to update based on the MRVS variable value\n        if (mrvsparsed[i].mrvsvar1 == mrvs_variable1) {\n            mrvsparsed[i].mrvs_status_variable = Status; // Update the status variable for the found row\n            // break; // Uncomment if you want to exit the loop after updating the first match\n        }\n    }\n\n    task.work_notes = Worknotes; // Update the task's work notes with the provided details\n    task.update(); \n\t\n    response = 'Sc_task worknotes updated successfully'; // Set the response message indicating success\n    return response; \n    }\n\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/Webhook receiver with HMAC SHA-256 validation/HmacUtils.js",
    "content": "// Script Include: HmacUtils\n// Purpose: Compute HMAC SHA-256 and constant-time compare.\n\nvar HmacUtils = Class.create();\nHmacUtils.prototype = {\n  initialize: function() {},\n\n  hmacSha256Hex: function(secret, message) {\n    var mac = Packages.javax.crypto.Mac.getInstance('HmacSHA256');\n    var key = new Packages.javax.crypto.spec.SecretKeySpec(\n      new Packages.java.lang.String(secret).getBytes('UTF-8'),\n      'HmacSHA256'\n    );\n    mac.init(key);\n    var raw = mac.doFinal(new Packages.java.lang.String(message).getBytes('UTF-8'));\n\n    var sb = new Packages.java.lang.StringBuilder();\n    for (var i = 0; i < raw.length; i++) {\n      var hex = Packages.java.lang.Integer.toHexString((raw[i] & 0xff) | 0x100).substring(1);\n      sb.append(hex);\n    }\n    return sb.toString();\n  },\n\n  constantTimeEquals: function(a, b) {\n    var A = String(a || '');\n    var B = String(b || '');\n    if (A.length !== B.length) return false;\n    var diff = 0;\n    for (var i = 0; i < A.length; i++) diff |= A.charCodeAt(i) ^ B.charCodeAt(i);\n    return diff === 0;\n  },\n\n  type: 'HmacUtils'\n};\n"
  },
  {
    "path": "Integration/Scripted REST Api/Webhook receiver with HMAC SHA-256 validation/README.md",
    "content": "# Webhook receiver with HMAC SHA-256 validation\n\n## What this solves\nInbound webhooks should be verified to ensure the payload really came from the sender. This receiver validates an `X-Signature` header containing an HMAC SHA-256 of the request body using a shared secret. Invalid signatures return HTTP 401.\n\n## Where to use\n- Scripted REST API resource script\n- Include the `HmacUtils` Script Include in the same app or global\n\n## How it works\n- Reads raw request body and the `X-Signature` header\n- Computes HMAC SHA-256 using the shared secret\n- Compares in constant time to avoid timing attacks\n- If valid, inserts the payload into a target table or queues it for processing\n\n## Configure\n- Set `SHARED_SECRET` (prefer credentials or encrypted properties)\n- Update `TARGET_TABLE` for successful inserts\n\n## References\n- Scripted REST APIs  \n  https://www.servicenow.com/docs/bundle/zurich-application-development/page/build/applications/task/create-scripted-rest-api.html\n- REST API request/response objects  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideHTTPRequest/concept/c_scripted-rest-api-request.html\n- Java crypto (used server-side)  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/Script/server_apis/concept/java-use.html\n"
  },
  {
    "path": "Integration/Scripted REST Api/Webhook receiver with HMAC SHA-256 validation/WebhookHmacReceiver.js",
    "content": "// Scripted REST API Resource Script: Webhook receiver with HMAC validation\n(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n  var SHARED_SECRET = gs.getProperty('x_acme.webhook.secret', '');\n  var TARGET_TABLE = 'x_acme_inbound_webhook'; // replace with your table\n\n  try {\n    var body = request.body && request.body.data ? request.body.data : '';\n    var signature = request.getHeader('X-Signature') || ''; // hex HMAC hash\n\n    if (!SHARED_SECRET) {\n      response.setStatus(500);\n      response.setBody({ error: 'Server not configured' });\n      return;\n    }\n    if (!signature || !body) {\n      response.setStatus(400);\n      response.setBody({ error: 'Missing signature or body' });\n      return;\n    }\n\n    var util = new HmacUtils();\n    var expected = util.hmacSha256Hex(SHARED_SECRET, body);\n\n    if (!util.constantTimeEquals(expected, signature)) {\n      response.setStatus(401);\n      response.setBody({ error: 'Invalid signature' });\n      return;\n    }\n\n    // Valid payload: insert a record for processing\n    var rec = new GlideRecord(TARGET_TABLE);\n    rec.initialize();\n    rec.payload = body;\n    rec.signature = signature;\n    rec.insert();\n\n    response.setStatus(200);\n    response.setBody({ ok: true });\n  } catch (e) {\n    response.setStatus(500);\n    response.setBody({ error: String(e) });\n  }\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted REST Api/compare roles/README.md",
    "content": "This is a scripted rest api and print the difference between two fields\n\nQuery parameters:\nrole1: role\n\nrole2:role\n"
  },
  {
    "path": "Integration/Scripted REST Api/compare roles/output.txt",
    "content": "Response Body:\n\n{\n  \"result\": [\n    {\n      \"field\": \"Sys ID\",\n      \"role1Value\": \"abc\",\n      \"role2Value\": \"xyz\"\n    },\n    {\n      \"field\": \"Description\",\n      \"role1Value\": \"Role assigned to Account recovery users while they login in recovery mode\",\n      \"role2Value\": \"This role give workflow users the ability to create custom orchestration activities in the workflow canvas.\"\n    },\n    {\n      \"field\": \"Name\",\n      \"role1Value\": \"acr_admin\",\n      \"role2Value\": \"activity_creator\"\n    }\n  ]\n}\n"
  },
  {
    "path": "Integration/Scripted REST Api/compare roles/script.js",
    "content": "(function process(/*RESTAPIRequest*/ request, /*RESTAPIResponse*/ response) {\n    var role1 = request.queryParams.role1;\n    var role2 = request.queryParams.role2; \n\n    var role1GR = new GlideRecord('sys_user_role');\n    role1GR.addQuery('name', role1);\n\trole1GR.query();\n\trole1GR.next();\n\n    var role2GR = new GlideRecord('sys_user_role');\n    role2GR.addQuery('name', role2);\n\trole2GR.query();\n\trole2GR.next();\n\n    var differences = [];\n\n    var fieldsGR = new GlideRecord('sys_dictionary'); //querying over dictionary record\n    fieldsGR.addQuery('name', 'sys_user_role'); //querying over particular role table in dictionary record\n    fieldsGR.query();\n    while (fieldsGR.next()) {\n        var fieldName = fieldsGR.element.getDisplayValue();\n        if (role1GR[fieldName] && role2GR[fieldName]) {\n            var role1Value = role1GR.getDisplayValue(fieldName);  //fetching the value of field for role1\n            var role2Value = role2GR.getDisplayValue(fieldName);  //fetching the value of field for role2\n            if (role1Value !== role2Value) { // checking the difference\n                differences.push({\n                    field: fieldsGR.column_label.getDisplayValue(),\n                    role1Value: role1Value,\n                    role2Value: role2Value\n                }); //stroing different value between two role in differences array\n            }\n        }\n    }\n    response.setStatus(200);\n    response.setBody(differences);  //setting the differences array in response body\n})(request, response);\n"
  },
  {
    "path": "Integration/Scripted SOAP Incident Creation/Scripted SOAP incident creation/README.md",
    "content": "I have created a scripted SOAP service for creation of incident with the help of third party tools, with all mandatory validations. My API will check the validations while creating the incidents from third party tool and it won't allow to submit the record untill all validations will pass.\n"
  },
  {
    "path": "Integration/Scripted SOAP Incident Creation/Scripted SOAP incident creation/Scripted SOAP incident creation.js",
    "content": "(function scriptedWebServiceOperation(request, response) {\n  \n\tvar v1=checkMandatory();  // This function will check the all mandatory validations.\n\tif(v1==true){\n\t\tvar grinc = new GlideRecord('incident');\n\t\tgrinc.initialize();\n\t\tgrinc.caller_id=request.Caller_id;\n\t\tgrinc.short_description=request.Short_Description;\n\t\tgrinc.cmdb_ci=request.CI;\n\t\tgrinc.insert();  // It will insert the record, with provided values.\n\t\tresponse.Result='Incident Created';\n\t\tresponse.Sys_Id=grinc.sys_id;  // It will return the sys_id in response body.\n\t\tresponse.Number=grinc.number;  // It will return the incident number in response body.\n\t}else{\n\t\tresponse.Result=v1;\n\t}\n    function checkMandatory() {\n        if (request.Caller_id) {\n            if (request.Short_Description) {\n                if (request.Description) {\n                    if (request.CI) {\n                        var grci = new GlideRecord('cmdb_ci');   // These are the mandatory validations while submitting the request from client.\n\t\t\t\t\t\tgrci.addQuery('name',request.CI);\n\t\t\t\t\t\tgrci.addQuery('opertaional_status','1');\n\t\t\t\t\t\tif(grci.next){\n\t\t\t\t\t\t\treturn true;\n\t\t\t\t\t\t}else{\n\t\t\t\t\t\t\treturn 'CI not found or may be it is not in opperational state';\n\t\t\t\t\t\t}\n                    } else {\n                        return 'CI can not be blank';\n                    }\n                } else {\n                    return 'Description can not be empty';\n                }\n            } else {\n                return 'Short description can not be blank';\n            }\n        } else {\n            return 'Caller Id can not be blank';\n        }\n    }\n\n})(request, response);\n"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/README.md",
    "content": "## What?\n\nPrior to the Tokyo Release, ServiceNow's serverside JavaScript utilized ECMAScript 5. \"ECMAScript\" is basically the version of JavaScript. Newer versions have come out since then (7+ years ago!) and thus, we have been missing out on some helpful functionality.\n\nClient-side scripting is run in your web browser so usually any client script you've made on ServiceNow could utilize the newest JavaScript features, but any script running on the server was limiteded to ES5.\n\nServiceNow has denoted our version of JavaScript as ECMAScript 2021, which encompasses the feature roll-up of ECMAScript 6 to ECMAScript 12. To see the full documentation of changes, you can see the release notes here: [JavaScript engine feature support](https://docs.servicenow.com/bundle/tokyo-application-development/page/script/JavaScript-engine-upgrade/reference/javascript-engine-feature-support.html). This page has all the new features that are supported, not supported, and disallowed.\n\n## How?\n\nTo utilize ECMASCript 2021 on your app, just follow these simple steps:\n\n1. Make sure your instance is upgraded to Tokyo (get a Personal Developer instance [here](https://developer.servicenow.com/))\n2. Create a new app via your preferred method (eg. Studio or App Engine Studio)\n3. Open the sys_app page for your app<br>![openrecord.png](openrecord.png)\n4. Under \"Design and Runtime\" change \"JavaSCript Mode\" to `ECMAScript 2021 (ES12)`<br>![javascriptmode.png](javascriptmode.png)\n5. Save your record. That's it!\n\n## Looking forward\n\n- The new features are available on scoped apps only for now but we've been told that Global scope support is on the roadmap.\n- If you switch existing apps to the new engine, we do recommend that you test for functionality and ensure that existing scripts are running correctly.\n\n## Features!\n\nAnd here are all the scripts that came from the show. **The scripts are all formatted as if they are running in a `Before Update Business Rule` to demonstrate that it works server side**:\n\n### const\n\nThis was actually available previously but would display an error to you when using it, despite allowing you to save. Almost everything else related to ECMAScript 6 and up would not allow you to save your record at all.\n\n`const` is a way to declare and initialize a variable that will never change its value. A great way to ensure a variable that is not meant to change never does (your script will throw an error).\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.query();\n\tvar x = inc_gr.getRowCount();\n\t\n\tconst y = 100;\n\tcurrent.work_notes = x + y;\n\n})(current, previous);\n```\n\n### let\n\nWhat's the difference between `var` and `let`?\n\n#### Scoping rules\n\nThe main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope).\n\n#### Hoisting\n\nWhile variables declared with var keyword are hoisted (initialized with undefined before the code is run) which means they are accessible in their enclosing scope even before they are declared\n\n#### Creating global object property\n\nAt the top level, let, unlike var, does not create a property on the global object\n\n#### Redeclaration\n\nIn strict mode, var will let you re-declare the same variable in the same scope while let raises a SyntaxError.\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.query();\n\tvar x = inc_gr.getRowCount();\n\t\n\tlet y = 200;\n\tcurrent.work_notes = x + y;\n\n})(current, previous);\n```\n\n### arrow functions\n\nHow many times have we googled \"how to do ____ in JavaScript?\" and the top result was a StackOverflow answer that utilized arrow functions? Arrow functions are a compact alternative to traditional function expressions. Combined with other new features, there are so many use-cases for arrow functions (like quickly reordering arrays of objects!).\n\n```javascript\n//before\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar sd = current.short_description;\n\tvar d = current.description;\n\t\n\tfunction addDescriptions(x, y){\n\t\treturn x + '\\n' + y;\n\t}\n\t\n\tcurrent.work_notes = addDescriptions(sd, d);\n\t\n})(current, previous);\n\n//after\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar sd = current.short_description;\n\tvar d = current.description;\n\t\n\tlet addDescriptions = (x, y) => x + '\\n'+ y; //one line!\n\t\n\tcurrent.work_notes = addDescriptions(sd, d);\n\t\n})(current, previous);\n```\n\n### for/of\n\nA different kind of for/in to add to your arsenal.\n\n```javascript\n//before\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(10);\n\tinc_gr.query();\n\tvar incidents = [];\n\twhile (inc_gr.next()){\n\t\tincidents.push(inc_gr.getValue('short_description'));\n\t}\n\tvar work_notes = [];\n\tfor (var inc in incidents){\n\t\twork_notes.push(incidents[inc]);\n\t}\n\t\n\tcurrent.work_notes = work_notes.join('\\n');\n\t\n})(current, previous);\n\n//after\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(10);\n\tinc_gr.query();\n\tvar incidents = [];\n\twhile (inc_gr.next()){\n\t\tincidents.push(inc_gr.getValue('short_description'));\n\t}\n\tlet work_notes = [];\n\tfor (let inc of incidents){\n\t\twork_notes.push(inc); //note that no index reference is needed\n\t}\n\t\n\tcurrent.work_notes = work_notes.join('\\n');\n\t\n})(current, previous);\n```\n\n### map\n\nThe Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.\n\nObject is similar to Map—both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. For this reason (and because there were no built-in alternatives), Object has been used as Map historically.\n\nHowever, there are important differences that make Map preferable in some cases:\n\n- Accidental Keys (objects initialize with prototype)\n- Key types (previously just strings or symbols, now can be functions, objects, any primitive)\n- Key Order (simplified to order of entry insertion)\n- Size (inherent size property)\n- Iteration (objects aren't inherently iterable)\n- Performance (additions and removals are more performative)\n- Serialization and parsing (object wins in this case)\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(3);\n\tinc_gr.query();\n\t\n\tconst incidents = new Map();\n\n\tconst keyString = 'a string';\n\tconst keyObj = {};\n\tconst keyFunc = function() {};\n\t\n\tinc_gr.next();\n\tincidents.set(keyString, inc_gr.getValue('short_description'));\n\t\n\tinc_gr.next();\n\tincidents.set(keyObj, inc_gr.getValue('short_description'));\n\t\n\tinc_gr.next();\n\tincidents.set(keyFunc, inc_gr.getValue('short_description'));\n\n\tlet work_notes = [];\n\twork_notes.push('map size: ' + incidents.size);\n\twork_notes.push(incidents.get(keyString));\n\twork_notes.push(incidents.get(keyObj));\n\twork_notes.push(incidents.get(keyFunc)); //Finding an a value by providing a function!\n\twork_notes.push(incidents.get('a string'));\n\n\tcurrent.work_notes = work_notes.join('\\n');\n\t\n})(current, previous);\n```\n\n### set\n\nThe Set object lets you store unique values of any type, whether primitive values or object references. Faster, and forces uniqueness of values.\n\n```javascript\nconst mySet1 = new Set()\n\nmySet1.add(1)           // Set [ 1 ]\nmySet1.add(5)           // Set [ 1, 5 ]\nmySet1.add(5)           // Set [ 1, 5 ]\nmySet1.add('some text') // Set [ 1, 5, 'some text' ]\nconst o = {a: 1, b: 2}\nmySet1.add(o)\n\nmySet1.add({a: 1, b: 2})   // o is referencing a different object, so this is okay\n\nmySet1.has(1)              // true\nmySet1.has(3)              // false, since 3 has not been added to the set\nmySet1.has(5)              // true\nmySet1.has(Math.sqrt(25))  // true\nmySet1.has('Some Text'.toLowerCase()) // true\nmySet1.has(o)       // true\n\nmySet1.size         // 5\n\nmySet1.delete(5)    // removes 5 from the set\nmySet1.has(5)       // false, 5 has been removed\n\nmySet1.size         // 4, since we just removed one value\n\nmySet1.add(5)       // Set [1, 'some text', {...}, {...}, 5] - a previously deleted item will be added as a new item, it will not retain its original position before deletion\n\nconsole.log(mySet1)\n// logs Set(5) [ 1, \"some text\", {…}, {…}, 5 ] in Firefox\n// logs Set(5) { 1, \"some text\", {…}, {…}, 5 } in Chrome\n```\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(3);\n\tinc_gr.query();\n\t\n\tlet incidents = new Set();\n\t\n\twhile (inc_gr.next()){\n\t\tincidents.add(inc_gr.getValue('short_description'));\n\t}\n\t\n\tcurrent.work_notes = incidents.has(inc_gr.getValue('short_description')) + '\\n' + incidents.values().next().value + '\\n' + incidents.size;\n\t\n})(current, previous);\n```\n\n### symbol\n\nSymbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding.\n\nEvery Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for(\"key\") call will always return the same Symbol for a given value of \"key\". When Symbol.for(\"key\") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned.\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar incidents = [];\n\tlet incident = {\n\t\tnumber: current.number,\n\t\tshort_description: current.short_description\n\t};\n\t\n\tincidents.push(incident);\n\tincidents.push(incident);\n\t\n\tvar incidents2 = [];\n\tincidents2.push(Symbol(incident));\n\tincidents2.push(Symbol(incident));\n\t\n\tcurrent.work_notes = (incidents[0] == incidents[1]) + '\\n' + (incidents2[0] == incidents2[1]); //Notice how the first one is true and the second is false, despite all four items being the \"same\"\n\t\n})(current, previous);\n```\n\n### default params\n\nInstead of having to check for included parameters in a function's body, we can do it directly in the parameters now.\n\n```javascript\n//before\n(function executeRule(current, previous /*null when async*/) {\n\n\tfunction add (x, y){\n\t\tif (y == null) y = 'nothing to see here';\n\t\treturn x + '\\n' + y;\n\t}\n\t\n\tcurrent.work_notes = add(current.short_description);\n\t\n})(current, previous);\n\n//after\n(function executeRule(current, previous /*null when async*/) {\n\n\tfunction add (x, y = 'nothing to see here'){\n\t\treturn x + '\\n' + y;\n\t}\n\t\n\tcurrent.work_notes = add(current.short_description);\n\t\n})(current, previous);\n```\n\n### spread\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(3);\n\tinc_gr.query();\n\tlet incidents = [];\n\twhile (inc_gr.next()){\n\t\tincidents.push(inc_gr.getValue('short_description'));\n\t}\n\n\tfunction sum(x, y, z) {\n\t\treturn x + '\\n' + y + '\\n' + z;\n\t}\n\t\n\tconst incidents_obj = { ...incidents}; //the array's items are \"spread\" out as parameters\n\t\n\tcurrent.work_notes = sum(...incidents) + '\\n' + JSON.stringify(incidents_obj);\n\n})(current, previous);\n```\n\n### template strings/literals\n\nI love this one because it makes it so much easier to copy and paste multi-line strings into my code. I use this a lot on AdventOfCode challenges!\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tlet x = `hello\n\tworld\n\tlchh loves you`;\n\t\n\tcurrent.work_notes = x; //goodbye \\n\n\n})(current, previous);\n```\n\nAnother way that these are helpful are for templates (Thanks Chris Helming for the suggestion):\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\tconst a = 5; \n\tconst b = 10; \n\tcurrent.work_notes = `Fifteen is ${a + b} and not ${2 * a + b}.`;\n\n})(current, previous);\n```\n\n### destructuring\n\nOkay, so destructuring is a LOT more than this but here is just an example.\n\n```javascript\nconst x = [1, 2, 3, 4, 5];\nconst [y, z] = x;\n// y: 1\n// z: 2\n\nconst obj = { a: 1, b: 2 };\nconst { a, b } = obj;\n// is equivalent to:\n// const a = obj.a;\n// const b = obj.b;\n```\n\n### class\n\nHoisting differences (functions and classes are both hoisted and declared but classes are not initialized)\n\n```javascript\nclass Rectangle {\n  constructor(height, width) {\n    this.name = 'Rectangle';\n    this.height = height;\n    this.width = width;\n  }\n}\n\nclass FilledRectangle extends Rectangle {\n  constructor(height, width, color) {\n    super(height, width);\n    this.name = 'Filled rectangle';\n    this.color = color;\n  }\n}\n```\n\n### And more\n\nThat's not all! Go check it out in the docs!\n\n### ES Sources\n\n- https://betterprogramming.pub/difference-between-regular-functions-and-arrow-functions-f65639aba256\n- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/arrowfunctions.js",
    "content": "// How many times have we googled \"how to do ____ in JavaScript?\" and the top result was a StackOverflow answer that utilized arrow functions? Arrow functions are a compact alternative to traditional function expressions. Combined with other new features, there are so many use-cases for arrow functions (like quickly reordering arrays of objects!).\n\n//before\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar sd = current.short_description;\n\tvar d = current.description;\n\t\n\tfunction addDescriptions(x, y){\n\t\treturn x + '\\n' + y;\n\t}\n\t\n\tcurrent.work_notes = addDescriptions(sd, d);\n\t\n})(current, previous);\n\n//after\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar sd = current.short_description;\n\tvar d = current.description;\n\t\n\tlet addDescriptions = (x, y) => x + '\\n'+ y; //one line!\n\t\n\tcurrent.work_notes = addDescriptions(sd, d);\n\t\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/class.js",
    "content": "// Hoisting differences (functions and classes are both hoisted and declared but classes are not initialized)\n\nclass Rectangle {\n  constructor(height, width) {\n    this.name = 'Rectangle';\n    this.height = height;\n    this.width = width;\n  }\n}\n\nclass FilledRectangle extends Rectangle {\n  constructor(height, width, color) {\n    super(height, width);\n    this.name = 'Filled rectangle';\n    this.color = color;\n  }\n}"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/const.js",
    "content": "// This was actually available previously but would display an error to you when using it, despite allowing you to save. Almost everything else related to ECMAScript 6 and up would not allow you to save your record at all.\n\n// `const` is a way to declare and initialize a variable that will never change its value. A great way to ensure a variable that is not meant to change never does (your script will throw an error).\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.query();\n\tvar x = inc_gr.getRowCount();\n\t\n\tconst y = 100;\n\tcurrent.work_notes = x + y;\n\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/defaultparms.js",
    "content": "// Instead of having to check for included parameters in a function's body, we can do it directly in the parameters now.\n\n//before\n(function executeRule(current, previous /*null when async*/) {\n\n\tfunction add (x, y){\n\t\tif (y == null) y = 'nothing to see here';\n\t\treturn x + '\\n' + y;\n\t}\n\t\n\tcurrent.work_notes = add(current.short_description);\n\t\n})(current, previous);\n\n//after\n(function executeRule(current, previous /*null when async*/) {\n\n\tfunction add (x, y = 'nothing to see here'){\n\t\treturn x + '\\n' + y;\n\t}\n\t\n\tcurrent.work_notes = add(current.short_description);\n\t\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/destructuring.js",
    "content": "// Okay, so destructuring is a LOT more than this but here is just an example.\n\nconst x = [1, 2, 3, 4, 5];\nconst [y, z] = x;\n// y: 1\n// z: 2\n\nconst obj = { a: 1, b: 2 };\nconst { a, b } = obj;\n// is equivalent to:\n// const a = obj.a;\n// const b = obj.b;"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/forof.js",
    "content": "// A different kind of for/in to add to your arsenal.\n\n//before\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(10);\n\tinc_gr.query();\n\tvar incidents = [];\n\twhile (inc_gr.next()){\n\t\tincidents.push(inc_gr.getValue('short_description'));\n\t}\n\tvar work_notes = [];\n\tfor (var inc in incidents){\n\t\twork_notes.push(incidents[inc]);\n\t}\n\t\n\tcurrent.work_notes = work_notes.join('\\n');\n\t\n})(current, previous);\n\n//after\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(10);\n\tinc_gr.query();\n\tvar incidents = [];\n\twhile (inc_gr.next()){\n\t\tincidents.push(inc_gr.getValue('short_description'));\n\t}\n\tlet work_notes = [];\n\tfor (let inc of incidents){\n\t\twork_notes.push(inc); //note that no index reference is needed\n\t}\n\t\n\tcurrent.work_notes = work_notes.join('\\n');\n\t\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/let.js",
    "content": "// What's the difference between `var` and `let`?\n\n// Scoping rules\n// The main difference is scoping rules. Variables declared by var keyword are scoped to the immediate function body (hence the function scope) while let variables are scoped to the immediate enclosing block denoted by { } (hence the block scope).\n\n// Hoisting\n// While variables declared with var keyword are hoisted (initialized with undefined before the code is run) which means they are accessible in their enclosing scope even before they are declared\n\n// Creating global object property\n// At the top level, let, unlike var, does not create a property on the global object\n\n// Redeclaration\n// In strict mode, var will let you re-declare the same variable in the same scope while let raises a SyntaxError.\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.query();\n\tvar x = inc_gr.getRowCount();\n\t\n\tlet y = 200;\n\tcurrent.work_notes = x + y;\n\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/map.js",
    "content": "// The Map object holds key-value pairs and remembers the original insertion order of the keys. Any value (both objects and primitive values) may be used as either a key or a value.\n\n// Object is similar to Map—both let you set keys to values, retrieve those values, delete keys, and detect whether something is stored at a key. For this reason (and because there were no built-in alternatives), Object has been used as Map historically.\n\n// However, there are important differences that make Map preferable in some cases:\n\n// - Accidental Keys (objects initialize with prototype)\n// - Key types (previously just strings or symbols, now can be functions, objects, any primitive)\n// - Key Order (simplified to order of entry insertion)\n// - Size (inherent size property)\n// - Iteration (objects aren't inherently iterable)\n// - Performance (additions and removals are more performative)\n// - Serialization and parsing (object wins in this case)\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(3);\n\tinc_gr.query();\n\t\n\tconst incidents = new Map();\n\n\tconst keyString = 'a string';\n\tconst keyObj = {};\n\tconst keyFunc = function() {};\n\t\n\tinc_gr.next();\n\tincidents.set(keyString, inc_gr.getValue('short_description'));\n\t\n\tinc_gr.next();\n\tincidents.set(keyObj, inc_gr.getValue('short_description'));\n\t\n\tinc_gr.next();\n\tincidents.set(keyFunc, inc_gr.getValue('short_description'));\n\n\tlet work_notes = [];\n\twork_notes.push('map size: ' + incidents.size);\n\twork_notes.push(incidents.get(keyString));\n\twork_notes.push(incidents.get(keyObj));\n\twork_notes.push(incidents.get(keyFunc)); //Finding an a value by providing a function!\n\twork_notes.push(incidents.get('a string'));\n\n\tcurrent.work_notes = work_notes.join('\\n');\n\t\n})(current, previous);\n"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/set.js",
    "content": "// The Set object lets you store unique values of any type, whether primitive values or object references. Faster, and forces uniqueness of values.\n\nconst mySet1 = new Set()\n\nmySet1.add(1)           // Set [ 1 ]\nmySet1.add(5)           // Set [ 1, 5 ]\nmySet1.add(5)           // Set [ 1, 5 ]\nmySet1.add('some text') // Set [ 1, 5, 'some text' ]\nconst o = {a: 1, b: 2}\nmySet1.add(o)\n\nmySet1.add({a: 1, b: 2})   // o is referencing a different object, so this is okay\n\nmySet1.has(1)              // true\nmySet1.has(3)              // false, since 3 has not been added to the set\nmySet1.has(5)              // true\nmySet1.has(Math.sqrt(25))  // true\nmySet1.has('Some Text'.toLowerCase()) // true\nmySet1.has(o)       // true\n\nmySet1.size         // 5\n\nmySet1.delete(5)    // removes 5 from the set\nmySet1.has(5)       // false, 5 has been removed\n\nmySet1.size         // 4, since we just removed one value\n\nmySet1.add(5)       // Set [1, 'some text', {...}, {...}, 5] - a previously deleted item will be added as a new item, it will not retain its original position before deletion\n\nconsole.log(mySet1)\n// logs Set(5) [ 1, \"some text\", {…}, {…}, 5 ] in Firefox\n// logs Set(5) { 1, \"some text\", {…}, {…}, 5 } in Chrome\n\n//or in servicenow\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(3);\n\tinc_gr.query();\n\t\n\tlet incidents = new Set();\n\t\n\twhile (inc_gr.next()){\n\t\tincidents.add(inc_gr.getValue('short_description'));\n\t}\n\t\n\tcurrent.work_notes = incidents.has(inc_gr.getValue('short_description')) + '\\n' + incidents.values().next().value + '\\n' + incidents.size;\n\t\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/spread.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\tvar inc_gr = new GlideRecord('incident');\n\tinc_gr.orderByDesc('number');\n\tinc_gr.setLimit(3);\n\tinc_gr.query();\n\tlet incidents = [];\n\twhile (inc_gr.next()){\n\t\tincidents.push(inc_gr.getValue('short_description'));\n\t}\n\n\tfunction sum(x, y, z) {\n\t\treturn x + '\\n' + y + '\\n' + z;\n\t}\n\t\n\tconst incidents_obj = { ...incidents}; //the array's items are \"spread\" out as parameters\n\t\n\tcurrent.work_notes = sum(...incidents) + '\\n' + JSON.stringify(incidents_obj);\n\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/symbol.js",
    "content": "// Symbol is a built-in object whose constructor returns a symbol primitive — also called a Symbol value or just a Symbol — that's guaranteed to be unique. Symbols are often used to add unique property keys to an object that won't collide with keys any other code might add to the object, and which are hidden from any mechanisms other code will typically use to access the object. That enables a form of weak encapsulation, or a weak form of information hiding.\n\n// Every Symbol() call is guaranteed to return a unique Symbol. Every Symbol.for(\"key\") call will always return the same Symbol for a given value of \"key\". When Symbol.for(\"key\") is called, if a Symbol with the given key can be found in the global Symbol registry, that Symbol is returned. Otherwise, a new Symbol is created, added to the global Symbol registry under the given key, and returned.\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar incidents = [];\n\tlet incident = {\n\t\tnumber: current.number,\n\t\tshort_description: current.short_description\n\t};\n\t\n\tincidents.push(incident);\n\tincidents.push(incident);\n\t\n\tvar incidents2 = [];\n\tincidents2.push(Symbol(incident));\n\tincidents2.push(Symbol(incident));\n\t\n\tcurrent.work_notes = (incidents[0] == incidents[1]) + '\\n' + (incidents2[0] == incidents2[1]); //Notice how the first one is true and the second is false, despite all four items being the \"same\"\n\t\n})(current, previous);"
  },
  {
    "path": "Modern Development/ECMASCript 2021/Server-side ECMAScript 2021 examples/templatestringsandliterals.js",
    "content": "// I love this one because it makes it so much easier to copy and paste multi-line strings into my code. I use this a lot on AdventOfCode challenges!\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tlet x = `hello\n\tworld\n\tlchh loves you`;\n\t\n\tcurrent.work_notes = x; //goodbye \\n\n\n})(current, previous);\n\n// Another way that these are helpful are for templates (Thanks Chris Helming for the suggestion):\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tconst a = 5; \n\tconst b = 10; \n\tcurrent.work_notes = `Fifteen is ${a + b} and not ${2 * a + b}.`;\n\n})(current, previous);\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample INC Details GraphQL Code Snippet/CMDB Resolver.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\n    var user= env.getSource();\n\tvar CI = new GlideRecord(\"cmdb_ci\");\n\tCI.addQuery(\"sys_id\",CI);\n\tCI.query();\n\treturn CI;\n\t\n\n})(env);\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample INC Details GraphQL Code Snippet/GetIncident_Details.gql",
    "content": "schema {\n    query: Query\n}\n\ntype Query {\n   EntersysID(sys_id:ID!):incident\n}\n\ntype incident{\n    sys_id:DisplayableString\n\tnumber:DisplayableString\n\tshort_description:DisplayableString\n\tcaller_id: User @source(value:\"caller_id.value\")\n\tcmdb_ci: CI @source(value:\"cmdb_ci.value\")\n}\n\ntype User{\n\tsys_id:DisplayableString\n\tuser_name:DisplayableString\n\tfirst_name:DisplayableString\n\tlast_name:DisplayableString\n\temail:DisplayableString\n}\n\ntype CI{\n\tsys_id:DisplayableString\n\tinstall_status:DisplayableString\n\tname:DisplayableString\n}\n\ntype DisplayableString{\n\tvalue:String\n\tdisplay_value:String\n}\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample INC Details GraphQL Code Snippet/Incident Resolver.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\n    var sys_id=env.getArguments().sys_id;\n\tvar INC = new GlideRecord(\"incident\");\n\tINC.addQuery(\"sys_id\",sys_id);\n\tINC.query();\n\treturn INC;\n\t\n\n})(env);\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample INC Details GraphQL Code Snippet/README.md",
    "content": "Example of incident query for GraphQL API\nThe code within this filder is just an example of working and basic implementation of sample incident details schema using GraphQL API in ServiceNow. It consist of:\n\n1. schema (schema.gql)\n2. scripted resolvers (.js extension)\n3. json file representing the resolver mappings (key is Path, value is Resolver)\nThis is just an example\nSo read it, learn it and explore GraphQL 😊\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample INC Details GraphQL Code Snippet/User Resolver.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\n    var user= env.getSource();\n\tvar User = new GlideRecord(\"sys_user\");\n\tUser.addQuery(\"sys_id\",user);\n\tUser.query();\n\treturn User;\n\t\n\n})(env);\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample INC Details GraphQL Code Snippet/resolver mapping.js",
    "content": "{\n  \"incident:cmdb_ci\": \"CMDB Resolver\",\n  \"incident:caller_id\": \"User Resolver\",\n  \"Query:EntersysID\":\"Incident Resolver\"\n}\n"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/README.md",
    "content": "## Example of groups query for GraphQL API\n\nThe code within this filder is just an example of working and basic implementation of sample group schema using `GraphQL API` in ServiceNow.\nIt consist of:\n - a schema (`schema.gql`)\n - 2 scripted resolvers (`.js` extension)\n - a json file representing the resolver mappings (key is _Path_, value is _Resolver_)\n - 2 json files representing the properties of the data brokers (table `sys_ux_data_broker_graphql`)\n - 2 sample queries (`add_user_to_group_broker_query.gql`, `get_groups_broker_query.gql`)\n\n## This is just an example\n\nSo read it, learn it and explore [GraphQL](https://graphql.org/) 😊"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/addUserToGroup.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\tconst userId = env.getArguments().userID;\n\tconst groupId = env.getArguments().groupID;\n\tlet newRec = new GlideRecord('sys_user_grmember');\n\tnewRec.initialize();\n\tnewRec.setValue('user', userId);\n\tnewRec.setValue('group', groupId);\n\tnewRec.insert();\n\n\treturn {\n\t\tuserName: userId,\n\t\tgroupName: groupId\n\t};\n})(env);"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/add_user_to_group_broker_properties.json",
    "content": "[\n    {\n      \"name\": \"userID\",\n      \"label\": \"User ID\",\n      \"description\": \"sys_id of the user\",\n      \"fieldType\": \"string\",\n      \"mandatory\": true\n    },\n    {\n      \"name\": \"groupID\",\n      \"label\": \"Group ID\",\n      \"description\": \"sys_id of the group\",\n      \"fieldType\": \"string\",\n      \"mandatory\": true\n    }\n]"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/add_user_to_group_broker_query.gql",
    "content": "mutation ($userID: ID!, $groupID: ID!) {\n    app_namespace {\n        schema_namespace {\n            addUserToGroup(userID: $userID, groupID: $groupID) {\n                userName\n                groupName\n            }\n        }\n    }\n}"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/getGroups.js",
    "content": "(function process(env) {\n\tlet ret = [];\n\tnew global.GlideQuery('sys_user_group')\n\t\t.whereNotNull('manager')\n\t\t.select('name', 'manager$DISPLAY')\n\t\t.forEach((g) => {\n\t\t\tret.push({\n\t\t\t\tid: g.sys_id,\n\t\t\t\tname: g.name,\n\t\t\t\tmanager: g['manager$DISPLAY']\n\t\t\t});\n\t\t});\n\treturn ret;\n})(env);"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/get_groups_broker_query.gql",
    "content": "query {\n    app_namespace {\n        schema_namespace {\n            getGroups {\n                id\n                name\n                manager\n            }\n         }\n    }\n}"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/resolver_mappings.json",
    "content": "{\n    \"Query:getGroups\": \"getGroups\",\n    \"Mutation:addUserToGroup\": \"addUserToGroup\"\n}"
  },
  {
    "path": "Modern Development/GraphQL/Sample group query/schema.gql",
    "content": "schema {\n    query: Query\n\tmutation: Mutation\n}\n\ntype Query {\n\tgetGroups: [Group]\n}\n\ntype Mutation {\n\taddUserToGroup(userID: ID!, groupID: ID!): GroupMember\n}\n\ntype Group {\n\tid: ID\n\tname: String\n\tmanager: String\n}\n\ntype GroupMember {\n\tuserName: String\n\tgroupName: String\n}"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/README.md",
    "content": "## Example of users query for GraphQL API\n\nThe code within this filder is just an example of working and basic implementation of sample user schema using `GraphQL API` in ServiceNow.\nIt consist of:\n - a schema (`schema.gql`)\n - 3 scripted resolvers (`.js` extension)\n - a json file representing the resolver mappings (key is _Path_, value is _Resolver_)\n - a json file representing the properties of the data broker (table `sys_ux_data_broker_graphql`)\n - a sample query (`get_user_broker_query.gql`)\n\n## This is just an example\n\nSo read it, learn it and explore [GraphQL](https://graphql.org/) 😊"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/getUserGroups.js",
    "content": "(function process(env) {\n\tconst userid = env.getArguments().id != null ? env.getArguments().id : env.getSource().sys_id;\n\tlet ret = [];\n\tlet grGroup = new GlideRecord('sys_user_grmember');\n\tgrGroup.addQuery('user', userid);\n\tgrGroup.query();\n\twhile (grGroup.next()){\n\t\tret.push({\n\t\t\tname: grGroup.getDisplayValue('group'),\n\t\t\tmanager: grGroup.getDisplayValue('group.manager')\n\t\t});\n\t}\n\treturn ret;\n})(env);"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/getUserObject.js",
    "content": "(function process(env) {\n\tconst userid = env.getArguments().id != null ? env.getArguments().id : env.getSource();\n\tlet gqUser = new global.GlideQuery('sys_user')\n\t\t\t\t\t.get(userid, ['sys_id', 'first_name', 'last_name'])\n\t\t\t\t\t.orElse({\n\t\t\t\t\t\tsys_id: '-1', \n\t\t\t\t\t\tfirst_name: 'Unknown', \n\t\t\t\t\t\tlast_name: 'User'\n\t\t\t\t\t});\n\tlet userObj = {\n\t\tsys_id: gqUser.sys_id,\n\t\tuserName: gqUser.first_name + ' ' + gqUser.last_name\n\t};\n\treturn userObj;\n})(env);"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/getUserRoles.js",
    "content": "(function process(/*ResolverEnvironment*/ env) {\n\tconst userid = env.getArguments().id != null ? env.getArguments().id : env.getSource().sys_id;\n\tlet ret = [];\n\tnew global.GlideQuery('sys_user_has_role')\n\t\t.where('user', userid)\n\t\t.select('role$DISPLAY')\n\t\t.forEach((r) => {\n\t\t\tret.push(r['role$DISPLAY']);\n\t\t});\n\treturn ret;\n})(env);"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/get_user_broker_properties.json",
    "content": "[\n    {\n        \"name\": \"id\",\n        \"label\": \"User Sys Id\",\n        \"description\": \"User's Sys ID\",\n        \"readOnly\": false,\n        \"fieldType\": \"string\",\n        \"mandatory\": true,\n        \"defaultValue\": \"\"\n    }\n]"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/get_user_broker_query.gql",
    "content": "query ($id: ID!) {\n    app_namespace {\n        schema_namespace {\n            getUser (id: $id){\n                name\n                id\n                roles { \n                    name\n                }\n                groups {\n                    name\n                    manager\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/resolver_mappings.json",
    "content": "{\n    \"User:groups\": \"getUserGroups\",\n    \"Query:getUser\": \"getUserObject\",\n    \"User:roles\": \"getUserRoles\"\n}"
  },
  {
    "path": "Modern Development/GraphQL/Sample users query/schema.gql",
    "content": "schema {\n    query: Query\n}\n\ntype Query {\n   getUser(id: ID!): User\n}\n\ntype User {\n\tid: ID @source(value: \"sys_id\")\n\tname: String @source(value: \"userName\")\n\troles: [Role]\n\tgroups: [Group]\n}\n\ntype Role {\n\tname: String\n}\n\ntype Group {\n\tname: String\n\tmanager: String\n}"
  },
  {
    "path": "Modern Development/NOW Experience/JSX Cheat Sheet/README.md",
    "content": "# JSX cheat sheet\n\n## Setting the **`class`** attribute\n\nTrying to directly use `class` attribute in JSX syntax can yield some unexpected results since you need to provide an object with properties. Each property represents a class you want to add to the element and it will be added only if the assigned value will be truthy.\n\n```jsx\n/* will be transpiled into <div class=\"0 1 2 3 4 5 6\"></div> */\nconst view = () => <div class=\"awesome\"></div>\n\n/* this will automatically add visible/enabled classes based on the provided expressions */\nconst classObject = {\n\tvisible: isVisible\n\tenabled: isEnabled\n}\nconst view = () => <div class={classObject}></div>\n\n```\n\nIf you need to specify static class or assign multiple classes you can set the `className` property of the element. In case you need multiple classes you need to separate them by space. e.g.:\n\n```jsx\n\n<div className=\"awesome\"></div>\n<div className=\"awesome list\"></div>\n```\n\nAnother example is by using a `class-` prefix to the name of class you want to add:\n\n```jsx\n/* will generate <div class=\"visible enabled\"></div> if both variables are true */\n<div class-visible={isVisible} class-enabled={isEnabled}></div>\n```\n\nTrying to use multiple styles of class definitions doesn't seem to work as expected:\n\n```jsx\n/* will generate only the awesome class, the second one will be ignored */\n<div className=\"awesome\" class-visible={isVisible}></div>\n```\n\n## Using inline styles\n\nTrying to assign inline style in JSX requires to pass an object with properties representing individual style props.\n\nNotice that instead of `font-size` I used `fontSize` .\n\n```jsx\n/* This will lead to an error when browser will try to parse it */\n<div style=\"font-size: 1.5rem; background-color: #2bb673;\"></div>\n\n<div style={{ fontSize: \"1.5rem\", backgroundColor: \"#2bb673\" }}></div>\n```\n\n## JSX components\n\nSimilar to the React, snabbdom also allows for creation of functional components that are just functions returning JSX elements. You can pass arbitrary properties and also access children.\n\nDon't forget to use capital letter for your functional component. Otherwise it would be treated as a standard DOM element name.\n\n```jsx\nconst Greetings = (props, children) => {\n  return (\n    <h2>\n      {props.message} {children}\n    </h2>\n  );\n};\n\nconst view = () => {\n  return (\n    <div>\n      <Greetings message=\"Hello\">Tomas 👋</Greetings>\n    </div>\n  );\n};\n```\n\n## Using \"inline\" SVG\n\nTrying to render SVG inline might give you some headaches since you will run into an issue when the browser will try to assign values to the read only properties of SVG elements. Instead you will need to prefix attributes with `attr-` prefix or use an `attrs={someObject}` notation.\n\n```jsx\n/* THIS DOESNT WORK */\n<svg\n  width=\"46\"\n  height=\"77\"\n  viewBox=\"0 0 46 77\"\n  fill=\"none\"\n  xmlns=\"http://www.w3.org/2000/svg\"\n>\n  <path\n    d=\"M12.5627 33.0787C12.5627 36.1551 13.4422 38.644 15.201 40.5447C16.9601 42.4467 19.4308 43.397 22.6135 43.397C25.7955 43.397 28.2664 42.4467 30.0252 40.5447C31.784 38.644 32.6635 36.1551 32.6635 33.0787C32.6635 30.003 31.784 27.5142 30.0252 25.6119C28.2664 23.7115 25.7955 22.7603 22.6135 22.7603C19.4308 22.7603 16.9601 23.7115 15.201 25.6119C13.4422 27.5142 12.5627 30.003 12.5627 33.0787ZM0 33.0787C0 29.8353 0.58653 26.8994 1.75876 24.27C2.93118 21.6422 4.5357 19.3913 6.57506 17.5171C8.61192 15.6439 11.0135 14.2036 13.7772 13.1963C16.5409 12.1899 19.4854 11.6866 22.6135 11.6866C25.7394 11.6866 28.6854 12.1899 31.4493 13.1963C34.2128 14.2036 36.6126 15.6439 38.652 17.5171C40.6889 19.3913 42.2952 21.6422 43.4674 24.27C44.6398 26.8994 45.2262 29.8353 45.2262 33.0787C45.2262 36.3228 44.6398 39.2589 43.4674 41.8865C42.2952 44.5159 40.6889 46.7668 38.652 48.6401C36.6126 50.5141 34.2128 51.9538 31.4493 52.9602C28.6854 53.9667 25.7394 54.4698 22.6135 54.4698C19.4854 54.4698 16.5409 53.9667 13.7772 52.9602C11.0135 51.9538 8.61192 50.5141 6.57506 48.6401C4.5357 46.7668 2.93118 44.5159 1.75876 41.8865C0.58653 39.2589 0 36.3228 0 33.0787Z\"\n    fill=\"#2bb673\"\n  />\n  <path\n    d=\"M45.2262 55.3193C45.2262 58.5626 44.6398 61.4987 43.4674 64.1279C42.2951 66.7557 40.6905 69.0066 38.6512 70.8808C36.6143 72.7541 34.2128 74.1944 31.4491 75.2016C28.6852 76.208 25.7411 76.7113 22.6127 76.7113C19.4869 76.7113 16.5407 76.208 13.7772 75.2016C11.0135 74.1944 8.61358 72.7541 6.57423 70.8808C4.5373 69.0066 2.93118 66.7557 1.75876 64.1279C0.58653 61.4987 0 58.5626 0 55.3193H12.5627C12.5627 58.3949 13.4422 60.8838 15.201 62.7861C16.9601 64.6866 19.4308 65.6378 22.6127 65.6378C25.7955 65.6378 28.2662 64.6866 30.0252 62.7861C31.784 60.8838 32.6635 58.3949 32.6635 55.3193H45.2262Z\"\n    fill=\"#2bb673\"\n  />\n  <path\n    d=\"M38.9128 -1.04359e-05C40.6545 -1.04359e-05 42.1417 0.618073 43.376 1.8526C44.6091 3.08876 45.2262 4.57934 45.2262 6.32385C45.2262 8.06852 44.6091 9.55877 43.376 10.7951C42.1417 12.0313 40.6545 12.6477 38.9128 12.6477C37.171 12.6477 35.6822 12.0313 34.4489 10.7951C33.2147 9.55877 32.5983 8.06852 32.5983 6.32385C32.5983 4.57934 33.2147 3.08876 34.4489 1.8526C35.6822 0.618073 37.171 -1.04359e-05 38.9128 -1.04359e-05Z\"\n    fill=\"#2bb673\"\n  />\n</svg>\n```\n\n```jsx\n/* THIS WORKS FLAWLESSLY */\n<svg\n  attrs={{\n    width: \"46\",\n    height: \"77\",\n    viewBox: \"0 0 46 77\",\n    fill: \"none\",\n    xmlns: \"http://www.w3.org/2000/svg\",\n  }}\n>\n  <path\n    attr-d=\"M12.5627 33.0787C12.5627 36.1551 13.4422 38.644 15.201 40.5447C16.9601 42.4467 19.4308 43.397 22.6135 43.397C25.7955 43.397 28.2664 42.4467 30.0252 40.5447C31.784 38.644 32.6635 36.1551 32.6635 33.0787C32.6635 30.003 31.784 27.5142 30.0252 25.6119C28.2664 23.7115 25.7955 22.7603 22.6135 22.7603C19.4308 22.7603 16.9601 23.7115 15.201 25.6119C13.4422 27.5142 12.5627 30.003 12.5627 33.0787ZM0 33.0787C0 29.8353 0.58653 26.8994 1.75876 24.27C2.93118 21.6422 4.5357 19.3913 6.57506 17.5171C8.61192 15.6439 11.0135 14.2036 13.7772 13.1963C16.5409 12.1899 19.4854 11.6866 22.6135 11.6866C25.7394 11.6866 28.6854 12.1899 31.4493 13.1963C34.2128 14.2036 36.6126 15.6439 38.652 17.5171C40.6889 19.3913 42.2952 21.6422 43.4674 24.27C44.6398 26.8994 45.2262 29.8353 45.2262 33.0787C45.2262 36.3228 44.6398 39.2589 43.4674 41.8865C42.2952 44.5159 40.6889 46.7668 38.652 48.6401C36.6126 50.5141 34.2128 51.9538 31.4493 52.9602C28.6854 53.9667 25.7394 54.4698 22.6135 54.4698C19.4854 54.4698 16.5409 53.9667 13.7772 52.9602C11.0135 51.9538 8.61192 50.5141 6.57506 48.6401C4.5357 46.7668 2.93118 44.5159 1.75876 41.8865C0.58653 39.2589 0 36.3228 0 33.0787Z\"\n    attr-fill=\"#2bb673\"\n  />\n  <path\n    attr-d=\"M45.2262 55.3193C45.2262 58.5626 44.6398 61.4987 43.4674 64.1279C42.2951 66.7557 40.6905 69.0066 38.6512 70.8808C36.6143 72.7541 34.2128 74.1944 31.4491 75.2016C28.6852 76.208 25.7411 76.7113 22.6127 76.7113C19.4869 76.7113 16.5407 76.208 13.7772 75.2016C11.0135 74.1944 8.61358 72.7541 6.57423 70.8808C4.5373 69.0066 2.93118 66.7557 1.75876 64.1279C0.58653 61.4987 0 58.5626 0 55.3193H12.5627C12.5627 58.3949 13.4422 60.8838 15.201 62.7861C16.9601 64.6866 19.4308 65.6378 22.6127 65.6378C25.7955 65.6378 28.2662 64.6866 30.0252 62.7861C31.784 60.8838 32.6635 58.3949 32.6635 55.3193H45.2262Z\"\n    attr-fill=\"#2bb673\"\n  />\n  <path\n    attr-d=\"M38.9128 -1.04359e-05C40.6545 -1.04359e-05 42.1417 0.618073 43.376 1.8526C44.6091 3.08876 45.2262 4.57934 45.2262 6.32385C45.2262 8.06852 44.6091 9.55877 43.376 10.7951C42.1417 12.0313 40.6545 12.6477 38.9128 12.6477C37.171 12.6477 35.6822 12.0313 34.4489 10.7951C33.2147 9.55877 32.5983 8.06852 32.5983 6.32385C32.5983 4.57934 33.2147 3.08876 34.4489 1.8526C35.6822 0.618073 37.171 -1.04359e-05 38.9128 -1.04359e-05Z\"\n    attr-fill=\"#2bb673\"\n  />\n</svg>\n```\n\n## Dynamic element\n\nIf you need to render the element based on some property you can do that by creating a variable and assigning the desired element name to that.\n\nThe variable name need to start with a **capital letter**\n\n```jsx\n/* will render h1-h6 based on properties.size value */\n\nconst view = ({properties}) => {\n\tconst Heading = `h${properties.size}`;\n\t<Heading>Hello world<Heading>\n}\n```\n\n## Event listeners\n\nEvent listeners are normal DOM events triggered by user interaction. To attach an event handler you need to prefix the name of an event with `on-` prefix. For an overview of all standard events browse the following link: [https://developer.mozilla.org/en-US/docs/Web/Events#Standard_events](https://developer.mozilla.org/en-US/docs/Web/Events#Standard_events)\n\n```jsx\nconst view = () => (\n  <button on-click={(event) => console.log(\"clicked\", event)}>Submit</button>\n);\n```\n\n## Conditional rendering\n\nSometimes you might need to render specific section in HTML only under specific condition, typically a loading indicator. For that case a ternary operator might come handy.\n\nTrying to use **if** **statement** inside of JSX doesn't work and the compilation will fail. You can only use **expressions** inside.\n\n```jsx\nconst view = (state) => (\n  <div>\n    /* only render now-loader when isLoading == true */\n    {state.isLoading ? <now-loader></now-loader> : null}\n  </div>\n);\n```\n\n## Repeating element n times\n\nIf we have some results stored in the array and would like to render them, we can easily utilize the map array method like this:\n\n```jsx\nconst users = [\n  { name: \"John\", id: 1 },\n  { name: \"Fred\", id: 2 },\n  { name: \"Admin\", id: 3 },\n];\n\n<ul>\n  {users.map((user) => (\n    <li key={user.id}>{user.name}</li>\n  ))}\n</ul>;\n```\n\nUsage of key property is highly recommended for optimizing DOM operations such as reordering or re-rendering.\n\n## Multiple nodes\n\nThe view method can only render a single root node. If adding an extra HTML element (such as a div or span) is not ideal or possible, we can use `Fragment` element instead. Fragment fulfills the parent element requirement but does not render any HTML element. Another approach is to return an array of elements.\n\n```jsx\n/* Leads to an error */\nconst view = () => {\n\treturn (\n\t\t<div>Content A</div>\n\t\t<div>Content B</div>\n\t);\n};\n\n/* Fragment element to the rescue */\nimport snabbdom, { Fragment } from '@servicenow/ui-renderer-snabbdom';\n\nconst view = () => {\n\treturn (\n\t\t<Fragment>\n\t\t\t<div>Content A</div>\n\t\t\t<div>Content B</div>\n\t\t</Fragment>\n\t);\n};\n\n/* returning an array of JSX elements can solve the issue as well */\nconst view = () => {\n\treturn (\n\t\t[\n\t\t\t<div>Content A</div>,\n\t\t\t<div>Content B</div>\n\t\t]\n\t);\n};\n```\n"
  },
  {
    "path": "Modern Development/Service Portal/Active Tickets Dashboard/README.md",
    "content": "**Overview**\nThe Active Tickets Dashboard Widget displays a summary of active incidents, changes, problems, and catalog tasks assigned to the logged-in user. This widget provides a quick overview of the user's current workload, enhancing efficiency in managing tasks.\n\n**Features**\n  Displays counts for:\n  1.Active Incidents\n  2.Active Changes\n  3.Active Problems\n  4.Active Catalog Tasks\n\nEach tile is clickable, leading to a filtered list of records based on the logged-in user.\n\n**Setup Instructions**\n\nCreate a New Widget\n\nNavigate to Service Portal > Widgets in the ServiceNow application navigator.\n\nClick on New to create a new widget.\n\nFill in the Name (e.g., \"Active Tickets Dashboard\") and select a suitable Widget Type (e.g., \"Custom\") and copy paste all the code into the relevant sections.\n"
  },
  {
    "path": "Modern Development/Service Portal/Active Tickets Dashboard/active_tasks_service_script.js",
    "content": "(function() {\n    var userId = gs.getUserID(); \n    data.userId = userId; \n    \n    // Using GlideAggregate for active incidents assigned to the user\n    var incidentAgg = new GlideAggregate('incident');\n    incidentAgg.addEncodedQuery('state!=Resolved^state!=Closed^assigned_to=' + userId);\n    incidentAgg.addAggregate('COUNT');\n    incidentAgg.query();\n    incidentAgg.next();\n    data.activeIncidents = incidentAgg.getAggregate('COUNT');\n\n    // Using GlideAggregate for active changes assigned to the user\n    var changeAgg = new GlideAggregate('change_request');\n    changeAgg.addEncodedQuery('state!=Closed^assigned_to=' + userId);\n    changeAgg.addAggregate('COUNT');\n    changeAgg.query();\n    changeAgg.next();\n    data.activeChanges = changeAgg.getAggregate('COUNT');\n\n    // Using GlideAggregate for active problems assigned to the user\n    var problemAgg = new GlideAggregate('problem');\n    problemAgg.addEncodedQuery('state!=Closed^assigned_to=' + userId);\n    problemAgg.addAggregate('COUNT');\n    problemAgg.query();\n    problemAgg.next();\n    data.activeProblems = problemAgg.getAggregate('COUNT');\n\n    // Using GlideAggregate for active catalog tasks assigned to the user\n    var catalogTaskAgg = new GlideAggregate('sc_task');\n    catalogTaskAgg.addEncodedQuery('state!=Closed^assigned_to=' + userId);\n    catalogTaskAgg.addAggregate('COUNT');\n    catalogTaskAgg.query();\n    catalogTaskAgg.next();\n    data.activeCatalogTasks = catalogTaskAgg.getAggregate('COUNT');\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal/Active Tickets Dashboard/active_tickets_client_script.js",
    "content": "(function() {\n    var c = this;\n\n    c.activeIncidents = 0;\n    c.activeChanges = 0;\n    c.activeProblems = 0;\n    c.activeCatalogTasks = 0;\n\n    var userId = c.userId; \n\n    c.openIncidents = function() {\n        var url = '/incident_list.do?sysparm_query=active=true^assigned_to=' + userId + '^state!=Resolved^state!=Closed'; \n        window.location.href = url; \n    };\n\n    c.openChanges = function() {\n        var url = '/change_request_list.do?sysparm_query=active=true^assigned_to=' + userId + '^state!=Closed'; \n        window.location.href = url; \n    };\n\n    c.openProblems = function() {\n        var url = '/problem_list.do?sysparm_query=active=true^assigned_to=' + userId + '^state!=Closed'; \n        window.location.href = url; \n    };\n\n    c.openCatalogTasks = function() {\n        var url = '/sc_task_list.do?sysparm_query=active=true^assigned_to=' + userId + '^state!=Closed'; \n        window.location.href = url; \n    };\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal/Active Tickets Dashboard/active_tickets_dashboard.css",
    "content": ".tile-container {\n    display: flex;\n    flex-wrap: wrap;\n    justify-content: space-around;\n}\n\n.tile {\n    background-color: #f4f4f4;\n    border: 1px solid #ccc;\n    border-radius: 5px;\n    width: 200px;\n    padding: 20px;\n    text-align: center;\n    margin: 10px;\n    cursor: pointer;\n    transition: background-color 0.3s;\n}\n\n.tile:hover {\n    background-color: #e1e1e1;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal/Active Tickets Dashboard/active_tickets_dashboard.html",
    "content": "<div class=\"tile-container\">\n    <div class=\"tile\" ng-click=\"c.openIncidents()\">\n        <h2>Active Incidents</h2>\n        <p>{{ c.activeIncidents }}</p>\n    </div>\n    <div class=\"tile\" ng-click=\"c.openChanges()\">\n        <h2>Active Changes</h2>\n        <p>{{ c.activeChanges }}</p>\n    </div>\n    <div class=\"tile\" ng-click=\"c.openProblems()\">\n        <h2>Active Problems</h2>\n        <p>{{ c.activeProblems }}</p>\n    </div>\n    <div class=\"tile\" ng-click=\"c.openCatalogTasks()\">\n        <h2>Active Catalog Tasks</h2>\n        <p>{{ c.activeCatalogTasks }}</p>\n    </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal/Search Sources/Approvals.html",
    "content": "<div>\n  <a href=\"?id=approvals&table={{item.table}}&sys_id={{item.sys_id}}\" class=\"h4 text-primary m-b-sm block\">Approval for: {{item.number}}\n<span ng-bind-html=\"highlight(item.primary, data.q)\"></span></a>\n <span class=\"text-muted\">\n  {{item.number}}\n </span>\n  <span class=\"m-l-xs m-r-xs\"> &middot; </span>\n  <span class=\"text-muted\">\n  {{item.sys_created_on}}\n    </span>\n</div>\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal/Search Sources/DataFetchScript.js",
    "content": "(function(query) {\n    var results = [];\n    var getuserdelegateof = new GlideRecord('sys_user_delegate');\n    getuserdelegateof.addQuery('delegate', gs.getUserID());\n\tgetuserdelegateof.addEncodedQuery(\"endsONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()\");\n    getuserdelegateof.query();\n    if (getuserdelegateof.next()) {\n        var grApprovals = new GlideRecord('sysapproval_approver');\n        grApprovals.addQuery('state', 'requested');\n        grApprovals.addQuery('approver', getuserdelegateof.user);\n        var sQuery = \"sysapproval.short_descriptionLIKE\" + query + \"^ORsysapproval.numberLIKE\" + query;\n        grApprovals.addQuery(sQuery);\n        grApprovals.query();\n        while (grApprovals.next()) {\n            var myapproval = {};\n            myapproval.table = 'sysapproval_approver';\n            myapproval.sys_id = grApprovals.getValue(\"sys_id\");\n            myapproval.number = grApprovals.sysapproval.number + \"\";\n            myapproval.short_description = grApprovals.sysapproval.short_description + \"\";\n            myapproval.sys_created_on = grApprovals.getValue('sys_created_on');\n            myapproval.url = \"?id=approvals&table=sysapproval_approver&sys_id=\" + grApprovals.getValue(\"sys_id\");\n            myapproval.label = grApprovals.sysapproval.number;\n            myapproval.primary = grApprovals.sysapproval.number;\n            results.push(myapproval);\n        }\n\n        return results;\n    } else {\n        var grApprovalsare = new GlideRecord('sysapproval_approver');\n        grApprovalsare.addQuery('state', 'requested');\n        grApprovalsare.addQuery('approver', gs.getUserID());\n        var sQuery = \"sysapproval.short_descriptionLIKE\" + query + \"^ORsysapproval.numberLIKE\" + query;\n        grApprovalsare.addQuery(sQuery);\n        grApprovalsare.query();\n        while (grApprovalsare.next()) {\n            var myapprovals = {};\n            myapprovals.table = 'sysapproval_approver';\n            myapprovals.sys_id = grApprovalsare.getValue(\"sys_id\");\n            myapprovals.number = grApprovalsare.sysapproval.number + \"\";\n            myapprovals.short_description = grApprovalsare.sysapproval.short_description + \"\";\n            myapprovals.sys_created_on = grApprovalsare.getValue('sys_created_on');\n            myapprovals.url = \"?id=approvals&table=sysapproval_approver&sys_id=\" + grApprovalsare.getValue(\"sys_id\");\n            myapprovals.label = grApprovalsare.sysapproval.number;\n            myapprovals.primary = grApprovalsare.sysapproval.number;\n            results.push(myapprovals);\n        }\n        return results;\n    }\n\n\n})(query);\n"
  },
  {
    "path": "Modern Development/Service Portal/Search Sources/README.md",
    "content": "The purpose is to ensure the Global Search on the portal displays approval record. For instance, a case where user has quite a few approval records and wants to search on basis of the ticket to approve instead of navigating to My To-Dos or My Approvals.\nWhen user will enter the ticket number (for this use case it is RITM) in the global search system will display a record with a tick to the left of the number.\nThis is added in the HTML file to help differentiate approval record from regular ticekt record. Inention here is when user selects the record with a 'tick' it will take to approval record where Approval action can be taken whilst the one without 'tick' will take to the actual ticket.\n\nThis is not only for approvers but even the delegated user will be able to look at the records and action directly.\n\nReplace the 'id=approvals' with relevant custom page, if used for approvals.\n\nRecord needs to be created in  sp_search_source table with the HTML & JS script as added to this folder, accordingly.\n\nOnce done, ensure to use the Search source by navigating to the Service Portals >> Portals and relevant portal needs to have the above created Search Source added in the related list.\n\nResult as below <img width=\"433\" alt=\"image\" src=\"https://github.com/Jaspalsinghchot/code-snippets/assets/30924269/7885cb4d-dd36-48c7-94ee-5a79ceb1cee9\">\n\nwhere first one will take us ticket page while the other one is approval record.\n\n"
  },
  {
    "path": "Modern Development/Service Portal/dark-mode-switcher/README.md",
    "content": "# Dark mode switcher ☀️/🌑\n\nThis is a simple demonstration of achieving a dark mode switcher for Employee Center portal. The switcher itself allows user to quickly toggle between multiple color modes:\n\n* OS Default - uses the color scheme based on OS settings\n* Dark - uses the dark color scheme ignoring OS settings\n* Light - uses the light color scheme ignoring OS setting\n\n![A quick demo of a dark mode in action](demo.gif)\n\nThis implementation doesn't need to clone any widget 🤯 and was originally build for Tokyo release 🗼 of Employee Center portal. Before trying to use it in your environment consider the following 👇:\n* it was built just for fun 🎉 and as a personal challenge\n* not all colors have been adjusted for dark mode, I even barely tested the homepage 🦄\n* it might break in any upcoming releases 🐛\n* finding the right color balance with good contrast ratio for all pages might require some additional effort and patience \n* consider how all of your images will look on both color schemes (you might even end with providing 2 color variants)\n* let's hope that the nextgen portal will have this built-in OOTB 🤞\n\n## Implementations Details\n\n### user preference\nFor storing the user's preference of color scheme I created a user preference with the name `preferred_color_scheme`. I suggest to create a global one with the value `os-default`.\n\n### Angular Providers\nThe following angular providers need to be created and attached to the `Employee Center Header` widget. You can create those via `m2m_sp_ng_pro_sp_widget` table.\n\n[themeSwitcherMenu](themeSwitcherMenu.js) - a directive that will allow user to change color scheme\n\n[avatarDropDown](avatarDropDown.js) - a \"hacky\" directive that will inject our theme switcher into the portal header next to the avatar profile menu\n\n### Portal Theme\n\n[EC Theme](portal_theme.scss) - just put this into your portal theme\n\n### CSS Include\n\n[EC Dark Mode](dark_mode.scss) - put this into your theme as a CSS Include\n\n"
  },
  {
    "path": "Modern Development/Service Portal/dark-mode-switcher/avatarDropDown.js",
    "content": "function avatarDropDown($compile){\n\treturn {\t\t\n\t\trestrict: 'C',\t\n\t\tlink: function(scope, element) {\n\t\t\tvar $themeSwitcher = angular.element('<theme-switcher-menu></theme-switcher-menu>');\n\t\t\telement.before($themeSwitcher);\n\t\t\t$compile($themeSwitcher)(scope);\n\t\t}\n\t};\n}"
  },
  {
    "path": "Modern Development/Service Portal/dark-mode-switcher/dark_mode.scss",
    "content": ""
  },
  {
    "path": "Modern Development/Service Portal/dark-mode-switcher/portal_theme.scss",
    "content": "$background-primary: var(--sp-color--background-primary);\n$background-secondary: var(--sp-color--background-secondary);\n$text-color: var(--sp-color--text-color);\n$gray-base: var(--sp-color--gray-base);\n$color-lightest: var(--sp-color--lightest);\n$text-primary: var(--sp-color--text-primary);\n\n//=New Variables\n      //==Text color\n      //$text-primary:                          #181A1F; //$text-color --now-color_text--primary\n      $text-secondary:                        #474D5A; //--now-color_text--secondary\n      $text-tertiary:                         #656E81; //$text-muted --now-color_text--tertiary\n      $text-white:                            #ffffff; //--now-color--neutral-0\n\n      //==Backgrounds\n      //$background-primary:                    #ffffff; //--now-color_background--primary\n      //$background-secondary:                  #f6f6f8; //--now-color_background--secondary\n      $background-tertiary:                   #F0F1F5; //--now-color_background--tertiary\n\n      //==borders\n      $border-primary:                        #8790A1; //--now-color_border--primary\n      $border-secondary:                      #ACB2BE; //--now-color_border--secondary\n      $border-tertiary:                       #DADDE2; //--now-color_border--tertiary\n\n      //==colors for selected\n      $select-primary: \t                    #007b58; //--now-color_selection--primary-2\n      $select-primary-lighter: \t            #D7EBE5; //--now-color_selection--primary-0\n      $select-primary-darker: \t            #003929; //--now-color_selection--primary-4\n\n      //==Primary color \n      $brand-primary-darkest:                 #1D1E46; //--now-color--primary-3\n      $brand-primary-darker:                  #333579; //--now-color--primary-2\n      $brand-primary-lighter:                 #8789D2; //--now-color--interactive-1\n      $brand-primary-lightest:                #D1D2EE; //--now-color--primary-0\n      $brand-primary-opacity:\t\t\t\t #E3E4F2; //--now-color--primary-0 (with opacity 0.5)\n\n      //==Brand Darker colors\n      $brand-danger-darker:                   #CC293C!default; //--now-color_alert--critical-3\n      $brand-warning-darker:                  #AFA400!default; //--now-color_alert--warning-3\n      $brand-moderate-darker:                 #6B52C4!default; //--now-color_alert--moderate-3 \n      $brand-low-darker:                      #6E6F78!default; //--now-color_alert--low-3 \n      $brand-success-darker:                  #3B7F00!default; //--now-color_alert--positive-3\n      $brand-info-darker:                     #297CB2!default; //--now-color_alert--info-3\n\n      //==spacing according to Ryan's design\n      $sp-space--xxs:                         2px;\n      $sp-space--xs:     \t                    4px;\n      $sp-space--sm:     \t                    8px;\n      $sp-space--md:    \t                    12px; \n      $sp-space--lg:     \t                    16px;\n      $sp-space--xl:\t\t                    24px;\n      $sp-space--xxl: \t                    32px;\n      $sp-space--3xl: \t                    40px;\n\n      //==Box shadow according to Ryan's design\n      $sp-panel-box-shadow:                   0 4px 8px 0 rgba(23,40,52,0.08);\n\n      //==Subnav background-color\n      $sp-nav-subnav:                         $sp-navbar-divider-color;\n\n      //==End new variables ----/\n\n\n      // Define NavBar\n      $sp-navbar-height: \t\t                60px;\n      $sp-navbar-divider-color:               #333579 !default; //--now-color_chrome--divider-10\n      $sp-navbar-inverse-bg:                  $navbar-inverse-bg !default;\n      $navbar-inverse-bg:                     #181826 !default; //--now-color_chrome--brand-10\n      $navbar-inverse-link-color:             #DADDE2 !default; //--now-color--neutral-2\n      $navbar-inverse-link-hover-color:       #ffffff !default; //--now-color--neutral-0\n      $sp-logo-margin-x: \t\t                6px !default;\n      $sp-logo-margin-y:\t\t                6px !default;\n      $sp-tagline-color:\t\t                $text-color !default;\n\n      // Define Body\n      $body-bg:\t\t\t\t\t\t\t\t              $background-secondary!default;\n      $sp-body-bg:\t\t\t                    $background-secondary; \n      $table-hover:                         #e6f2ee; //--now-color_surface--brand-1\n\n      $text-color:\t\t\t\t\t\t\t            $text-primary!default;\n      $text-muted:\t\t\t\t\t\t\t            $text-tertiary!default;\n\n      //## Gray and brand colors for use across Bootstrap.\n      //$gray-base:                             #000000; //--now-color--neutral-21\n      $gray-darker:                           #181A1F; //--now-color--neutral-18\n      $gray-dark:                             #282C33; //--now-color--neutral-16\n      $gray:                                  #4f5664; //--now-color--neutral-11\n      $gray-light:                            #656E81; //--now-color--neutral-9\n      $gray-lighter:                          #F6F6F8; //--now-color--neutral-1\n\n      $brand-primary:                         #4f52bd!default; //--now-color--primary-1\n      $brand-danger:                          #ff334b!default; //--now-color_alert--critical-2\n      $brand-warning:                         #F0E000!default; //--now-color_alert--warning-2\n      $brand-moderate:                        #8B6BFF!default; //--now-color_alert--moderate-2 NEW\n      $brand-low:                            \t#9698A4!default; //--now-color_alert--low-2 NEW\n      $brand-success:                         #51ae00!default; //--now-color_alert--positive-2\n      $brand-info:                            #38aaf4!default; //--now-color_alert--info-2\n\n      $primary:                               $brand-primary;\n      $warning:                               $brand-warning;\n      $success:                               $brand-success;\n      $info:                                  $brand-info;\n      $danger:                                $brand-danger;\n      $error:                                 $danger;\n\n      $color-darkest:                         #181A1F; //--now-color--neutral-18\n      $color-darker: \t                        #474D5A; //--now-color--neutral-12\n      $color-dark:                            #575F6E; //--now-color--neutral-10\n      $color-disabled:                        #9AA1AF; //--now-color--neutral-6\n      $color-light:                           #BDC2CB; //--now-color--neutral-4\n      $color-lighter:                         #F0F1F5; //--now-color--neutral-2\n      \n      $color-accent:                          #007b58; //--now-color_selection--primary-2\n      $color-accent-light:                    #51A58D; //--now-color_selection--primary-1\n      $color-accent-lightest:                 #D7EBE5; //--now-color_selection--primary-0\n\n      $border:                                $border-tertiary;\n      $panel-primary:                         $border;\n\n      $overdue:                               $brand-danger;\n      $due-today:                             $brand-warning;\n      $due-later:                             $brand-success;\n      $complete:                              $brand-low;\n      $in-progress:                           $brand-success;\n\n      $color-blue-lightest:                   #D9F4F9; //--now-color_grouped--blue-0\n      $color-blue-light:                      #A0E3EF; //--now-color_grouped--blue-1\n      $color-blue-dark:                       #3A7782; //--now-color_grouped--blue-4\n      $color-grey:                            #C6CBCB; //--now-color_grouped--gray-1\n      $color-green-dark:                      #1C4122; //--now-color_grouped--green-5\n\n      $progress-bar:                          $brand-primary;\n\n      //define service portal preview outline highlighting\n      $preview-outline:                       #1A4E70; //--now-color_alert--info-4\n      $preview-outline-text-color:            #ffffff; //--now-color--neutral-0\n\n\n\n      ////Bootstrap variable\n\n      //** Global textual link color.\n      $link-color:                            #3c59e7; //--now-color--link-2\n      $link-hover-color:                      #263994; //--now-color--link-3\n\n      //== Typography\n      $font-family-sans-serif:                \"Lato\", sans-serif;\n\n      $font-size-base:                        16px; \n      $font-size-3xl:                         ceil(($font-size-base * 2.25)); // 36px New\n      $font-size-xxl:                         ceil(($font-size-base * 1.875)); // 30px  New\n      $font-size-xl:                          ceil(($font-size-base * 1.5)); // 24px New\n      $font-size-large:                       ceil(($font-size-base * 1.25)); // 20px  \n      $font-size-md:                          $font-size-base;  // 16px New\n      $font-size-small:                       ceil(($font-size-base * 0.875)); // 14px\n      $font-size-xs:                          ceil(($font-size-base * 0.75)); // 12px New \n\n      $font-size-h1:                          ceil(($font-size-base * 2)); // 32px \n      $font-size-h2:                          ceil(($font-size-base * 1.5)); // 24px \n      $font-size-h3:                          ceil(($font-size-base * 1.25)); // 20px \n      $font-size-h4:                          ceil(($font-size-base * 1.125)); // 18px \n      $font-size-h5:                          $font-size-base; \n      $font-size-h6:                          ceil(($font-size-base * 0.875)); // 14px  \n\n      $line-height-base:                      1.4;\n\n      $headings-font-family:                  'Lato', sans-serif;\n      $headings-font-weight:                  600;\n      $headings-line-height:                  1.1;\n\n      //== Components\n      //\n      $padding-base-vertical:                 6px;\n      $padding-base-horizontal:               $sp-space--lg;\n\n      $padding-large-vertical:                $sp-space--sm;\n      $padding-large-horizontal:              $sp-space--xl;\n\n      $padding-small-vertical:                $sp-space--xs;\n      $padding-small-horizontal:              $sp-space--md;\n\n      $padding-xs-vertical:                   $sp-space--xs;\n      $padding-xs-horizontal:                 $sp-space--sm;\n\n      $border-radius-base:                    4px;\n      $border-radius-large:                   8px;\n      $border-radius-small:                   2px;\n\n      //== Global color for active items (e.g., navs or dropdowns).\n      $component-active-color:                $text-white; \n      $component-active-bg:                   $select-primary;\n\n\n      //== Tables\n      //\n      $table-cell-padding:                    $sp-space--sm;\n      $table-condensed-cell-padding:          $sp-space--xs;\n\n      $table-bg:                              transparent; \n      $table-bg-accent:                       $background-secondary; \n      $table-bg-hover:                        $background-tertiary; \n      $table-border-color:                    $border-tertiary; \n\n      //== Buttons\n      //\n      $btn-font-weight:                       600;\n\n      $btn-default-color:                     $brand-primary;\n      $btn-default-bg:                        $background-primary; \n      $btn-default-border:                    $brand-primary;\n\n      $btn-primary-color:                     $text-white; \n      $btn-primary-bg:                        $brand-primary;\n      $btn-primary-border:                    $brand-primary;\n\n      $btn-success-color:                     $text-white;\n      $btn-success-bg:                        #3B7F00; //--now-color_alert--positive-3\n      $btn-success-border:                    $btn-success-bg;\n\n      $btn-info-color:                        $text-primary; \n      $btn-info-bg:                           #8CCEF9; //--now-color_alert--info-1\n      $btn-info-border:                       $btn-info-bg;\n\n      $btn-warning-color:                     $text-primary; \n      $btn-warning-bg:                        #F6ED6C; //--now-color_alert--warning-1\n      $btn-warning-border:                    $btn-warning-bg;\n\n      $btn-danger-color:                      $text-white; \n      $btn-danger-bg:                         #CC293C; //--now-color_alert--critical-3\n      $btn-danger-border:                     $btn-danger-bg;\n\n      $btn-link-disabled-color:               $text-muted;\n\n      // Allows for customizing button radius independently from global border radius\n      $btn-border-radius-base:                $border-radius-base;\n      $btn-border-radius-large:               $border-radius-base;\n      $btn-border-radius-small:               $border-radius-base;\n\n\n      //== Forms\n      //\n      $input-bg:                              $background-primary; \n      $input-bg-disabled:                     $background-secondary; \n      $input-color:                           $text-primary; \n      $input-border:                          $border-primary; \n\n      $input-border-focus:                    #3c59e7; //--now-color--focus-2\n      $input-color-placeholder:               $text-tertiary; \n\n      $form-group-margin-bottom:              $sp-space--lg;\n      $label-margin-bottom:                   $sp-space--xs;\n\n      $legend-color:                          $text-secondary;\n      $legend-border-color:                   $border-tertiary; \n\n      //** Background color for textual input addons\n      $input-group-addon-bg:                  $background-secondary; \n      $input-group-addon-border-color:        $input-border;\n\n\n      //== Dropdowns\n      //\n      $dropdown-bg:                           $background-primary; \n      $dropdown-border:                       $border-tertiary; \n      $dropdown-fallback-border:              $border-tertiary; \n      $dropdown-divider-bg:                   $dropdown-border;\n\n      $dropdown-link-color:                   $text-primary; \n      $dropdown-link-hover-color:             $text-primary; \n      $dropdown-link-hover-bg:                $background-secondary; \n\n      $dropdown-link-active-color:            $component-active-color;\n      $dropdown-link-active-bg:               $component-active-bg;\n      $dropdown-link-disabled-color:          $text-muted;\n\n      $dropdown-header-color:                 $text-muted;\n\n\n      //== Grid system\n      //\n      $grid-gutter-width:                     32px;\n\n\n      //== Navbar\n      //\n      $navbar-height:                             $sp-navbar-height;\n      //=== Inverted navbar\n      // Reset inverted navbar basics\n      $navbar-inverse-color:                      #F0F1F5 !default; //--now-color--neutral-2\n      $navbar-inverse-bg:                         #181826 !default; //--now-color_chrome--brand-10\n      $navbar-inverse-border:                     #0a090f !default; //--now-color_chrome--divider-10\n\n      // Inverted navbar links\n      $navbar-inverse-link-color:                 #F0F1F5 !default; //--now-color--neutral-2\n      $navbar-inverse-link-hover-color:           #ffffff !default; //--now-color--neutral-0\n      $navbar-inverse-link-hover-bg:              transparent !default;\n      $navbar-inverse-link-active-color:          $navbar-inverse-link-hover-color !default;\n      $navbar-inverse-link-active-bg:             #0a090f; //--now-color_chrome--divider-10\n      $navbar-inverse-link-disabled-color:        #DADDE2; //--now-color--neutral-3\n      $navbar-inverse-link-disabled-bg:           transparent; \n\n      // Inverted navbar brand label\n      $navbar-inverse-brand-color:                $navbar-inverse-link-color;\n      $navbar-inverse-brand-hover-color:          $navbar-inverse-link-hover-color;\n      $navbar-inverse-brand-hover-bg:             transparent; \n\n\n      //== Navs\n      //\n      $nav-link-padding:                          $sp-space--sm $sp-space--lg;\n      $nav-link-hover-bg:                         $background-secondary;\n      $nav-disabled-link-color:                   $text-muted;\n      $nav-disabled-link-hover-color:             $text-muted;\n\n      //== Tabs\n      $nav-tabs-border-color:                     transparent; \n      $nav-tabs-link-hover-border-color:          transparent;\n      $nav-tabs-active-link-hover-bg:             $select-primary;\n      $nav-tabs-active-link-hover-color:          $text-white; \n      $nav-tabs-active-link-hover-border-color:   $select-primary;\n      $nav-tabs-justified-link-border-color:            transparent; \n      $nav-tabs-justified-active-link-border-color:     $select-primary;\n\n      //== Pills\n      $nav-pills-border-radius:                   $border-radius-base !default;\n      $nav-pills-active-link-hover-bg:            $select-primary; \n      $nav-pills-active-link-hover-color:         $text-white; \n\n\n      //== Pagination\n      //\n      $pagination-color:                          $link-color;\n      $pagination-bg:                             $background-primary; \n      $pagination-border:                    \t    $border-tertiary; \n\n      $pagination-hover-color:                    $link-hover-color;\n      $pagination-hover-bg:                       $background-secondary; \n      $pagination-hover-border:                   $border-secondary; \n\n      $pagination-active-color:                   $text-white; \n      $pagination-active-bg:                      $brand-primary;\n      $pagination-active-border:                  $brand-primary;\n\n      $pagination-disabled-color:                 $text-muted;\n      $pagination-disabled-bg:                    $background-primary; \n      $pagination-disabled-border:                $border-tertiary; \n\n\n      //== Pager\n      //\n      $pager-border-radius:                       $sp-space--lg;\n\n      //== Form states and alerts\n      //\n      $state-success-text:                        $text-primary;\n      $state-success-bg:                          #D2EABC; //--now-color_alert--positive-0\n      $state-success-border:                      $brand-success;\n\n      $state-info-text:                           $text-primary;\n      $state-info-bg:                             #CBE9FC; //--now-color_alert--info-0\n      $state-info-border:                         $brand-info;\n\n      $state-warning-text:                        $text-primary;\n      $state-warning-bg:                          #FBF7BC; //--now-color_alert--warning-0\n      $state-warning-border:                      $brand-warning;\n\n      $state-danger-text:                         $btn-danger-bg;\n      $state-danger-bg:                           #FFCCD2; //--now-color_alert--critical-0\n      $state-danger-border:                       $brand-danger;\n\n      //== Popovers\n      //\n      $popover-bg:                                $background-primary;\n      $popover-border-color:                      $border-tertiary;\n      $popover-fallback-border-color:             $border-tertiary;\n      $popover-title-bg:                          $background-secondary;\n\n\n      //== Labels\n      //\n      $label-default-bg:                          #C2C4CA; //--now-color_alert--low-1\n      $label-primary-bg:                          $brand-primary-lightest;\n      $label-success-bg:                          $state-success-bg;\n      $label-info-bg:                             $state-info-bg;\n      $label-warning-bg:                          $state-warning-bg;\n      $label-danger-bg:                           $state-danger-bg;\n      $label-color:                               $text-primary; \n      $label-link-hover-color:                    $text-primary; \n\n\n      //== Modals\n      //\n      $modal-inner-padding:                       $sp-space--lg;\n      $modal-title-padding:                       $sp-space--lg;\n      $modal-content-bg:                          $background-primary; \n      $modal-content-border-color:                $border-tertiary; \n      $modal-content-fallback-border-color:       $border-tertiary; \n\n      $modal-backdrop-bg:                         $gray-base; \n      $modal-backdrop-opacity:                    .5 !default;\n      $modal-header-border-color:                 $border-tertiary; \n      $modal-footer-border-color:                 $modal-header-border-color !default;\n\n      //== Alerts\n      //\n      $alert-padding:                             $sp-space--lg;\n      $alert-success-bg:            \t\t\t\t      $state-success-bg;\n      $alert-success-text:          \t\t\t\t      $state-success-text;\n      $alert-success-border:        \t\t\t\t      $state-success-border;\n\n      $alert-info-bg:               \t\t\t\t      $state-info-bg;\n      $alert-info-text:             \t\t\t\t      $state-info-text;\n      $alert-info-border:           \t\t\t\t      $state-info-border;\n\n      $alert-warning-bg:            \t\t\t\t      $state-warning-bg;\n      $alert-warning-text:          \t\t\t\t      $state-warning-text;\n      $alert-warning-border:        \t\t\t\t      $state-warning-border;\n\n      $alert-danger-bg:             \t\t\t\t      $state-danger-bg;\n      $alert-danger-text:           \t\t\t\t      $state-danger-text;\n      $alert-danger-border:         \t\t\t\t      $state-danger-border;\n\n      //== Progress bars\n      //\n      $progress-bg:                               $background-tertiary; \n      $progress-bar-color:                        $text-white; \n\n      //== List group\n      //\n      $list-group-bg:                             $background-primary; \n      $list-group-border:                         $border-tertiary; \n      $list-group-hover-bg:                       $background-secondary; \n      $list-group-active-color:                   $component-active-color;\n      $list-group-active-bg:                      $component-active-bg;\n      $list-group-active-border:                  $list-group-active-bg;\n      $list-group-active-text-color:              $list-group-active-color;\n      $list-group-disabled-color:                 $text-tertiary; \n      $list-group-disabled-bg:                    $background-secondary; \n      $list-group-disabled-text-color:            $list-group-disabled-color;\n\n      $list-group-link-color:                     $text-primary; \n      $list-group-link-hover-color:               $list-group-link-color;\n      $list-group-link-heading-color:             $text-primary; \n\n      //== Panels\n      //\n      $panel-bg:                                  $background-primary!default; \n      $panel-body-padding:                        $sp-space--lg $sp-space--xl;\n      $panel-heading-padding:                     $sp-space--xl;\n      $panel-border-radius:                       $border-radius-large;\n\n      //** Border color for elements within panels\n      $panel-inner-border:                        $border-tertiary; \n      $panel-footer-bg:                           $background-secondary; \n\n      $panel-default-text:                        $text-primary;\n      $panel-default-border:                      $border-tertiary; \n      $panel-default-heading-bg:                  $background-secondary; \n\n      $panel-primary-text:                        $text-white; \n\n      //== Thumbnails\n      //\n      $thumbnail-padding:                         $sp-space--xs;\n      $thumbnail-border:                          $border;\n      $thumbnail-caption-padding:                 $sp-space--sm;\n\n      //== Wells\n      //\n      $well-bg:                                   $body-bg;\n      $well-border:                               $border;\n\n      //== Badges\n      //\n      $badge-color:                               $text-white; \n      $badge-link-hover-color:                    $text-white; \n      $badge-bg:                                  $gray-light;\n      $badge-active-color:                        $link-color;\n      $badge-active-bg:                           $background-primary; \n\n      //== Breadcrumbs\n      //\n      $breadcrumb-padding-vertical:               $sp-space--sm;\n      $breadcrumb-padding-horizontal:             $sp-space--lg;\n      $breadcrumb-bg:                             $body-bg;\n      $breadcrumb-color:                          $text-tertiary;\n      $breadcrumb-active-color:                   $text-primary;\n\n      //== Carousel\n      //\n      $carousel-indicator-active-bg:              $brand-primary; \n\n      //== Code\n      //\n      $code-color:                                #cd293c; //--now-color_alert--critical-3\n      $code-bg:                                   $background-tertiary; \n\n      $kbd-color:                                 $text-white; \n      $kbd-bg:                                    $gray-dark;\n\n      $pre-bg:                                    $background-secondary; \n      $pre-color:                                 $gray-dark;\n      $pre-border-color:                          $border-tertiary; \n\n      //== Type\n      //\n      $abbr-border-color:                         $border-tertiary;\n      $headings-small-color:                      $text-muted;\n      $blockquote-small-color:                    $text-muted;\n      $blockquote-border-color:                   $border-tertiary;\n      $page-header-border-color:                  $border-tertiary;\n      $hr-border:                                 $border-tertiary;\n\n      $sp-b-border-color:\t                        $border;\n      $panel-default-border:                      $border;\n\n      //sc help icon color\n      $sc-field-error-color: \t\t                  $text-secondary;\n\n      //define La Jolla variable that is used for KB Star colors\n      $fav-star-color: $brand-warning;\n      $fav-star-color-off: #ffffff;\n      $fav-star-outline-color: darken($brand-warning, 30%);\n      $fav-star-outline: -1px 0 $fav-star-outline-color, 0 1px $fav-star-outline-color, 1px 0$fav-star-outline-color, 0 -1px $fav-star-outline-color;\n\n\n      //==AI Search\n      $now-sp-font-family-sans-serif: 'Lato', sans-serif;\n      $now-sp-tabs--selected--color: $select-primary;\n      $now-sp-tabs--color--hover: $select-primary;\n      $now-sp-tabs--border-color: $sp-b-border-color;\n      $now-sp-tabs--selected--background-color: $select-primary;\n\n      //==Chat\n      $sp-agent-chat-bg: $brand-primary;\n    "
  },
  {
    "path": "Modern Development/Service Portal/dark-mode-switcher/themeSwitcherMenu.js",
    "content": "function themeSwitcherMenu(){\n\treturn {\n\t\trestrict: 'E',\n\t\treplace: true,\n\t\tcontrollerAs: 'c',\n\t\tcontroller: function(userPreferences){\n\t\t\tvar c = this;\n\t\t\tc.$onInit = function(){\n\t\t\t\tuserPreferences.getPreference('preferred_color_scheme').then(function(response){\n\t\t\t\t\tc.currentTheme = response || 'os-default';\n\t\t\t\t\tdocument.documentElement.dataset[\"colorScheme\"] = c.currentTheme;\n\t\t\t\t});\n\n\t\t\t};\n\n\t\t\tc.THEME_ICON = {\n\t\t\t\t\"os-default\": \"adjust\",\n\t\t\t\tlight: \"sun-o\",\n\t\t\t\tdark: \"moon-o\"\n\t\t\t};\n\n\t\t\tc.themeChosen = function(theme){\n\t\t\t\tc.currentTheme = theme;\n\t\t\t\tuserPreferences.setPreference('preferred_color_scheme', theme);\n\t\t\t\tdocument.documentElement.dataset[\"colorScheme\"] = theme;\n\t\t\t};\n\n\t\t},\n\t\ttemplate: \"<li class=\\\"theme-switcher gt-menu-item\\\" role=\\\"menuitem\\\">\\n\" +\n\t\t\"  <ul class=\\\"nav navbar-nav\\\" role=\\\"menubar\\\">\\n\" +\n\t\t\"    <li class=\\\"dropdown\\\" role=\\\"presentation\\\">\\n\" +\n\t\t\"      <a\\n\" +\n\t\t\"        href=\\\"javascript:void(0)\\\"\\n\" +\n\t\t\"        class=\\\"toggle-dropdown\\\"\\n\" +\n\t\t\"        data-toggle=\\\"dropdown\\\"\\n\" +\n\t\t\"        aria-expanded=\\\"false\\\"\\n\" +\n\t\t\"        title=\\\"Theme switcher\\\"\\n\" +\n\t\t\"        aria-label=\\\"Theme: {{c.currentTheme}}\\\"\\n\" +\n\t\t\"        role=\\\"menuitem\\\"\\n\" +\n\t\t\"        aria-haspopup=\\\"true\\\"\\n\" +\n\t\t\"      >\\n\" +\n\t\t\"        <i\\n\" +\n\t\t\"          class=\\\"fa fa-{{c.THEME_ICON[c.currentTheme]}}\\\"\\n\" +\n\t\t\"          aria-hidden=\\\"true\\\"\\n\" +\n\t\t\"        ></i>\\n\" +\n\t\t\"      </a>\\n\" +\n\t\t\"      <ul class=\\\"dropdown-menu\\\" role=\\\"menu\\\" aria-label=\\\"Theme options\\\">\\n\" +\n\t\t\"        <li class=\\\"header-menu-item\\\">\\n\" +\n\t\t\"          <a\\n\" +\n\t\t\"            tabindex=\\\"-1\\\"\\n\" +\n\t\t\"            href=\\\"javascript:void(0)\\\"\\n\" +\n\t\t\"            ng-click=\\\"c.themeChosen('os-default')\\\"\\n\" +\n\t\t\"            ><i class=\\\"fa fa-adjust m-r-xs\\\" aria-hidden=\\\"true\\\"></i> ${OS Default}\" +\n\t\t\"          </a\\n\" +\n\t\t\"          >\\n\" +\n\t\t\"        </li>\\n\" +\n\t\t\"        <li class=\\\"header-menu-item\\\">\\n\" +\n\t\t\"          <a\\n\" +\n\t\t\"            tabindex=\\\"-1\\\"\\n\" +\n\t\t\"            href=\\\"javascript:void(0)\\\"\\n\" +\n\t\t\"            ng-click=\\\"c.themeChosen('light')\\\"\\n\" +\n\t\t\"            ><i class=\\\"fa fa-sun-o m-r-xs\\\" aria-hidden=\\\"true\\\"></i>${Light}</a\\n\" +\n\t\t\"          >\\n\" +\n\t\t\"        </li>\\n\" +\n\t\t\"        <li class=\\\"header-menu-item\\\">\\n\" +\n\t\t\"          <a\\n\" +\n\t\t\"            tabindex=\\\"-1\\\"\\n\" +\n\t\t\"            href=\\\"javascript:void(0)\\\"\\n\" +\n\t\t\"            ng-click=\\\"c.themeChosen('dark')\\\"\\n\" +\n\t\t\"            ><i class=\\\"fa fa-moon-o m-r-xs\\\" aria-hidden=\\\"true\\\"></i> ${Dark}</a\\n\" +\n\t\t\"          >\\n\" +\n\t\t\"        </li>\\n\" +\n\t\t\"      </ul>\\n\" +\n\t\t\"    </li>\\n\" +\n\t\t\"  </ul>\\n\" +\n\t\t\"</li>\\n\" \n\t};\n}"
  },
  {
    "path": "Modern Development/Service Portal/instance-badge/README.md",
    "content": "# Simple badge for portal header to visually distinguish subprod instances\n\nThis is a simple directive, that can be attached to the OOTB widget header and will render a simple badge based on the url hostname. \nThis doesn't require any cloning of the OOTB widget. The only requirement is that the portal is using some logo.\n\n![header sample](header.png)\n\n## installation\n1. create a new provider (select Service Portal->Angular Providers->New\n2. select type directive\n3. use the name `navbarBrandLogo` - this is important since it assumes usage of navbar-brand-logo class within the markup\n4. paste the following code\n```javascript\n/*directive that will inject a badge next to the portal logo based on the instance url */\nfunction navbarBrandLogo() {\n  return {\n    restrict: \"C\",\n    link: function (scope, element) {\n      //make sure we are applying this link function only within the header/footer\n      if (scope.widget && scope.widget.sys_class_name !== \"sp_header_footer\") {\n        return;\n      }\n      //TODO: adjust to your instance naming\n      var matchedHostName = /yourcompanyname(.*).service-now.com/.exec(location.hostname);\n      var instanceSuffix = matchedHostName ? matchedHostName[1] : \"\";\n      var BADGE_COLORS = { dev: \"#BADA55\", qa: \"lollipop\" };\n      if (instanceSuffix) {\n        element.addClass(\"flex-row items-center\");\n        element.append(\n          '<div class=\"badge m-l\" style=\"background-color: ' +\n            BADGE_COLORS[instanceSuffix] +\n            '\">' +\n            instanceSuffix.toUpperCase() +\n            \"</div>\"\n        );\n      }\n    },\n  };\n}\n\n```\n5. attach the created provider to the header widget\n"
  },
  {
    "path": "Modern Development/Service Portal/sn-avatar/README.md",
    "content": "# sn-avatar\n`sn-avatar` directive is one of the many built-in directives that you can freely use in your widgets for displaying a user's profile picture.\n\nThe following table lists all of the scope bindings that can be passed to the directive\n\n| Property           | Description                                          |\n|--------------------|------------------------------------------------------|\n| primary            | sysID of the user                                    |\n| showPresence       | if true will display an indicator of user's presence |\n| members            |                                                      |\n| enableContextMenu  |                                                      |\n| enableTooltip      |                                                      |\n| enableBindOnce     |                                                      |\n| displayMemberCount |                                                      |\n| groupAvatar        |                                                      |\n\n\n## Usage example\n\n### Simple avatar\nTo display a user's avatar you just need to pass a user's sys_id as a `primary` attribute within the directive. The user's picture is fetched automatically.\ne.g.\n\n```html\n<sn-avatar primary=\"user.sys_id\"></sn-avatar>\n\n```\n![simple avatar](2021-10-15-23-18-40.png)\n\n\n### Showing presence\n\n```html\n<sn-avatar primary=\"user.sys_id\" show-presence=\"true\"></sn-avatar>\n```\n![avatar with presence example](2021-10-15-23-22-31.png)\n\n### Different sizes\nTo specify a size of the displayed avatar, you can use one of the predefined classes or create your own stylings.\n```html\n<sn-avatar class=\"avatar-small\" primary=\"user.sys_id\" show-presence=\"true\"></sn-avatar>\n<sn-avatar class=\"avatar-medium\" primary=\"user.sys_id\" show-presence=\"true\"></sn-avatar>\n<sn-avatar class=\"avatar-large\" primary=\"user.sys_id\" show-presence=\"true\"></sn-avatar>\n<sn-avatar class=\"avatar-extra-large\" primary=\"user.sys_id\" show-presence=\"true\"></sn-avatar>\n```\n![different sizes](2021-10-15-23-25-15.png)\n"
  },
  {
    "path": "Modern Development/Service Portal/sn-choice-list/README.md",
    "content": "# sn-choice-list\n`sn-choice-list` is an existing and really useful directive that can help you quickly create a simple choice list.\n\nThe following table lists all of the scope bindings that can be passed to the directive\n\n| Property      | Description                                                                                                     |\n|---------------|-----------------------------------------------------------------------------------------------------------------|\n| snModel         | simple javascript variable that will store selected value. You can set default of the sn-choice-list by setting this variable |\n| snTextField         | ****required****, set which key from the object in snItems to be used as a display value                                                                         |\n| snValueField  | ****required****, set which key from the object in snItems to be used as a value                                                                       |\n| snOptions    | object with 3 configurations. <br />- *placeholder* will set a text to appear when there is no value set <br />- *allowClear* (true/false) will show or hide \"x\" button to allow user to clear the value by pressing it <br />- *hideSearch* (true/false) will show or hide search bar to allow user search for the choices             |\n| snItems  | ****required****, array of objects, that will represent our choices. It should contain a value and a label, value is hidden and is used to track which option the user chose. Label is visible. We need to specify what is the name/key of our \"value\" and \"label\" so directive knows which values to use. We can specify it by using snTextField and snValueField attribute                                           |\n| snOnChange    | It is a callback when user choses a value. We can call a function that can do additional logic after a user choses a value                                             |\n| snDisabled  | (true/false) value that can disable the choicelist (prevent users from interacting with it)                                                          |\n| snDialogName  | if we are using this directive in a dialog and we specify a name attribute of the dialog class. We can add this name as an option and when the dialog will fire a close event sn-choice-list link function will destroy this element\n\n\n## Usage example\n\n### Simple choice list\n![simple user picker](screen1.png)\n\n**HTML**\n```html\n  <sn-choice-list sn-model=\"c.choice\" sn-value-field=\"value\" sn-text-field=\"label\" sn-items=\"c.items\"></sn-choice-list>\n```\n**Client controller**\n```javascript\nc.choice = '';\n\nc.items = [{\n\tvalue: 'volvo',\n\tlabel: 'Volvo'\n\t},{\n\tvalue: 'skoda',\n\tlabel: 'Skoda'\n\t},{\n\tvalue: 'seat',\n\tlabel: 'Seat'\n\t},{\n\tvalue: 'toyota',\n\tlabel: 'Toyota'\n}];\n```\n\n### Setting options and callback\n![display additional fields](screen2.png)\n\n**HTML**\n```html\n <sn-choice-list sn-model=\"c.field\" \n                  sn-value-field=\"value\" \n                  sn-text-field=\"label\"\n                  sn-items=\"c.items\"\n                  sn-options=\"c.choiceListOptions\"\n                  sn-on-change=\"c.logValue()\"></sn-choice-list>\n```\n**Client controller**\n```javascript\nc.field = {\n    value: '',\n    displayValue: ''\n};\n```\n\n### Multiple selection\n![Multiple selection](2021-10-16-01-11-40.png)\n\n**HTML**\n```html\n<sn-record-picker table=\"'sys_user'\" display-field=\"'name'\" multiple=\"true\" field=\"c.field\"></sn-record-picker>\n\n```\n**Client controller**\n```javascript\n/* widget controller */\nvar c = this;\n\n// Configuration file to set default options\nc.choiceListOptions = {\n\tplaceholder: 'Please select your favourite car maker',\n\tallowClear: true,\n\thideSearch: false\n};\n\n// List of our available Choices\nc.items = [{\n\tvalue: 'volvo',\n\tlabel: 'Volvo'\n\t},{\n\tvalue: 'skoda',\n\tlabel: 'Skoda'\n\t},{\n\tvalue: 'seat',\n\tlabel: 'Seat'\n\t},{\n\tvalue: 'toyota',\n\tlabel: 'Toyota'\n}];\n\n// Setting Default Value\nc.field = 'skoda';\n\nc.logValue = function() {\n\tconsole.log(c.field);\n}\n```"
  },
  {
    "path": "Modern Development/Service Portal/sn-record-picker/README.md",
    "content": "# sn-record-picker\n`sn-record-picker` is probably one of the most famous directive that you can utilize in your widgets and use it in situations where you need to have a reference field.\n\nThe following table lists all of the scope bindings that can be passed to the directive\n\n| Property      | Description                                                                                                     |\n|---------------|-----------------------------------------------------------------------------------------------------------------|\n| field         | simple javascript object holding currently selected properties of displayValue, value and name                  |\n| table         | specify the table to fetch records from                                                                         |\n| defaultQuery  | encoded query that you would like to apply                                                                      |\n| startswith    | by default the searching uses `CONTAINS` operator, if specified it will use `STARTSWITH` instead                |\n| searchFields  | comma separated list of field names that will be used for searching                                             |\n| valueField    | field name that will be used for storing the value, default sys_id                                              |\n| displayField  | field name that will be visible for selected record                                                             |\n| displayFields | comma separated list of additional fields that can be seen in the dropdown                                      |\n| pageSize      | number of records to display in dropdown, additional records will be loaded after scrolling down  , default: 20 |\n| onChange      | allows to execute a function when an item from picker is selected                                               |\n| snDisabled    | makes the whole picker as readonly                                                                              |\n| multiple      | when true, allows to select multiple records                                                                    |\n| options       | an object that allows to pass additional config options like `allowClear` and `cache`                           |\n| placeholder   | text used if no item is selected                                                                                |\n\n\n> :warning: For fetching the data, the directive is using the native table API, which respects the ACLs. Make sure the user is able to see all of the required records and fields, otherwise he might experience the picker as a broken with infinite searching indicator.\n\n## Usage example\n\n### Simple user picker\n![simple user picker](2021-10-16-01-10-08.png)\n\n**HTML**\n```html\n<sn-record-picker table=\"'sys_user'\" field=\"c.field\"></sn-record-picker>\n\n```\n**Client controller**\n```javascript\nc.field = {\n    value: '',\n    displayValue: ''\n};\n```\n\n### Displaying additional fields\n![display additional fields](2021-10-16-01-08-46.png)\n\n**HTML**\n```html\n<sn-record-picker table=\"'sys_user'\" field=\"c.field\" display-fields=\"'email,location'\"></sn-record-picker>\n\n```\n**Client controller**\n```javascript\nc.field = {\n    value: '',\n    displayValue: ''\n};\n```\n\n### Multiple selection\n![Multiple selection](2021-10-16-01-11-40.png)\n\n**HTML**\n```html\n<sn-record-picker table=\"'sys_user'\" display-field=\"'name'\" multiple=\"true\" field=\"c.field\"></sn-record-picker>\n\n```\n**Client controller**\n```javascript\nc.field = {\n    value: '',\n    displayValue: ''\n};\n```\n\n### Handling change\n\n**HTML**\n```html\n<sn-record-picker on-change=\"c.userSelected()\" table=\"'sys_user'\" display-field=\"'name'\" field=\"c.field\"></sn-record-picker>\n\n```\n**Client controller**\n```javascript\nc.field = {\n    value: '',\n    displayValue: ''\n};\n\nc.userSelected = function(){\n\t\t//TODO\n\t}\n\t\n```\n\n### Caching data\nBy passing the special config option, you can cache the results, which can significantly improve any subsequent loading of data.\n\n**HTML**\n```html\n<sn-record-picker options=\"c.options\" table=\"'sys_user'\" display-field=\"'name'\" field=\"c.field\"></sn-record-picker>\n\n```\n**Client controller**\n```javascript\nc.field = {\n    value: '',\n    displayValue: ''\n};\n\nc.options = {\t\t\n\t\tcache: true\n\t};\n\t\n```"
  },
  {
    "path": "Modern Development/Service Portal/sn-time-ago/README.md",
    "content": "# sn-time-ago\n\n`sn-time-ago` is a usefull directive that allows you to present the datetime fields in a localized relative date/time formatting. It automatically chooses the right units (seconds, minutes, days, month etc.) to format a time interval.\n\n**Examples:**\n\n- just now\n- 15m ago\n- about an hour ago\n- 10h ago\n- a day ago\n- 12d ago\n- about a month ago\n- 10mo ago\n- about a year ago\n- 4y ago\n\n## Usage example\n\nYou can render this relative time by just passing a datetime display value into a timestamp scope property.\n\n**widget template**\n\n```html\n<sn-time-ago timestamp=\"data.now\"></sn-time-ago>\n```\n\n**widget server script**\n\n```javascript\n(function () {\n  data.now = new GlideDateTime().toString();\n})();\n```\n\n## Localization\n\nThe following list display UI messages that allows you to further improve/modify translations:\n\n- `%d ago`\n- `%d from now`\n- `just now`\n- `less than a minute`\n- `about a minute`,\n- `%d minutes`\n- `about an hour`\n- `about %d hours`\n- `today`\n- `a day`\n- `%d days`\n- `about a month`\n- `%d months`\n- `about a year`\n- `about a year`\n- `%d years`\n\n### Localization demo widget\n\nIn order to test all available variations of time ago, you can easily do that by importing the provided [demo widget](sp_widget_sn_timeago_demo.xml) and putting it on some demo page.\n"
  },
  {
    "path": "Modern Development/Service Portal/sn-time-ago/sp_widget_sn_timeago_demo.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<unload unload_date=\"2021-10-24 21:19:34\">\n<sp_widget action=\"INSERT_OR_UPDATE\">\n<category>custom</category>\n<client_script><![CDATA[api.controller=function(timeAgo) {\n\t/* widget controller */\n\tvar c = this;\n\n\t\n};]]></client_script>\n<controller_as>c</controller_as>\n<css/>\n<data_table>sp_instance</data_table>\n<demo_data/>\n<description>A simple demonstration widget to showcase all available variants of sn-time-ago directive</description>\n<docs display_value=\"\"/>\n<field_list>title</field_list>\n<has_preview>true</has_preview>\n<id>sn-timeago-demo</id>\n<internal>false</internal>\n<link><![CDATA[function link(scope, element, attrs, controller) {\n  \n}]]></link>\n<name>Timeago DEMO</name>\n<option_schema/>\n<public>false</public>\n<roles/>\n<script><![CDATA[(function() {\n\t\n\t\n\tdata.dates = [generateTimeAgo(0)];\n\t\t\n\tfor (var minute=1; minute<45; minute++ ){\n\t\tdata.dates.push(generateMinuteAgo(minute));\n\t}\n\t\n\tfor (var hour=1; hour<24; hour++ ){\n\t\tdata.dates.push(generateHoursAgo(hour));\n\t}\n\t\n\tfor (var day=1; day<30; day++ ){\n\t\tdata.dates.push(generateDaysAgo(day));\n\t}\n\t\n\tfor (var months=1; months<13; months++ ){\n\t\tdata.dates.push(generateMonthsAgo(months));\n\t}\n\t\n\tfor (var years=1; years<10; years++ ){\n\t\tdata.dates.push(generateYearsAgo(years));\n\t}\n\t\n\tfunction generateYearsAgo(years){\n\t\treturn generateDaysAgo(years*365);\n\t}\n\t\t\t\n\tfunction generateMonthsAgo(months){\n\t\treturn generateDaysAgo(months*30);\n\t}\n\t\n\t\n\tfunction generateDaysAgo(days){\n\t\treturn generateTimeAgo(days*3600*24);\n\t}\n\t\n\tfunction generateHoursAgo(hours){\n\t\treturn generateTimeAgo(hours*3600);\n\t}\n\t\n\tfunction generateMinuteAgo(minute){\n\t\treturn generateTimeAgo(minute*60);\n\t}\n\t\n\tfunction generateTimeAgo(seconds){\n\t\tvar nowGDT = new GlideDateTime();\n\t\tnowGDT.addSeconds(-seconds);\n\t\treturn nowGDT.toString();\t\t\n\t}\n\t\n})();]]></script>\n<servicenow>false</servicenow>\n<sys_class_name>sp_widget</sys_class_name>\n<sys_created_by>admin</sys_created_by>\n<sys_created_on>2021-10-14 20:31:55</sys_created_on>\n<sys_id>6218a9cc1b17f01088d943fddc4bcb64</sys_id>\n<sys_mod_count>232</sys_mod_count>\n<sys_name>Timeago DEMO</sys_name>\n<sys_package display_value=\"Global\" source=\"global\">global</sys_package>\n<sys_policy/>\n<sys_scope display_value=\"Global\">global</sys_scope>\n<sys_update_name>sp_widget_6218a9cc1b17f01088d943fddc4bcb64</sys_update_name>\n<sys_updated_by>admin</sys_updated_by>\n<sys_updated_on>2021-10-24 21:19:27</sys_updated_on>\n<template><![CDATA[<h2>\n  {{::options.title}}\n</h2>\n\n<div ng-repeat=\"date in c.data.dates track by $index\">\n  <sn-time-ago timestamp=\"date\"></sn-time-ago>  \n</div>\n]]></template>\n</sp_widget>\n</unload>\n"
  },
  {
    "path": "Modern Development/Service Portal/sn-watchlist/README.md",
    "content": "# sn-watchlist\n\n`sn-watchlist` is a custom directive that you can use to create a watchlist like field within your widgets.\n\n![watchlist in action](sn-watchlist.gif)\n\n| Property    | Description                                                                                                  |\n| ----------- | ------------------------------------------------------------------------------------------------------------ |\n| placeholder | specify a placeholder for the input                                                                          |\n| label       | specify a label                                                                                              |\n| ngModel     | allows to bind a model holding a list of seleted users. the user object has properties `sys_id, name, email` |\n| ngChange    | expression to execute when the model is changed element                                                      |\n\n## Installation\n\n1. create an angular provider with the name `snWatchlist`\n2. paste the content of `snWatchListDirective.js` into the client script field\n3. associate the angular provider with the widget where you would like to use it\n4. you can now use it as described within [example usage](#example-usage)\n\n> :information_source: for more info visit [docs](https://docs.servicenow.com/bundle/rome-servicenow-platform/page/build/service-portal/task/angular-providers.html)\n\n## Example usage\n\n**HTML Template**\n\n```html\n<sn-watchlist\n  label=\"Users watchlist\"\n  placeholder=\"Search from list or type in an email\"\n  ng-model=\"c.watchlist\"\n  ng-change=\"c.watchlistChanged()\"\n></sn-watchlist>\n```\n\n**Client controller**\n\n```javascript\napi.controller = function () {\n  /* widget controller */\n  var c = this;\n\n  c.$oninit = function () {\n    c.watchlist = [];\n  };\n\n  c.watchlistChanged = function () {\n    var watchList = c.watchlist\n      .map(function (user) {\n        return user.sys_id || user.email;\n      })\n      .join(\",\");\n\n    console.log(watchList);\n  };\n};\n```\n"
  },
  {
    "path": "Modern Development/Service Portal/sn-watchlist/snWatchListDirective.js",
    "content": "function snWatchlistDirective($http, $q) {\n  return {\n    restrict: \"E\",\n    require: [\"ngModel\", \"snWatchlist\"],\n    template:\n      '<div class=\"m-b watchList select2-container-multi\"><label>{{c.label}}</label>   <input name=\"watchlist\" type=\"text\" ng-model=\"c.searchWatchListUserContent\" ng-blur=\"c.searchWatchListUserContent = \\'\\'\" ng-attr-placeholder={{c.placeholder}} ng-model-options=\"{debounce: 250}\" autocomplete=\"off\" uib-typeahead=\"item as item.name for item in c.getWatchListResults($viewValue)\" typeahead-on-select=\"c.onWatchListAdd($item)\" typeahead-template-url=\"user-search.html\" typeahead-min-length=\"0\" typeahead-focus-on-select=\"false\" class=\"form-control input-typeahead m-b\">   <ul class=\"list-group select2-choices\"><li ng-repeat=\"user in c.watchList\" class=\"list-group-item attachment-list-item select2-search-choice\"><a ng-click=\"c.removeFromWatchList(user)\" class=\"select2-search-choice-close fa fa-times\" role=\"button\" href=\"#\" ng-attr-aria-label=\"{{c.getAriaLabel(user.name)}}\"></a><div class=\"attachment-details\"><span>{{ user.name }}</span></div></li></ul> </div> ',\n    scope: {\n      label: \"@\",\n      placeholder: \"@\",\n    },\n    link: function ($scope, $element, $attrs, $ctrls) {\n      var $modelCtrl = $ctrls[0];\n      var c = $ctrls[1];\n\n      $modelCtrl.$render = function () {\n        c.watchList = $modelCtrl.$modelValue || [];\n      };\n\n      c.onWatchListAdd = function ($item) {\n        $modelCtrl.$setViewValue(c.watchList.concat($item));\n        c.watchList.push($item);\n        c.searchWatchListUserContent = null;\n      };\n\n      c.removeFromWatchList = function (deletedUser) {\n        c.watchList = c.watchList.filter(function (currentUser) {\n          if (!currentUser.sys_id || !deletedUser.sys_id) {\n            return currentUser.email != deletedUser.email;\n          } else {\n            return (\n              currentUser.sys_id != deletedUser.sys_id &&\n              currentUser.email != deletedUser.email\n            );\n          }\n        });\n        $modelCtrl.$setViewValue(c.watchList);\n      };\n    },\n    bindToController: true,\n    controller: function ($http, $templateCache) {\n      var c = this;\n\n      c.$onInit = function () {\n        $templateCache.put(\n          \"user-search.html\",\n          '<a class=\"ta-item\" href=\"javascript:void(0)\"><span ng-bind-html=\"match.model.name | uibTypeaheadHighlight:query\"></span></a>'\n        );\n      };\n\n      c.getAriaLabel = function (name) {\n        return \"Remove \" + name + \" from watchlist\";\n      };\n\n      c.getWatchList = function () {\n        return c.watchList\n          .map(function (user) {\n            return user.sys_id || user.email;\n          })\n          .join(\",\");\n      };\n\n      c.existsInWatchList = function (user) {\n        var existsInList = false;\n        c.watchList.forEach(function (listEntry) {\n          if (user.name === listEntry.name) {\n            existsInList = true;\n          } else if (user.email === listEntry.email) {\n            existsInList = true;\n          }\n        });\n        return existsInList;\n      };\n\n      c.getWatchListResults = function (query) {\n        var responseArr = [];\n        var emailCheck = /\\S+@\\S+\\.\\S+/;\n        if (query && query.match(emailCheck)) {\n          if (\n            !c.existsInWatchList({\n              email: query,\n            })\n          ) {\n            responseArr.push({\n              name: query,\n              sys_id: \"\",\n              email: query,\n            });\n          }\n          return responseArr;\n        } else {\n          var url =\n            \"/api/now/table/sys_user?sysparm_limit=10&sysparm_query=active=true^ORDERBYname\";\n\n          if (query && query.length > 0) {\n            url += \"^nameLIKE\" + query;\n          }\n          return $http.get(url).then(function (response) {\n            response.data.result.forEach(function (userResponse) {\n              if (!c.existsInWatchList(userResponse)) {\n                responseArr.push({\n                  name: userResponse.name,\n                  sys_id: userResponse.sys_id,\n                  email: userResponse.email,\n                });\n              }\n            });\n            return responseArr;\n          });\n        }\n      };\n    },\n    controllerAs: \"c\",\n  };\n}\n"
  },
  {
    "path": "Modern Development/Service Portal/sp-date-picker/README.md",
    "content": "# sp-date-picker\n\n![date picker example](sp-date-picker.png)\n\n`sp-date-picker` renders a native datetime picker which respects the user's preferred datetime format.\n\nThe following table lists all of the scope bindings that can be passed to the directive\n\n| Property      | Description                                                                                                |\n| ------------- | ---------------------------------------------------------------------------------------------------------- |\n| field         | simple javascript object holding currently selected properties of value, isInvalid and isInvalidDateFormat |\n| snDisabled    | makes the whole picker as readonly                                                                         |\n| snIncludeTime | when true, allows to select a time as well                                                                 |\n| snChange      | allows to execute a function when a new date is selected                                                   |\n| snMaxDate     | allows defining maximum possible date selection after that dates are not selectable in the picker          |\n| snMinDate     | allows to set minimum possible date selection to the date picker                                           |\n\n> snMaxDate and snMinDate seems to be available after Rome relase\n\n## Usage example\n\nThe following example should render a simple date picker that allows user to only select days within the current month.\n\n**HTML**\n\n```html\n<sp-date-picker\n  field=\"c.model\"\n  sn-change=\"c.dateChanged()\"\n  sn-min-date=\"data.minDate\"\n  sn-max-date=\"data.maxDate\"\n></sp-date-picker>\n```\n\n**Client controller**\n\n```javascript\napi.controller = function () {\n  /* widget controller */\n  var c = this;\n  c.model = {};\n\n  c.dateChanged = function () {\n    //TODO\n  };\n};\n```\n\n**Server script**\n\n```javascript\n(function () {\n  data.minDate = gs.beginningOfThisMonth();\n  data.maxDate = gs.endOfThisMonth();\n})();\n```\n\nThe other alternative option would be to use datetimepicker from the [UI Bootstrap library](https://angular-ui.github.io/bootstrap/#!#datepicker) which is already included within the service portal and offers more options and flexibility.\n"
  },
  {
    "path": "Modern Development/Service Portal/sp-editable-field/README.md",
    "content": "# sp-editable-field\n`sp-editable-field` directive allows to render an interactive field label which allows user to directly modify the value by showing a miniform with just one field.\n\nThe following table lists all of the scope bindings that can be passed to the directive\n\n| Property              | Description                                                        |\n|-----------------------|--------------------------------------------------------------------|\n| fieldModel            | fieldModel object that can be obtained via $sp.getForm API         |\n| table                 | table name                                                         |\n| tableId               | record sys_id                                                      |\n| block                 | display as a block level element                                   |\n| editableByUser        | if true allows user to edit the field value                        |\n| onChange              | function to execute when onChange event occurs within the form     |\n| onSubmit              | function to execute when onSubmit event is triggered from the form |\n| asyncSubmitValidation |                                                                    |\n\n\n## Usage example\n\n\n### Editable user's email address\n![editable user's email](2021-10-17-00-17-56.png)\n\n**HTML temlate**\n```html\n<sp-editable-field table=\"sys_user\" table-id=\"data.userSysId\" editable-by-user=\"true\" field-model=\"data.sysUserModel.email\"></sp-editable-field>\n```\n\n**Server Script**\n```javascript\n(function() {  \n\tvar sysUserForm = $sp.getForm('sys_user', gs.getUserID());\n\tdata.userSysId = gs.getUserID();\n\tdata.sysUserModel = sysUserForm._fields;\t\n})();\n```"
  },
  {
    "path": "Modern Development/Service Portal/sp-modal/README.md",
    "content": "This script is used to generate a popup on the Service Portal page, you can update the html based on your need.\n\nIf a callback is needed, use the .then() part of the spModal and add any functions/statements to it as required.\n"
  },
  {
    "path": "Modern Development/Service Portal/sp-modal/script.js",
    "content": "api.controller=function(spModal) {\n/* widget controller */\nvar c = this;\n\nvar html = \"<p>Test</p>\";\n// Use spModal to pop the HTML\nspModal.open({\ntitle: 'This is test Modal: ',\nmessage: html,\nbuttons: [\n\t{label:'OK', primary: true}\n]\n}).then(function(answer)){\n\tconsole.log('this is callback');\n});\n};\n"
  },
  {
    "path": "Modern Development/Service Portal/spGlideAjax/README.md",
    "content": "# spGlideAjax\n\nThis is a demo of promise like service which allows to make a GlideAjax call directly from the controller function.\nThe service has a single method called `getAnswer` which returns a promise, which is then resolved directly to the answer attribute from the XML response.\nThe method accepts 3 parameters:\n* `processor` - name of the client callable script include\n* `name` - method name you would like to execute\n* `params` - object containing additional parameters\n\n## Installation\n\n1. create an angular provider with the name `spGlideAjax`\n2. paste the content of `spGlideAjaxService.js` into the client script field\n3. associate the angular provider with the widget where you would like to use it\n4. you can now use it as described within [example usage](#example-usage)\n\n> :information_source: for more info visit [docs](https://docs.servicenow.com/bundle/rome-servicenow-platform/page/build/service-portal/task/angular-providers.html)\n\n## Example usage\n\n```javascript\napi.controller = function (spGlideAjax) {\n  /* widget controller */\n  var c = this;\n\n  c.getGlideDateTime = function (ms) {\n    spGlideAjax\n      .getAnswer(\"DateTimeUtils\", \"msToGlideDateTime\", {\n        sysparm_value: ms,\n      })\n      .then(\n        function (resp) {\n          console.log(resp);\n        },\n        function (error) {\n          console.error(error);\n        }\n      );\n  };\n};\n```\n"
  },
  {
    "path": "Modern Development/Service Portal/spGlideAjax/spGlideAjaxService.js",
    "content": "function spGlideAjaxService($http, $q, $httpParamSerializerJQLike){\n\t\n\tfunction parseXMLAttribute(response, attr){\t\n\t\tvar xml = new DOMParser().parseFromString(response.data, 'text/xml');\n\t\treturn xml ? xml.documentElement.getAttribute(attr) : null;\t\t\n\t}\n\t\t\n\tthis.getAnswer = function(processor, name, params){\n\t\tvar deferred = $q.defer();\n\t\tvar data = angular.extend({\n\t\t\tsysparm_processor:  processor,\n\t\t\tsysparm_name: name\t\t\n\t\t}, params);\n\n\t\tvar config = {headers: {\n\t\t\t'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',\n\t\t}};\n\n\t\t$http.post('xmlhttp.do', $httpParamSerializerJQLike(data), config).then(function(response){\n\t\t\tdeferred.resolve(parseXMLAttribute(response, 'answer'));\n\t\t}, function(response){\n\t\t\tdeferred.reject(parseXMLAttribute(response, 'error'));\n\t\t});\n\t\treturn deferred.promise;\n\t};\n\n}\n"
  },
  {
    "path": "Modern Development/Service Portal/sparkling/README.md",
    "content": "# Sparkling text effect\nSimple directive that provides a visually sparkling effect on an element with class `sparkling`.\n\ne.g. `<span class=\"sparkling\">Hello world</span>`\n\n![sparkling animation](sparkling.gif)\n\n## Installation\n1. create an angular provider with the following code\n```javascript\nfunction sparkling($timeout) {\n  return {\n    restrict: \"C\",\n    link: function (scope, element) {\n      var color = \"#FFC700\";\n      var svgPath =\n        \"M26.5 25.5C19.0043 33.3697 0 34 0 34C0 34 19.1013 35.3684 26.5 43.5C33.234 50.901 34 68 34 68C34 68 36.9884 50.7065 44.5 43.5C51.6431 36.647 68 34 68 34C68 34 51.6947 32.0939 44.5 25.5C36.5605 18.2235 34 0 34 0C34 0 33.6591 17.9837 26.5 25.5Z\";\n\n      function sparkling() {\n        var sparklingElement = element;\n        var stars = sparklingElement.find(\".star\");\n\n        // remove the first star when more than 6 stars existing\n        if (stars.length > 5) {\n          stars.each(function (index) {\n            if (index === 0) {\n              $(this).remove();\n            }\n          });\n        }\n        // add a new star\n        sparklingElement.append(addStar());\n\n        var rand = Math.round(Math.random() * 700) + 300;\n        scope.timer = $timeout(sparkling, rand);\n      }\n\n      scope.$on(\"$destroy\", function () {\n        $timeout.cancel(scope.timer);\n      });\n\n      sparkling();\n\n      function addStar() {\n        var size = Math.floor(Math.random() * 20) + 10;\n        var top = Math.floor(Math.random() * 100) - 50;\n        var left = Math.floor(Math.random() * 100);\n\n        return (\n          '<span class=\"star\" style=\"top:' +\n          top +\n          \"%; left:\" +\n          left +\n          '%;\">' +\n          '<svg width=\"' +\n          size +\n          '\" height=\"' +\n          size +\n          '\" viewBox=\"0 0 68 68\" fill=\"none\">' +\n          '<path d=\"' +\n          svgPath +\n          '\" fill=\"' +\n          color +\n          '\" /></svg></span>'\n        );\n      }\n    },\n  };\n}\n\n```\n2. attach the provider to any widget that might be using that class\n3. provide the following code to your portal theme\n```css\n/* sparkling stars */\n/*******************/\n@-webkit-keyframes comeInOut {\n    0% { transform: scale(0); }\n    50% { transform: scale(1); }\n    100% { transform: scale(0); }\n}\n@-moz-keyframes comeInOut {\n    0% { transform: scale(0); }\n    50% { transform: scale(1); }\n    100% { transform: scale(0); }\n}\n@-o-keyframes comeInOut {\n    0% { transform: scale(0); }\n    50% { transform: scale(1); }\n    100% { transform: scale(0); }\n}\n@keyframes comeInOut {\n    0% { transform: scale(0); }\n    50% { transform: scale(1); }\n    100% { transform: scale(0); }\n}\n\n@-webkit-keyframes spin {\n    0% { transform: rotate(0deg); }\n    100% { transform: rotate(180deg); }\n}\n@-moz-keyframes spin {\n    0% { transform: rotate(0deg); }\n    100% { transform: rotate(180deg); }\n}\n@-o-keyframes spin {\n    0% { transform: rotate(0deg); }\n    100% { transform: rotate(180deg); }\n}\n@keyframes spin {\n    0% { transform: rotate(0deg); }\n    100% { transform: rotate(180deg); }\n}\n\n.sparkling {\n    position: relative;    \n    z-index: 0;        \n}\n\n.sparkling > span {\n    z-index: -1;\n    position: absolute;\n    display: block;\n    animation: comeInOut 700ms forwards;\n}\n\n.sparkling > span > svg {\n    display: block;\n    animation: spin 1000ms linear;\n}\n\n```\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal/userPreferences/README.md",
    "content": "# userPreferences\nBy injecting `userPreferences` factory into your controller function you can easily get and set user preferences.\n\n## Usage example\n\n### Reading user preference\n\n```javascript\napi.controller=function(userPreferences) {\n  /* widget controller */\n  var c = this;\n\t\n\tuserPreferences.getPreference('rowcount').then(function(response){\n\t\tc.rowcount = response;\n\t});\n\t\n};\n```\n\n### Setting a user preference\n\n```javascript\napi.controller=function(userPreferences) {\n  /* widget controller */\n  var c = this;\n\t\n\tuserPreferences.setPreference('rowcount', 10);\n\t\n};\n```\n"
  },
  {
    "path": "Modern Development/Service Portal/validate-data-field/README.md",
    "content": "# Validating a data field in a Service Portal\n\nUse Case: When submitting a Record Producer, a date field can’t be in the past. How to accomplish this?\n\nWe can create a Catalog Client Script.\n\n1) What is the trigger?\n\nThe script will run when the date field value changed.\n\n2) What do we need to confirm?\n\nWe need to check if the date provided is in the future.\n\n3) What if the date is not in the future?\n\nIf the date is not in the future, then we show an error message to inform that the date cannot be in the past.\n\nStep by step process\n\n1) Create a Record Producer (RP) and define the Catalog and Category so it will be visible in a Service Portal\n\n2) Create a variable in your RP. In this example, the variable will have this configuration:\n\nType: Date\nQuestion: Project Deadline\nName: project_deadline\nMandatory: Yes (checked)\n\n3) Create a Catalog Client Script in your RP to check if the project_deadline value changed. If the date is in the past, show an error message to the user.\n\nYour Catalog Client Script will look like this:\n\nName: Validate Project Deadline\nApplies to: A Catalog Item\nUI Type: Mobile / Service Portal\nType: onChange\nVariable name: project_deadline"
  },
  {
    "path": "Modern Development/Service Portal/validate-data-field/script.js",
    "content": "//Catalog Client Script\nfunction onChange(control, oldValue, newValue, isLoading) {\n\n    if (isLoading || newValue == '') {\n        g_form.hideFieldMsg('project_deadline', true);\n        return;\n    }\n\n    //If the Project Deadline is in the past, show error message\n    var deadlineDate = new Date(getDateFromFormat(newValue, g_user_date_format));\n    var nowDate = new Date();\n\n    if (nowDate.getTime() >= deadlineDate.getTime()) {\n\n        g_form.setValue('project_deadline', '');\n        g_form.showErrorBox('project_deadline', 'Project deadline should be after today', true);\n\n    } else {\n\n        g_form.hideFieldMsg('project_deadline', true);\n\n    }\n\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Accordion Widget/CSS-SCSS.scss",
    "content": ".toggler {\n  width: 100%;\n  position: relative;\n  text-align: left;\n}\n\n.toggler::before {\n  content: \"\\f107\";\n  position: absolute;\n  top: 50%;\n  right: 0.8rem;\n  transform: translateY(-50%);\n  display: block;\n  font-family: \"FontAwesome\";\n  font-size: 1.1rem;\n}\n\n.toggler[aria-expanded=\"true\"]::before {\n  content: \"\\f106\";\n}\n.border-1{\n\tborder: 1px solid #d7d7d7;\n}\n.margin-bottom-0{\n\tmargin-bottom: 0px;\n}\n.padding-5{\n\tpadding: 10px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Accordion Widget/HTML Template.html",
    "content": "<div class=\"container\">\n  <div class=\"row\">\n    <div class=\"col-lg-9 mx-auto\">\n      <div id=\"accordionExample\" class=\"accordion shadow\">\n        <div class=\"card\">\n          <div id=\"headingOne\" class=\"\">\n            <h2 class=\"margin-bottom-0\">\n              <button type=\"button\" data-toggle=\"collapse\" data-target=\"#collapseOne\" aria-expanded=\"false\"\n                      aria-controls=\"collapseOne\"\n                      class=\"btn text-dark font-weight-bold text-uppercase toggler\">Accordion Header #1</button>\n            </h2>\n          </div>\n          <div id=\"collapseOne\" aria-labelledby=\"headingOne\" data-parent=\"#accordionExample\" class=\"collapse\">\n            <div class=\"card-body border-1\">\n              <p class=\"font-weight-light padding-5\">This is the inner text of the accordion. This is the inner text of the accordion. This is the inner text of the accordion. This is the inner text of the accordion. This is the inner text of the accordion. This is the inner text of the accordion.</p>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Accordion Widget/README.md",
    "content": "# Accordion Widget\n\n![alt text](https://raw.githubusercontent.com/debendu-das/code-snippets/service-portal-widget-accordion/Service%20Portal%20Widgets/Accordion%20Widget/Accordion%20Widget%20Image.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/AngularJS Directives and Filters/README.md",
    "content": "<h2>AngularJS Directives and Filters</h2>\n___\nThis widget demonstrates useful AngularJS Directives and Filters.\n\n<h3>Usage:</h3>\n1. Create a new widget.<br>\n2. Copy the respective code as mentioned by file names.\n\n<h3>Following are covered in this:</h3>\n1. ng-click<br>\n2. ng-model<br>\n3. ng-keyup<br>\n4. ng-repeat<br>\n5. ng-if<br>\n6. ng-show<br>\n7. ng-hide<br>\n8. ng-bind<br>\n9. ng-bind-html<br>\n10. $index<br>\n11. $even<br>\n12. $odd<br>\n13. $first<br>\n14. $last<br>\n15. $middle<br>\n16. limitTo<br>\n17. uppercase<br>\n18. lowercase<br>\n19. orderBy<br>\n20. filter \n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/AngularJS Directives and Filters/client_script.js",
    "content": "api.controller=function($scope, spModal ) {\n  /* widget controller */\n  var c = this;\n\t\n\t// Variables from client script\n  $scope.myText = \"Type in textbox.\";\t\n\t$scope.htmlText = \"<p><b>AnglurJS:</b> Directives and Filters\"\n  $scope.startIndex = 0;\n  $scope.myFilter = \"\";\n\n\t//On-Click Function\n  $scope.myButton = function(){\n\t\tspModal.alert(\"Run a function on Click\");\n\t}\t\n\n\t//Function on Enter Press\n  $scope.myKey = function() {\n\t\tspModal.alert(\"Run a function on Enter Key\");\t\t\n  }\t\n\t\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/AngularJS Directives and Filters/css.css",
    "content": "h4 {\n   font-weight: bolder;\n   margin-top: 30px; \n   color: green;\n}  \n\n.mydata {\n   font-weight: bolder;\n   color: blue;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/AngularJS Directives and Filters/html.html",
    "content": "<div id=\"\" style=\"overflow:scroll; height:400px;\">\n<!-- your widget template -->\n <h4>Data</h4>\n <p class=\"mydata\">\n  <b>record:</b>{{data.record}}</p>\n <p class=\"mydata\">  \n  <b>record1:</b>{{data.record1}}</p><br>\n  \n <h4>NG-CLICK</h4>\n <p>Run Function on Click</p>  \n <button ng-click=\"myButton()\">Click me!!!</button>\n\n <h4>NG-MODEL</h4>  \n <p>2-Way data variable</p>\n <b>myText:</b> {{myText}}<br>   \n <input type=\"text\" ng-model=\"myText\"><br>\n\n <h4>NG-KEYUP</h4>  \n <p>Use Enter to Run Function</p>\n <input type=\"text\" placeholder=\"Press Enter key here\" ng-keyup=\"$event.keyCode == 13 ? myKey() : null\"/><br><br>\n\n <h4>NG-REPEAT</h4>  \n <p>Looping through data</p>\n <p ng-repeat=\"x in data.record\">{{x}} - Index {{$index}}</p><br>\n\n <h4>NG-IF</h4>    \n <p>Show only where condition in passed<br>\n    Other Variables are not available</p>  \n <p ng-repeat=\"x in data.record\" ng-if=\"x == 3\">{{x}}</p>  \n\n  \n <h4>NG-SHOW</h4>    \n <p>Show only where condition in passed<br>\n    Other Variables are available but hidden</p>\n <p ng-repeat=\"x in data.record\" ng-show=\"x == 3\">{{x}}</p> \n  \n\n <h4>NG-HIDE</h4>    \n <p>Hide where condition in passed<br>\n    Variable is available but hidden</p>\n <p ng-repeat=\"x in data.record\" ng-hide=\"x == 3\">{{x}}</p>    \n\n  \n <h4>NG-BIND</h4>    \n TEXT-BINDING\n <p ng-bind=\"mytext\"></p><br>\n\n <h4>NG-BIND-HTML</h4>    \n <p>BIND with HTML Formatting</p>\n <p ng-bind-html=\"htmlText\"></p> \n  \n  \n <h4>$INDEX</h4>\n <p ng-repeat=\"x in data.record\">{{x}} - Index {{$index}}</p>\n\n <h4>$INDEX IN IF CONDITION</h4>\n <p ng-repeat=\"x in data.record\" ng-if=\"$index == 3\">{{x}} - Index {{$index}}</p>\n\n <h4>$EVEN</h4>\n <p ng-repeat=\"x in data.record\" ng-if=\"$even\">{{x}} - Index {{$index}}</p>    \n\n <h4>$ODD</h4>\n <p ng-repeat=\"x in data.record\" ng-if=\"$odd\">{{x}} - Index {{$index}}</p>  \n\n <h4>$FIRST</h4>\n <p ng-repeat=\"x in data.record\" ng-if=\"$first\">{{x}} - Index {{$index}}</p> \n\n <h4>$LAST</h4>\n <p ng-repeat=\"x in data.record\" ng-if=\"$last\">{{x}} - Index {{$index}}</p>   \n\n <h4>$MIDDLE</h4>\n Leave 1st and Last\n <p ng-repeat=\"x in data.record\" ng-if=\"$middle\">{{x}} - Index {{$index}}</p>   \n\n <h4>LIMIT TO</h4>\n <p ng-repeat=\"x in data.record | limitTo:3\">{{x}} - Index {{$index}}</p>  \n\n <h4>LIMIT TO BEGIN</h4>\n Start Index:<input type=\"number\" ng-model=\"startIndex\"> <br>   \n <p ng-repeat=\"x in data.record | limitTo:3:startIndex\">{{x}} - Index {{$index}}</p>  \n\n <h4>UPPERCASE</h4>    \n {{data.record1}}<br>  \n <p ng-repeat=\"x in data.record1\">{{x | uppercase}} - Index {{$index}}</p>    \n\n <h4>lowercase</h4>\n <p ng-repeat=\"x in data.record1\">{{x | lowercase}} - Index {{$index}}</p>    \n\n <h4>ORDER BY</h4>\n <p ng-repeat=\"x in data.record1 | orderBy:x\">{{x}} - Index {{$index}}</p>    \n\n <h4>ORDER BY DESCENDING</h4>\n <p ng-repeat=\"x in data.record1 | orderBy:x:true\">{{x}} - Index {{$index}}</p>  \n\n  \n <h4>FILTER</h4>\n Filter:<input type=\"text\" placeholder=\"Enter filter here\" ng-model=\"myFilter\">\n <p ng-repeat=\"x in data.record1 | filter:myFilter\">{{x}} - Index {{$index}}</p>   \n  \n  \n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/AngularJS Directives and Filters/server_script.js",
    "content": "(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n\n\t//Variables from Server Script\n\tdata.record = [1,2,3,4,5,6];\t\n\tdata.record1 = [\"abc\",\"xyz\",\"Abc\",\"Xyz\",\"ABC\",\"XYZ\"];\n\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Animated Notification Badge/README.md",
    "content": "# 🔔 Animated Notification Badge\n\nThis snippet demonstrates how to create an animated notification badge using native ServiceNow client-side capabilities, without relying on direct DOM manipulation or inline styles.\nIt uses AngularJS and CSS to apply a pulsating animation to the badge, ideal for Portal widgets that require attention-grabbing indicators.\n\n![Demo of animated badge](./animated-badge.gif)\n\n## 📦 Files\n\n- `notification-badge.html` – Badge markup with conditional visibility\n- `notification-badge.css` – Keyframe animation and badge styling\n- `notification-badge.js` – Logic to trigger or reset badge visibility\n\n## 🚀 How to Use\n\n1. Copy the HTML, CSS, and client script into your custom Portal widget.\n2. Bind the badge visibility to a condition (e.g., number of unread messages).\n3. Use the `animate__pulse` class to trigger attention-grabbing animations.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Animated Notification Badge/notification-badge.css",
    "content": ".notification-wrapper {\n  position: relative;\n  display: inline-block;\n}\n\n.notification-icon {\n  font-size: 24px;\n  color: #333;\n}\n\n.notification-badge {\n  position: absolute;\n  top: -6px;\n  right: -6px;\n  background-color: #e74c3c;\n  color: white;\n  font-size: 12px;\n  padding: 2px 6px;\n  border-radius: 50%;\n  font-weight: bold;\n  animation-duration: 0.6s;\n  animation-fill-mode: both;\n}\n\n.notification-pulse {\n  animation-name: pulseScale;\n  animation-iteration-count: infinite;\n  animation-timing-function: ease-in-out;\n}\n\n@keyframes pulseScale {\n  0% { transform: scale(1); }\n  50% { transform: scale(1.2); }\n  100% { transform: scale(1); }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Animated Notification Badge/notification-badge.html",
    "content": "<div class=\"notification-wrapper\">\n  <i class=\"fa fa-bell notification-icon\" aria-hidden=\"true\"></i>\n  <span \n    class=\"notification-badge\" \n    ng-class=\"{'notification-pulse': c.hasNewNotification}\" \n    ng-if=\"c.hasNewNotification\">\n    {{ c.badgeCount }}\n  </span>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Animated Notification Badge/notification-badge.js",
    "content": "api.controller=function($scope) {\n  var c = this;\n\n  c.badgeCount = 3;\n  c.hasNewNotification = c.badgeCount > 0;\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/ApplyCSSDynamically/README.md",
    "content": "The widget demonstrates how CSS can be dynamically applied on the HTML attributes via client controller. Inorder to demonstrate, a color picker dropdown has been placed in the HTML. Upon selecting a color option, the corresponding CSS is applied on an HTML text to color it with the same.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/ApplyCSSDynamically/applycssdynamically.html",
    "content": "<!--Contains a select dropdown with color. Upon selecting the color, the CSS is applied on the color text dynamically -->\n<div>\n   <label  for=\"color_picker\">${Choose Color:}</label>\n   <select id=\"color_picker\" class=\"form-control\" \n      name=\"color_picker\" ng-model=\"data.color\"\n      ng-change=\"setColor()\"\n      ng-options=\"item.id as item.name for item in data.options\"/>\n   <div class=\"color\">\n      <br>\n      <b>You have selected {{c.data.color}}</b>\n   </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/ApplyCSSDynamically/applycssdynamicallyclient.js",
    "content": "api.controller = function($element, $scope, $timeout) {\n    /* widget controller */\n    var c = this;\n    $scope.setColor = function() {\n\t//Apply CSS depending on color selected in select dropdoen\n\t//Logic can be modified dynamically depending on your conditions\n        $timeout(function() {\n\t//Function to apply CSS\n            $element.find('.color').css(\"color\", c.data.color);\n            $element.find('.color').css(\"font-size\", \"20px\");\n        }, 100);\n    };\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/ApplyCSSDynamically/applycssdynamicallyserver.js",
    "content": "(function() {\n    //Default values for select dropdown\n    data.options = [{\n        name: \"-- None --\",\n        id: \"\"\n    }, {\n        name: \"Green\",\n        id: \"green\"\n    }, {\n        name: \"Orange\",\n        id: \"orange\"\n    }, {\n        name: \"Red\",\n        id: \"red\"\n    }];\n\n    data.color = \"\";\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Batman Animation/README.md",
    "content": "In order to use the widget, follow the below steps:\n\n1.Create a new widget and copy the html, style and client script in the widget.\n\n2.Upload the 2 images (Batman logo and Batman Background Image) in the image table.\n\n3.Update the widget html with the correct image names.\n\n4.Create a page and add the widget to it(12 GRID LAYOUT) and also add the below styling to page CSS.\n\n****section {\n  background-image: url('Batman.jpg');\n  background-size: cover;\n  background-repeat: unset;\n}**\n**\n\nHere is Page Content structure\n\n\n![image](https://user-images.githubusercontent.com/28950517/135977861-16358193-2df6-460a-ba6c-094c5ba3955d.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Batman Animation/client_script.js",
    "content": "api.controller=function($scope) {\n\t/* widget controller */\n\tvar c = this;\n\tsetTimeout(function(){ $scope.togglelightray = true; }, 2000);\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Batman Animation/html_template.html",
    "content": "<div ng-show=\"togglelightray\">\n<div class=\"container\">\n  <div style=\"width: 205px;\n              transform: rotate(77deg);\n              height: 673px;\n              position: relative;\n              left: -166px;    \n              top: 100px;\n              border-top: 0px solid transparent;\n              border-bottom: 765px solid transparent;\n              border-left: 165px solid rgb(54 71 78);\n              opacity: 0.45;   \n              \"></div>\n  <div class=\"innercontainer\">\n    <div class=\"outer-circle\">\n      <div class=\"bat\">\n        <img src=\"batlogo.png\" class='batlogo'/>  \n      </div>\n    </div>\n  </div>\n</div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Batman Animation/style.css",
    "content": ".container {\n  width:100%;\n  height: 100%;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  left: 220px;\n  top: 120px;\n  position: absolute;\n}\n\n.innercontainer {\n  height:150px;\n  width:250px;\n  position:relative;\n  overflow:hidden;\n  transform: rotate(67deg);\n}\n\n.outer-circle {\n  position:absolute;\n  height:130px;\n  width:230px;\n  top:3px;\n  left:4px;\n  overflow:hidden;\n}\n\n.bat {\n  background: #36474e;\n  position: absolute;\n  height: 111px;\n  width: 189px;\n  border-radius: 50%;\n  box-shadow: 0px -3px 11px #fff;\n  top: 17px;\n  left: 17px;\n}\n.batlogo{\n  z-index: 9000;\n  height: 90px;\n  width: 181px;\n  box-shadow: 0 0 -3px greenyellow;\n  position: fixed;\n  top: 48px;\n  opacity: 0.75;\n  left: 23px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Calendar widget/README.md",
    "content": "# Calendar Widget for ServiceNow Portal\nThis widget creates a simple, interactive calendar for the ServiceNow portal, allowing users to navigate through months and view the current day highlighted. It displays the days of the month in a grid layout.\n\n## Features\nMonthly Navigation: Buttons for navigating between months.\nCurrent Day Highlighting: The current date is highlighted in light blue.\nResponsive Layout: The calendar adjusts to fit within the widget container.\n\n## Usage\n\nHTML Structure: The main HTML structure displays month navigation buttons and the days in a grid layout.\n\nCSS Styling: CSS styles define the appearance of the calendar, including a shadowed border, day alignment.\n\nJavaScript Controller:\n\nDefines the calendar's navigation functionality.\nCalculates and displays the days of the selected month.\nHighlights today’s date if it is within the selected month.\n\n## Code Structure\n### Controller (api.controller):\nInitializes the month and year to display the current month.\nProvides functions to navigate to the previous and next month.\nCalculates the days of the selected month and highlights the current date.\n### CSS:\n.calendar-widget: The main container for the calendar.\n.calendar-header: Contains the month name and navigation buttons.\n\n## Customization\n### Highlight Colors:\nModify .current-day in the CSS to change the color for today’s date.\nMonth Names and Day Names:\nUpdate the $scope.monthNames and $scope.dayNames arrays to localize or customize the labels.\n\n### Example Usage\nIn the ServiceNow portal, add this widget to display an interactive calendar. This can be embedded in any dashboard or custom page where date visualization is needed.\n\n## Known Issues\nInitial Load: If dates do not display immediately, ensure the ng-init=\"loadCalendar()\" directive is included in the main container.\nDate Accuracy: The calendar currently starts with today's date. If dates appear incorrect, check the $scope.loadCalendar() function for accurate month and day calculations."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Calendar widget/index.html",
    "content": "<div class=\"calendar-widget\" ng-init=\"loadCalendar()\">\n    <div class=\"calendar-header\">\n        <button ng-click=\"prevMonth()\">&#9664;</button>\n        <span>{{ monthNames[currentMonth] }} {{ currentYear }}</span>\n        <button ng-click=\"nextMonth()\">&#9654;</button>\n    </div>\n\n    <div class=\"calendar-days\">\n        <div class=\"day-name\" ng-repeat=\"day in dayNames\">{{ day }}</div>\n        <div \n            class=\"day\" \n            ng-repeat=\"day in days track by $index\" \n            ng-class=\"{'current-day': isCurrentDay(day), 'weekend': isWeekend($index), 'regular-day': day}\">\n            {{ day }}\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Calendar widget/script.js",
    "content": "api.controller = function($scope) {\n    // Month and day names\n    $scope.monthNames = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];\n    $scope.dayNames = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];\n\n    // Initialize the current month, year, and days\n    var today = new Date();\n    $scope.currentMonth = today.getMonth();\n    $scope.currentYear = today.getFullYear();\n    $scope.days = [];\n\n    // Load the calendar days\n    $scope.loadCalendar = function() {\n        $scope.days = [];\n        \n        // Calculate the first and last day of the month\n        var firstDay = new Date($scope.currentYear, $scope.currentMonth, 1).getDay();\n        var lastDate = new Date($scope.currentYear, $scope.currentMonth + 1, 0).getDate();\n        \n        // Add empty days for alignment (for days before the 1st of the month)\n        for (var i = 0; i < firstDay; i++) {\n            $scope.days.push('');\n        }\n        \n        // Add days of the month\n        for (var day = 1; day <= lastDate; day++) {\n            $scope.days.push(day);\n        }\n    };\n\n    // Check if the day is today\n    $scope.isCurrentDay = function(day) {\n        return day == today.getDate() && $scope.currentMonth == today.getMonth() && $scope.currentYear == today.getFullYear();\n    };\n\n    // Navigate to the previous month\n    $scope.prevMonth = function() {\n        if ($scope.currentMonth === 0) {\n            $scope.currentMonth = 11;\n            $scope.currentYear--;\n        } else {\n            $scope.currentMonth--;\n        }\n        $scope.loadCalendar();\n    };\n\n    // Navigate to the next month\n    $scope.nextMonth = function() {\n        if ($scope.currentMonth === 11) {\n            $scope.currentMonth = 0;\n            $scope.currentYear++;\n        } else {\n            $scope.currentMonth++;\n        }\n        $scope.loadCalendar();\n    };\n\n    // Initialize the calendar for the current month\n    $scope.loadCalendar();\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Calendar widget/style.css",
    "content": ".calendar-widget {\n    width: 100%;\n    max-width: 300px;\n    margin: auto;\n    font-family: Arial, sans-serif;\n    border: 1px solid #ddd;\n    box-shadow: 0px 0px 5px rgba(0, 0, 0, 0.1);\n}\n\n.calendar-header {\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n    padding: 10px;\n    background-color: #f4f4f4;\n    font-weight: bold;\n}\n\n.calendar-header button {\n    background: none;\n    border: none;\n    font-size: 1.2em;\n    cursor: pointer;\n}\n\n.calendar-days {\n    display: grid;\n    grid-template-columns: repeat(7, 1fr);\n    text-align: center;\n}\n\n.day-name {\n    font-weight: bold;\n    padding: 5px 0;\n    color: #555;\n}\n\n.day {\n    padding: 10px 0;\n    border: 1px solid #f0f0f0;\n}\n\n.current-day {\n    background-color: #d3e9ff;\n    font-weight: bold;\n    color: #333;\n    border-radius: 50%;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Card Image Link/README.md",
    "content": "# Card Image Link Widget\n\nProvides an easy to use and nice looking card widget to add to Service Portal pages\n\n# Set up \n![Image of Card set up](./doc/card_setup.png)\n\n# End Result\n![Image of Card result](./doc/card_result.png)\n\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Card Image Link/mm_card.html",
    "content": "<div class=\"mm_card\">\n  <a href=\"{{c.data.link}}\" aria-label=\"{{c.options.description}} which opens in a new window\">\n    <img src=\"{{c.data.image}}\" class=\"mm_card_image\" alt=\"{{c.data.title}}\"/>\n    <div class=\"mm_card__overlay\"></div>\n    <div class=\"mm_card__header\">\n        <h3 class=\"mm_card__title\">\n          <div class=\"mm_card__title_text\">{{c.data.title}}</div>\n      </h3>\n    </div>\n  </a>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Card Image Link/mm_card_client_script.js",
    "content": "api.controller=function() {\n    /* widget controller */\n    var c = this;\n  };"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Card Image Link/mm_card_css.css",
    "content": ".mm_card  {\n    position: relative;\n    color: rgb(119, 119, 119);\n    transition: transform .2s; /* Animation */\n    width: 100%; \n    padding: 0px;  \n    margin-bottom: 20px;\n    box-shadow: 2px 2px 3px 1px grey;\n    border: solid #fff 2px;\n  }\n  \n  .mm_card:hover {\n    background: #3b5998;\n    color: #fff;\n    cursor: pointer;\n    transform: scale(1.05);\n    box-shadow: 2px 20px 30px 1px grey;\n    border: solid #fff 4px;\n  }\n  \n  .mm_card_image  {\n    width: 100%; \n  }\n  \n  .mm_card__header {\n      position : absolute;\n    top : 5%;\n    left: 5%;\n    z-index: 2;\n  }\n  \n  .mm_card__title {\n      color: #ffffff;\n      font-family: \"Source Sans Pro\", \"Helvetica Neue\", \"Helvetica\", \"Roboto\", \"Arial\", sans-serif;\n      -webkit-font-smoothing: antialiased;\n      -moz-osx-font-smoothing: grayscale;\n      text-transform: uppercase;\n      font-size: 4vw;\n      margin: 0;\n      text-shadow: 0 2px 4px rgb(0 0 0 / 50%);\n  }\n  \n  .mm_card__title_text {\n      color: #ffffff;\n      font-family: \"Source Sans Pro\", \"Helvetica Neue\", \"Helvetica\", \"Roboto\", \"Arial\", sans-serif;\n      font-size: 1em;\n    \n  }\n  \n  .mm_card:before {\n      background: #2B5C0D;\n      bottom: 0;\n      content: \"\";\n      height: 0;\n      left: 0;\n      position: absolute;\n      right: 0;\n      width: 100%;\n      z-index: 0;\n  }\n  .mm_card__overlay {\n      position: absolute;\n      background: rgba(0, 0, 0, 0.3);\n      bottom: 0;\n      content: \"\";\n      height: 100%;\n      width: 100%;\n      z-index: 0;\n  }"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Card Image Link/mm_card_server_script.js",
    "content": "(function() {\n\n    var img = new GlideRecord('db_image');\n    img.addQuery('sys_id', options.image);\n    img.query();\n    if (img.next()) {\n        data.image = img.getValue('name');\n    }\n    data.title = options.title || 'Card Title';\n    data.link  = options.link  || '?id=sc_category';\n    data.image = data.image || 'now-image-placeholder.jpg';\n\n})();"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Card Image Link/mm_card_widget_schema.json",
    "content": "[\n    {\n        \"displayValue\": \"Images\",\n        \"name\": \"image\",\n        \"section\": \"Presentation\",\n        \"label\": \"Image\",\n        \"type\": \"reference\",\n        \"value\": \"db_image\",\n        \"ed\": {\n            \"reference\": \"db_image\"\n        }\n    },\n    {\n        \"name\": \"title\",\n        \"section\": \"Presentation\",\n        \"label\": \"Title\",\n        \"type\": \"string\"\n    },\n    {\n        \"name\": \"link\",\n        \"section\": \"Presentation\",\n        \"label\": \"Link\",\n        \"type\": \"string\"\n    }\n]"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Catalog Item Explorer/README.md",
    "content": "# Catalog Item Explorer\n\nCatalog Item Explorer is a powerful ServiceNow widget designed to enhance the catalog browsing experience within your Service Portal. It provides users with a convenient way to navigate and explore catalog items alphabetically, making it easier than ever to locate and access items in your ServiceNow catalog.\n\n## Main Idea - Learning Resource\n\nThe primary goal of the Catalog Item Explorer widget is to serve as a valuable learning resource for individuals interested in widget development for the ServiceNow platform. It not only offers a practical solution for catalog navigation but also includes common techniques, best practices, and extensive comments throughout the codebase. This makes it an ideal tool for developers looking to understand and master widget development within the ServiceNow ecosystem.\n\n## Key Features\n\n- **Alphabetical Item Browsing:** Browse catalog items alphabetically, making it effortless to find items by their first letter.\n- **Pagination Support:** Efficiently navigate through a large number of items with built-in pagination controls.\n- **Quick Search:** Utilize the quick search feature to instantly filter items based on user-defined keywords.\n- **Customizable:** Tailor the widget to your specific needs with a range of configurable options.\n\n## Configurable Options\n\n1. **Catalog Selection:** Choose the catalogs with which the widget should work, allowing for flexibility in catalog management.\n2. **Items Per Page:** Define the number of items displayed per page to suit your catalog's content density.\n3. **All Items Category Label:** Customize the label for the \"All Items\" category to match your catalog's naming conventions.\n4. **Debug Mode:** Enable or disable debug logging to facilitate troubleshooting and development.\n5. **Item Link Configuration:** Configure the default link structure used to redirect users to catalog items.\n6. **Paginator Customization:** Set the maximum number of pages to display in the pagination component, and choose whether to include First/Last links.\n7. **Quick Search Placeholder:** Define the placeholder message for the Quick Search field to align with your portal's user interface.\n8. **Widget Title:** Customize the title of the widget to match its purpose within your Service Portal.\n9. **Copyright Display:** Choose whether to display copyright information in the bottom right corner of the widget.\n\n## Latest Update (v1.21):\n- **Support for the external URL content items.\n- **The default target window changed to \"_self\" (same window).\n- **Option to open an item in the new window added at the end of the row.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Catalog Item Explorer/client_script.js",
    "content": "api.controller = function($scope, $window) {\n    /* widget controller */\n    var c = this;\n\n    /* Variable and Service Initizalization */\n    setWidgetState(\"initial\", c.data.catalogCategories);\n\n    /* Function to be called when \"Show All Items\" has been clicked */\n    c.showAllItems = function() {\n        setWidgetState(\"initial\", c.data.catalogCategories);\n        c.filteredCatalogItems = c.displayItems = c.data.catalogItems;\n        c.isShowAllSelected = true;\n        c.data.currentPage = resetCurrentPage();\n        c.isMultiplePage = checkMultiPage(c.filteredCatalogItems.length, c.data.itemsPerPage);\n    };\n\n    /* Function to be called when \"Quick Search\" is active */\n    c.quickSearch = function() {\n        if ($scope.searchText.length == 0) {\n            setWidgetState(\"initial\", c.data.catalogCategories);\n            return;\n        }\n\n        setWidgetState(\"default-selected\", c.data.catalogCategories);\n        c.data.currentPage = resetCurrentPage();\n        c.filteredCatalogItems = c.displayItems = $scope.searchText.length > 0 ? quickSearch(c.data.catalogItems, $scope.searchText) : [];\n        c.isMultiplePage = checkMultiPage(c.filteredCatalogItems.length, c.data.itemsPerPage);\n    };\n\n    /* Function to be called when category letter has been clicked */\n    c.selectCategory = function(category) {\n        setWidgetState(\"default\", c.data.catalogCategories);\n        category.selected = true;\n        c.data.currentPage = resetCurrentPage();\n        c.filteredCatalogItems = selectCategory(c.data.catalogItems, category);\n        c.isMultiplePage = checkMultiPage(c.filteredCatalogItems.length, c.data.itemsPerPage);\n        c.displayItems = calculateDisplayCatalogItems(c.filteredCatalogItems, c.data.currentPage, c.data.itemsPerPage);\n    };\n\n    /* Function to be called when reset button has been pressed*/\n    c.resetState = function() {\n        setWidgetState(\"initial\", c.data.catalogCategories);\n    };\n\n\t/* Function to generate URL and define the target window */\n\tc.openUrl = function (itemId, externalUrl, openInNewWindow) {\n\t\tvar fullLink = \"\";\n\t\tfullLink = c.data.defaultCatalogLink + itemId;\n\n\t\t/* If external URL provided then replace the output with it */\n\t\tif (externalUrl) { fullLink = externalUrl; }\n\n\t\t/* Define the target window */\n\t\tvar target = openInNewWindow ? '_blank' : '_self';\n\t\t$window.open(fullLink, target);\n\t};\n\n    /* Pagination */\n\n    /* Function to be called by the form element when another page has been selected */\n    c.pageChanged = function() {\n        c.displayItems = calculateDisplayCatalogItems(c.filteredCatalogItems, c.data.currentPage, c.data.itemsPerPage);\n    };\n\n    /* Functions */\n\n    /* If it is a quick seach then we are giving filtered array based on the condition */\n    function quickSearch(items, searchText) {\n        return items.filter(function(item) {\n            try {\n                /* First we need to check that values are not null, otherwise assign them with empty space to avoid app crash */\n                var itemName = item.name != null ? item.name.toLowerCase() : \"\";\n                var itemDescription = item.description != null ? item.description.toLowerCase() : \"\";\n\n                /* Return item if quick search text we placed in our input field is contained in the item name or description */\n                return (itemName).indexOf(searchText.toLowerCase()) != -1 || (itemDescription).indexOf(searchText.toLowerCase()) != -1;\n            } catch (error) {\n                console.log(\"Something went wrong while filtering searching by item name or description\");\n            }\n        });\n    }\n\n    /* If it is a quick seach then we are giving filtered array based on the condition */\n    function selectCategory(items, category) {\n        return items.filter(function(item) {\n            return (item.name.toLowerCase()).substring(0, 1) == category.letter.toLowerCase();\n        });\n    }\n\n    /* Function to reset the category selection to default state (all are non-selected) */\n    function resetSelected(items) {\n        for (var i = 0; i < items.length; i++) {\n            items[i].selected = false;\n        }\n        c.isShowAllSelected = false;\n    }\n\n    /* Function to reset quick search text in the input field */\n    function resetQuickSearchText() {\n        $scope.searchText = \"\";\n    }\n\n    /* Function that accumulates reset of selected category and quick search text */\n    function setWidgetState(state, items) {\n        /* Default state is intended to clear quick search text and reset category selection only */\n        if (state == \"default\") {\n            resetSelected(items);\n            resetQuickSearchText();\n\n            return c.data.msgDefaultState;\n        }\n\n        /* Default-Selected is intended to reset the category selection state only e.g. for All items category selection */\n        if (state == \"default-selected\") {\n            resetSelected(items);\n\n            return c.data.msgCategoryReset;\n        }\n\n        /* Initial is intended to bring the widget to the initial state same as after pager reload */\n        if (state == \"initial\") {\n            resetQuickSearchText();\n            resetSelected(items);\n            c.filteredCatalogItems = c.data.catalogItems;\n            c.displayItems = [];\n            c.isShowAllSelected = false;\n            c.isMultiplePage = false;\n\n            return \"Initialization has completed\";\n        }\n    }\n\n    /* Function to flag multipaging which is used by pagination to display page selector */\n    function checkMultiPage(itemsToDisplay, numOfPages) {\n        return Math.ceil(itemsToDisplay / numOfPages) > 1 ? true : false;\n    }\n\n    /* Function to reset the current page to 1 everytime the category changes */\n    function resetCurrentPage() {\n        return 1;\n    }\n\n    /* Function to prepare the list of items to display based on the selected page */\n    function calculateDisplayCatalogItems(filteredItemsArray, currentPage, itemsPerPage) {\n        return filteredItemsArray.slice((currentPage - 1) * itemsPerPage, currentPage * itemsPerPage);\n    }\n\n    /* Debug - Logs */\n    if (c.data.isDebugEnabled) {\n        console.log(c);\n    }\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Catalog Item Explorer/css.scss",
    "content": ".category-header {    \n    display: flex;\n    justify-content: center;\n    flex-wrap: wrap;    \n    width: 100%;\n    padding: 1rem 0;\n    margin: 0;\n}\n\n.catalog-category {\n    font-size: 2.4rem;\n    font-weight: 600;\n}\n\n.category-letter:hover {\n    transform: scale(1.4);\n    border-radius: 1rem;\n    cursor: pointer;\n}\n\n.selected {\n    transform: scale(1.4);\n    background-color: #FFFFFF;\n    color: darkgreen;\n}\n\n.list-inline {\n    margin: 0;\n}\n\n.list-group-item:hover {\n    background-color: #EEE;\n}\n\n.primary-display {\n    color: #428BCA;\n}\n\n.list-group-item {\n  margin:0; \n  display: flex; \n  align-items: center;\n}\n\n.main-column {\n    flex: 55%;\n    cursor: pointer;\n}\n\n.item-type-column {\n\tflex: 25%; \n    text-align: center;\n  \tfont-size: 1.2rem;\n}\n\n.external-redirect-cell {\n  \tflex: 10%; \n    text-align: center;\n}\n\n.panels-container {\n    display: flex; \n    justify-content: center;\n}\n\n.panel-footer, .panel-heading {\n  \theight: 4rem;\n    display: flex;\n    justify-content: space-between;\n    align-items: center;\n\n    .copyright {\n        align-self: center;\n    }\n}\n\n.xiva-quick-search-container {\n    display: flex;\n    flex-direction: row;\n    justify-content: space-evenly;\n    align-items: center;\n    border-left: 1px solid #ccc;    \n\n    .fa-search::before {\n        padding-right: 1rem;        \n    }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Catalog Item Explorer/options_schema.json",
    "content": "[ {\n  \"displayValue\" : \"Catalog\",\n  \"hint\" : \"Which catalog should this widget work with?\",\n  \"name\" : \"used_catalog\",\n  \"display_value_list\" : [ ],\n  \"section\" : \"Data\",\n  \"default_value\" : \"e0d08b13c3330100c8b837659bba8fb4\",\n  \"label\" : \"Used catalog\",\n  \"type\" : \"glide_list\",\n  \"value\" : \"sc_catalog\",\n  \"ed\" : {\n    \"reference\" : \"sc_catalog\"\n  }\n}, {\n  \"hint\" : \"How many items should be displayed per page?\",\n  \"name\" : \"items_per_page\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"10\",\n  \"label\" : \"Items per page\",\n  \"type\" : \"integer\"\n}, {\n  \"hint\" : \"How should All Items category be called?\",\n  \"name\" : \"show_all_items_category_label\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"All\",\n  \"label\" : \"Show All Items category label\",\n  \"type\" : \"string\"\n}, {\n  \"hint\" : \"Should debug logging be enabled?\",\n  \"name\" : \"is_debug_mode_on\",\n  \"section\" : \"other\",\n  \"default_value\" : \"false\",\n  \"label\" : \"Enable Debug\",\n  \"type\" : \"boolean\"\n}, {\n  \"hint\" : \"What link should the widget use to redirect to the catalog item?\",\n  \"name\" : \"default_item_link\",\n  \"section\" : \"Behavior\",\n  \"default_value\" : \"?id=sc_cat_item&sys_id=\",\n  \"label\" : \"Default link to the catalog item\",\n  \"type\" : \"string\"\n}, {\n  \"hint\" : \"How many pages should be displayed in paginator at once?\",\n  \"name\" : \"max_pages_in_paginator\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"4\",\n  \"label\" : \"Maximum pages in the pagination component\",\n  \"type\" : \"integer\"\n}, {\n  \"hint\" : \"Should the First/Last links be displayed in the paginator?\",\n  \"name\" : \"show_boundary_links_in_paginator\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"false\",\n  \"label\" : \"Show boundary links in the pagination component\",\n  \"type\" : \"boolean\"\n}, {\n  \"hint\" : \"What text should be used for Quick Search field as a placeholder?\",\n  \"name\" : \"quick_search_placeholder\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"Quick search...\",\n  \"label\" : \"Quick Search placeholder message\",\n  \"type\" : \"string\"\n}, {\n  \"hint\" : \"What title should this widget have?\",\n  \"name\" : \"widget_title\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"Catalog Item Explorer\",\n  \"label\" : \"Widget Title\",\n  \"type\" : \"string\"\n}, {\n  \"hint\" : \"Display copyright in the right bottom corner\",\n  \"name\" : \"show_copyright\",\n  \"section\" : \"Presentation\",\n  \"default_value\" : \"false\",\n  \"label\" : \"Show Copyright\",\n  \"type\" : \"boolean\"\n} ]\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Catalog Item Explorer/script.js",
    "content": "(function () {\n\n    /* Configuration */\n    data.isDebugEnabled = (options.is_debug_mode_on === \"true\");\n    data.showCopyright = options.show_copyright === \"true\" || false;\n    data.showAllMsg = gs.getMessage(options.show_all_items_category_label) || gs.getMessage(\"All\");\n    data.defaultCatalogLink = options.default_item_link ||\"?id=sc_cat_item&sys_id=\";\n    data.itemsPerPage = $sp.getParameter(\"items_per_page\") || options.items_per_page || 10;\n    data.maxPagesInPaginator = options.max_pages_in_paginator || 4;\n    data.showBoundaryLinks = (options.show_boundary_links_in_paginator === \"true\") || false;\n    data.widgetTitle = options.widget_title || \"Catalog Item Explorer\";\n\n    /* Used to set the default first page for the pagination */\n    data.currentPage = 1;    \n\n    /* Messages */\n    data.showAllMsg = gs.getMessage(options.show_all_items_category_label) || gs.getMessage(\"All\");\n    data.msgQuickSearchPlaceholder = gs.getMessage(options.quick_search_placeholder);\n    data.msgDefaultState = gs.getMessage(\"Widget fields were set back to default\");\n    data.msgCategoryReset = gs.getMessage(\"Category selection was reset\");\n\n    /* Get Catalog ID */\n    var catalogsId = $sp.getParameter(\"used_catalog\") || options.used_catalog;\n    \n    /* Get all catalog items which are active and not marked hidden on service portal */\n    var catalogItems = new GlideRecordSecure('sc_cat_item');\n    catalogItems.addQuery('sc_catalogs', 'IN', catalogsId);\n    catalogItems.addQuery('active', true);\n\tcatalogItems.addQuery('hide_sp', false);\n    catalogItems.orderBy('name');\n    catalogItems.query();\n\n    /* Save all sys_ids and names of catalog items to an array */\n    data.catalogItems = [];\n    \n    /* Declare a variable to host externalUrl */\n    var extUrl = \"\";\n\n    while (catalogItems.next()) {\n        if (!$sp.canReadRecord(\"sc_cat_item\", catalogItems.sys_id.getDisplayValue())) {\n            continue;\n        }\n\n        extUrl = \"\";        \n        if (catalogItems.sys_class_name == \"sc_cat_item_content\") {\n            var contentItemGr = new GlideRecordSecure('sc_cat_item_content');\n            contentItemGr.get(catalogItems.getUniqueValue());\n            extUrl = contentItemGr.isValidRecord() ? contentItemGr.getValue('url') : \"\";            \n        }\n\n        data.catalogItems.push({\n            itemId: catalogItems.getUniqueValue(),\n            name: catalogItems.getValue('name'),\n            description: catalogItems.getValue('short_description'),\n\t\t\ttype: catalogItems.getDisplayValue('sys_class_name'),\n\t\t\texternalUrl: extUrl\n        });\n    }\n\n    /* Get first letters to generate categories */\n    data.catalogCategories = getUniqueFirstLetters(data.catalogItems);\n\n    function getUniqueFirstLetters(strings) {\n        /* Create an object to store unique first letters */ \n\t\tvar firstLettersMap = {};\n\n\t\t/* Iterate over the input array of strings */ \n\t\tfor (var i = 0; i < strings.length; i++) {\n\t\t\t\t/* Get the first letter of the current string and convert it to uppercase */ \n\t\t\t\tvar firstLetter = strings[i].name.charAt(0).toUpperCase();\n\n\t\t\t\t/* Use the letter as a key in the object to ensure uniqueness */ \n\t\t\t\tif (!firstLettersMap[firstLetter]) {\n\t\t\t\t\t\tfirstLettersMap[firstLetter] = true;\n\t\t\t\t}\n\t\t}\n\n\t\t/* Convert the object keys to an array of objects */ \n\t\tvar firstLetters = [];\n\t\tfor (var letter in firstLettersMap) {\n\t\t\t\tif (firstLettersMap.hasOwnProperty(letter)) {\n\t\t\t\t\t\tfirstLetters.push({\n\t\t\t\t\t\t\t\tletter: letter,\n\t\t\t\t\t\t\t\tselected: false\n\t\t\t\t\t\t});\n\t\t\t\t}\n\t\t}\n\n\t\t/* Sort the array of objects */ \n\t\tfirstLetters.sort(function (a, b) {\n\t\t\t\treturn a.letter.localeCompare(b.letter);\n\t\t});\n\n\t\t/* Return the sorted array of unique first letters */ \n\t\treturn firstLetters;\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Catalog Item Explorer/template.html",
    "content": "<div class=\"panel panel-default b\">\n    <!-- Panel Heading -->\n    <div class=\"panel-heading\">\n        <div><h2 class=\"panel-title\">{{::c.data.widgetTitle}}</h2></div>\n        <div><button ng-if=\"c.filteredCatalogItems.length > 0\" type=\"button\"\n            class=\"btn btn-primary btn-xs\" ng-click=\"c.showAllItems()\">{{::c.data.showAllMsg}}</button>\n            <button ng-if=\"c.displayItems.length > 0\" type=\"button\"\n                class=\"btn btn-danger btn-xs hidden-xs\" ng-click=\"c.resetState()\">Reset</button></div>\n    </div>\n    <!-- Panel Body -->\n    <div class=\"panels-container\">\n        <div class=\"row category-header fit-content\">\n            <!-- Catalog Category Letters -->\n            <div class=\"col-lg-8 col-md-8 col-sm-12 col-xs-12\">\n                <ul class=\"list-inline text-center\">\n                    <li class=\"list-inline-item\" ng-repeat=\"category in c.data.catalogCategories\">\n                        <div class=\"category-letter\" item=\"category\" ng-class=\"{'selected' : category.selected}\"\n                            ng-click=\"c.selectCategory(category)\"><span\n                                class=\"catalog-category\">{{category.letter}}</span>\n                        </div>\n                    </li>                    \n                </ul>\n            </div>\n            <!-- Quick Search Section -->\n            <div class=\"xiva-quick-search-container col-lg-4 col-md-4 hidden-sm hidden-xs\">\n                <span class=\"fa fa-search\"></span><input class=\"form-control\" type=\"text\"\n                    ng-model=\"searchText\" ng-change=\"c.quickSearch()\"\n                    placeholder=\"{{c.data.msgQuickSearchPlaceholder}}\">\n            </div>\n        </div>\n    </div>\n    <!-- Item List -->\n    <ul role=\"rowgroup\" class=\"list-group padder-l-none padder-r-none\">\n      <li class=\"list-group-item\" ng-repeat=\"item in c.displayItems | limitTo:c.data.itemsPerPage\" ng-click=\"c.openUrl(item.itemId, item.externalUrl)\">\n          <div role=\"cell\" class=\"padder-l-none padder-r-none main-column\">\n              <div class=\"primary-display\">\n                  {{item.name}}\n              </div>\n              <small class=\"text-muted\">\n                  <div class=\"secondary-display\">\n                      <span>{{item.description}}</span>\n                  </div>\n              </small>\n          </div>\n          <!-- Item Type -->\n          <div role=\"cell\" class=\"item-type-column text-muted\">\n              {{item.type}}\n          </div>\n          <!-- External Redirect -->\t\n          <div role=\"cell external-redirect-cell\" ng-click=\"c.openUrl(item.itemId, item.externalUrl, true); $event.stopPropagation();\">\n              <i class=\"fa fa-external-link\" aria-hidden=\"true\"></i>              \n          </div>\n      </li>\n\t</ul>\n    <!-- Pagination -->\n    <div class=\"panels-container\" ng-if=\"c.isMultiplePage\">\n        <uib-pagination total-items=\"c.filteredCatalogItems.length\" ng-model=\"c.data.currentPage\"\n            ng-change=\"c.pageChanged()\" items-per-page=\"c.data.itemsPerPage\" max-size=\"c.data.maxPagesInPaginator\"\n            boundary-links=\"c.data.showBoundaryLinks\">\n        </uib-pagination>\n    </div>\n    <!-- Panel Footer -->\n    <div class=\"panel-footer\">Total items found:\n        {{c.filteredCatalogItems.length}}<span class=\"copyright\" ng-show=\"c.data.showCopyright\">&copy; 2025 Ivan Betev</span></div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Calendar Report/Body HTML template.html",
    "content": "<div class=\"report-widget-wrap\">\n\n\t<h2 ng-if=\"c.showTitle\" tabindex=\"0\" id=\"{{'title-' + c.rectangleId }}\" class=\"report-widget-title\">{{c.title}}</h2>\n\n\t<div id=\"report-widget-{{c.rectangleId}}\">\n     {{::c.initialMessage}}\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Calendar Report/CSS",
    "content": ".report-widget-wrap {\n\t\t\tbackground:#fff;\n\t\t\tpadding:15px;\n\t\t\tmargin: 0 0 15px 0;\n\t\t\t}\n\t\t\t\n\t\t\t.report-widget-title {\n\t\t\tpadding: $sp-space--xl;\n\t\t\tfont-weight:bold;\n\t\t\tmargin-top: 0;\n\t\t\tmargin-bottom: 0;\n\t\t\tfont-family: $now-sp-font-family-sans-serif;\n\t\t\tcolor: $text-color;\n\t\t\tfont-size: $font-size-h4;\n\t\t\t\n\t\t\t}\n\t\t\t\n\t\t\t.highcharts-container g.highcharts-button *,\n\t\t\t.highcharts-container image.hc-image {\n\t\t\ttransition: fill-opacity 0.3s linear, stroke-opacity 0.3s linear, opacity 0.3s linear;\n\t\t\tfill-opacity: 0;\n\t\t\tstroke-opacity: 0;\n\t\t\topacity:0;\n\t\t\t}\n\t\t\t\n\t\t\t.highcharts-container:hover g.highcharts-button *,\n\t\t\t.highcharts-container:hover image.hc-image {\n\t\t\tfill-opacity: 1;\n\t\t\tstroke-opacity: 1;\n\t\t\topacity:1;\n\t\t\t}\n\t\t\t\n\t\t\t.highcharts-legend-item span::after,\n\t\t\t.highcharts-legend-item::after {\n\t\t\tcontent: \"\\200E\";\n\t\t\t}\n\t\t\t\n\t\t\ttable.wide .pivot_cell,\n\t\t\ttable.wide .pivot_caption,\n\t\t\ttable.wide .pivot_caption_dark {\n\t\t\tpadding: 3px 5px;\n\t\t\t}\n            .highlight-wrap {\n                display: none;\n            }\n\n            .fc-week-number {\n                width: 42px;\n                background-color: #ededed;\n            }\n            \n\t\t\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Calendar Report/Client Controller",
    "content": "function($scope, $uibModal, $timeout, spUtil) {\n    var c = this;\n    var reportId = c.options.report_id || '';\n    c.rectangleId = c.widget.rectangle_id || c.data.rectangleId;\n    c.showTitle = (c.options.show_title === true || c.options.show_title === 'true');\n    c.title = c.options.title || '';\n\n    if (c.options.widget_parameters) {\n        c.initialMessage = c.data.ch.i18n.building;\n        window.chartHelpers = window.chartHelpers || {};\n        $.extend(window.chartHelpers, c.data.ch);\n\n        $timeout(function() {\n            var targetEl = $(\"#report-widget-\" + c.rectangleId);\n            embedReportById(targetEl, reportId);\n\n            $timeout(function() {\n                targetEl.off('click', 'a[href*=\"change_request.do?sys_id\"]');\n\n                targetEl.on('click', 'a[href*=\"change_request.do?sys_id\"]', function(event) {\n                    event.preventDefault();\n                    var href = $(this).attr('href') || '';\n                    var match = href.match(/sys_id=([a-f0-9]{32})/i);\n                    var sysId = match ? match[1] : '';\n\n                    var modalData = {\n                        number: '',\n                        short_description: '',\n                        description: '',\n                        sys_id: sysId\n                    };\n\n                    // Open modal immediately\n                    $uibModal.open({\n                        controller: function($scope, $uibModalInstance, $sce) {\n                            $scope.data = modalData;\n\n                            $scope.getTrustedDescription = function() {\n                                if (!$scope.data.description) return '';\n                                var text = $scope.data.description;\n\n                                // Convert line breaks to <br>\n                                text = text.replace(/\\n/g, '<br>');\n\n                                // Convert URLs to links\n                                var urlRegex = /(\\bhttps?:\\/\\/[^\\s<]+)/gi;\n                                text = text.replace(urlRegex, function(url) {\n                                    return '<a href=\"' + url + '\" target=\"_blank\" rel=\"noopener noreferrer\">' + url + '</a>';\n                                });\n\n                                // Convert emails to mailto links\n                                var emailRegex = /([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,6})/gi;\n                                text = text.replace(emailRegex, function(email) {\n                                    return '<a href=\"mailto:' + email + '\">' + email + '</a>';\n                                });\n\n                                return $sce.trustAsHtml(text);\n                            };\n\n                            $scope.close = function() {\n                                $uibModalInstance.dismiss('cancel');\n                            };\n                        },\n                        resolve: {\n                            $sce: function() {\n                                return angular.injector(['ng']).get('$sce');\n                            }\n                        },\n                        template: '<div class=\"modal-header\">' +\n                            '<h4 class=\"modal-title\">Change Details</h4>' +\n                            '</div>' +\n                            '<div class=\"modal-body\">' +\n                            '<p><strong>Change Number:</strong> {{data.number}}</p>' +\n                            '<p><strong>Short Description:</strong> {{data.short_description}}</p>' +\n                            '<p><strong>Description:</strong></p>' +\n                            '<p ng-bind-html=\"getTrustedDescription()\"></p>' +\n                            '</div>' +\n                            '<div class=\"modal-footer\">' +\n                            '<a class=\"btn btn-primary\" ng-href=\"/change_request.do?sys_id={{data.sys_id}}\" target=\"_blank\">View Record</a>' +\n                            '<button class=\"btn btn-default\" ng-click=\"close()\">Close</button>' +\n                            '</div>'\n                    });\n\n                    // Populate modal data via server\n                    spUtil.get(c.widget.sys_id, {\n                        action: 'getChangeDetails',\n                        sys_id: sysId\n                    }).then(function(response) {\n                        if (response.data.changeDetails && !response.data.changeDetails.error) {\n                            modalData.number = response.data.changeDetails.number;\n                            modalData.short_description = response.data.changeDetails.short_description;\n                            modalData.description = response.data.changeDetails.description;\n                        }\n                    });\n                });\n            }, 1000);\n        });\n    } else {\n        c.initialMessage = c.data.ch.i18n.selectReport;\n    }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Calendar Report/README.md",
    "content": "#  Report IT Change Request: Change Calendar Widget\n\nA **Service Portal widget** for displaying interactive **Change Request Calendar Reports** in ServiceNow.\nThis widget embeds a ServiceNow report, allows visual exploration of change data, and enhances user experience through color-coded legends and a modal view for detailed record insights.\n\n---\n\n## Features\n\n* **Report Embedding:** Displays a selected ServiceNow report dynamically in the portal.\n* **Interactive Legend:** Color legend automatically updates based on selected highlight field (`risk`, `type`, `state`).\n* **Change Request Details Modal:** Clicking a change number opens a modal showing detailed record information (number, description, risk, state, start and end dates).\n* **Dynamic Color Mapping:** Fetches `sys_ui_style` and `sys_choice` data to visualize change request status colors.\n* **Accessible & Responsive UI:** Fully keyboard-accessible with clear color indicators and responsive design.\n\n---\n\n## Configuration\n\n### **Widget Options**\n\n| Option       | Type                     | Description                           |\n| ------------ | ------------------------ | ------------------------------------- |\n| `report_id`  | Reference (`sys_report`) | Select the ServiceNow report to embed |\n| `show_title` | Boolean                  | Toggle visibility of report title     |\n\n### **Installation Steps**\n\n1. Import the widget XML into your ServiceNow instance via **Studio** or **Update Set**.\n2. Add the widget to a Service Portal page (e.g., Change Dashboard).\n3. In widget options:\n\n   * Select your desired **report** (`sys_report`).\n   * Enable “Show Title” if required.\n4. Save and reload the page — the report will render dynamically.\n\n---\n\n## Color Legend\n\n* Automatically generated from `sys_ui_style` table for elements `type`, `state`, and `risk`.\n* Displays color-coded labels for visual clarity.\n* Updates automatically when the highlight field dropdown changes.\n\n**Example:**\n\n| Element | Color        | Meaning            |\n| ------- | ------------ | ------------------ |\n| State   | 🔵 Implement | Change in progress |\n| Risk    | 🟡 High      | Requires review    |\n| Type    | 🟢 Normal    | Standard change    |\n\n---\n\n## Modal Preview of Change Details\n\nClicking a change number in the calendar opens a modal window with:\n\n| Field               | Description                  |\n| ------------------- | ---------------------------- |\n| Change Number       | Linked record reference      |\n| Short Description   | Summary of change            |\n| Description         | Detailed explanation         |\n| Type / Risk / State | Key metadata fields          |\n| Planned Start & End | Change implementation window |\n\n---\n\n## Technical Overview\n\n| Component         | Technology                              | Purpose                                            |\n| ----------------- | --------------------------------------- | -------------------------------------------------- |\n| **Client Script** | AngularJS + jQuery + `$uibModal`        | Event handling, modal logic, legend updates        |\n| **Server Script** | GlideRecord API                         | Fetch report and change details securely           |\n| **CSS**           | Custom SCSS / SP Variables              | Responsive layout, color blocks, and accessibility |\n| **Template**      | Angular bindings (`ng-if`, `ng-repeat`) | Dynamic rendering of legend and report             |\n\n---\n\n## Security & Performance\n\n* Uses `spUtil.get()` for secure data retrieval via the widget server script.\n* Enforces ACL-based record access (`change_request` table).\n* Sanitizes HTML using `$sce.trustAsHtml` for safe modal rendering.\n* Optimized DOM operations and `$timeout` to reduce UI latency.\n\n---\n\n## Dependencies\n\n* **ServiceNow Studio or App Engine Studio**\n* **Service Portal Enabled**\n* **Change Management Application** (`change_request` table)\n* **Performance Analytics & Reporting Plugin** (`com.snc.pa.sp.widget`)\n\nOptional:\n\n* **Color Mapping in `sys_ui_style`**\n* **Active `sys_report` record**\n\n---\n\n## Example Use Case\n\n> The Change Manager wants a visual, color-coded view of all scheduled changes for the month.\n> Using the **Report ITS Change Request** widget, they embed their “Change Calendar by Risk” report into the Service Portal.\n> They can quickly filter changes, view color-coded statuses, and open detailed records—all from one place.\n\n---\n\n## Testing Scenarios\n\n| Test                       | Expected Result                                       |\n| -------------------------- | ----------------------------------------------------- |\n| Load widget without report | Displays “Select a report in widget options!” message |\n| Click on change link       | Modal opens with record details                       |\n| Change highlight dropdown  | Legend updates to reflect new color group             |\n| No matching record         | Displays “Record not found” in modal                  |\n\n---\n\n## Future Enhancements\n\n* Filter changes by assignment group or service.\n* Add “Export as PDF” or “Add to Calendar” options.\n* Integrate with CAB meeting module for review visualization.\n* Replace jQuery with native AngularJS `$element` bindings for performance.\n\n---\n\n## Contributors\n\n* **Developer:** Admin / ServiceNow Platform Engineer\n* **Maintainers:** Performance Analytics & Reporting Widget Team\n* **Scope:** Global (`x_snc_pa.sp.widget`)\n\n---\nPlease find the screenshot below \n\n![WhatsApp Image 2025-10-26 at 09 59 27 (1)](https://github.com/user-attachments/assets/a2e024cf-87be-4f29-9c5a-aee3e2dffbfd)\n\n![WhatsApp Image 2025-10-26 at 09 59 27 (2)](https://github.com/user-attachments/assets/bd610e94-08ac-47be-842d-e8c59dadce70)\n\n![WhatsApp Image 2025-10-26 at 09 59 27](https://github.com/user-attachments/assets/1333e974-6b56-48b8-b1c2-340e0a35e0af)\n\n<img width=\"637\" height=\"454\" alt=\"image\" src=\"https://github.com/user-attachments/assets/07d34c8b-429d-4522-b68d-71603f0206eb\" />\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Calendar Report/Server Side Script",
    "content": "(function() {\n    options.report_id = options.report_id || '';\n\n    if (options.report_id !== '') {\n        var reportGr = new GlideRecord('sys_report');\n        reportGr.get(options.report_id);\n        if (reportGr.canRead())\n            options.title = reportGr.getDisplayValue('title');\n    }\n\n    var chartHelpers = chartHelpers || {};\n    chartHelpers.i18n = chartHelpers.i18n || {};\n\n    chartHelpers.i18n.selectReport = gs.getMessage('Select a report in widget options!');\n    chartHelpers.i18n.building = gs.getMessage('Building chart, please wait...');\n    chartHelpers.i18n.total = gs.getMessage('Total');\n    chartHelpers.i18n.maxCells = gs.getMessage('The size of the pivot table is too big. Use filters to reduce it or switch to a modern browser.');\n    chartHelpers.i18n.chartGenerationError = gs.getMessage('An error occurred while generating chart. Please try again later.');\n\n    chartHelpers.i18n.showAsHeatmap = gs.getMessage('Show data as a heatmap visualization');\n    chartHelpers.i18n.showAsMarkers = gs.getMessage('Show data using latitude and longitude');\n    chartHelpers.i18n.saveAsJpg = gs.getMessage('Save as JPEG');\n    chartHelpers.i18n.saveAsPng = gs.getMessage('Save as PNG');\n    chartHelpers.i18n.highlightBasedOn = gs.getMessage('Highlight based on:');\n    chartHelpers.i18n.isRTL = GlideI18NStyle().getDirection().equals('rtl');\n    chartHelpers.i18n.weekNumberTitle = gs.getMessage('Week');\n    chartHelpers.i18n.weekNumberTitleShort = gs.getMessage('Week');\n    chartHelpers.i18n.seeMoreEvents = gs.getMessage('See {0} more events');\n    chartHelpers.i18n.viewEventsInList = gs.getMessage('View {0} events in a list');\n    chartHelpers.i18n.viewAllEventsInList = gs.getMessage('View all events in a list');\n    chartHelpers.i18n.viewAllRecords = gs.getMessage('View all records');\n    chartHelpers.i18n.none = gs.getMessage('None');\n    chartHelpers.i18n.plusMany = gs.getMessage('+ many');\n    chartHelpers.i18n.plusMore = gs.getMessage('+ {0} more');\n    chartHelpers.i18n.buttonText = {\n        prevYear: \"\",\n        nextYear: \"\",\n        today: gs.getMessage('today'),\n        year: gs.getMessage('year'),\n        month: gs.getMessage('month'),\n        week: gs.getMessage('week'),\n        day: gs.getMessage('day')\n    };\n    chartHelpers.i18n.allDayHtml = gs.getMessage('all-day');\n    chartHelpers.i18n.daysNames = [\n        gs.getMessage('Sunday'),\n        gs.getMessage('Monday'),\n        gs.getMessage('Tuesday'),\n        gs.getMessage('Wednesday'),\n        gs.getMessage('Thursday'),\n        gs.getMessage('Friday'),\n        gs.getMessage('Saturday')\n    ];\n    chartHelpers.i18n.dayNamesShort = [\n        gs.getMessage('Sun'),\n        gs.getMessage('Mon'),\n        gs.getMessage('Tue'),\n        gs.getMessage('Wed'),\n        gs.getMessage('Thu'),\n        gs.getMessage('Fri'),\n        gs.getMessage('Sat')\n    ];\n    chartHelpers.i18n.monthNames = [\n        gs.getMessage('January'),\n        gs.getMessage('February'),\n        gs.getMessage('March'),\n        gs.getMessage('April'),\n        gs.getMessage('May'),\n        gs.getMessage('June'),\n        gs.getMessage('July'),\n        gs.getMessage('August'),\n        gs.getMessage('September'),\n        gs.getMessage('October'),\n        gs.getMessage('November'),\n        gs.getMessage('December')\n    ];\n    chartHelpers.i18n.monthNamesShort = [\n        gs.getMessage('Jan'),\n        gs.getMessage('Feb'),\n        gs.getMessage('Mar'),\n        gs.getMessage('Apr'),\n        gs.getMessage('May'),\n        gs.getMessage('Jun'),\n        gs.getMessage('Jul'),\n        gs.getMessage('Aug'),\n        gs.getMessage('Sep'),\n        gs.getMessage('Oct'),\n        gs.getMessage('Nov'),\n        gs.getMessage('Dec')\n    ];\n    chartHelpers.i18n.none = gs.getMessage('-- None --');\n    chartHelpers.i18n.groupBy = gs.getMessage('Group by');\n    chartHelpers.i18n.groupByTitle = gs.getMessage('Select a different group by field');\n    chartHelpers.i18n.stackBy = gs.getMessage('Stacked by');\n    chartHelpers.i18n.stackByTitle = gs.getMessage('Select a different stacked by field');\n    chartHelpers.device = {};\n    chartHelpers.device.type = GlideMobileExtensions.getDeviceType();\n\n    chartHelpers.systemParams = {\n        firstDay: (gs.getProperty(\"glide.ui.date_format.first_day_of_week\", 2) - 1) % 7,\n        defaultDate: SNC.ReportUtil.getNowTimeInUSFormat(),\n        maxEventsDisplayedPerCell: gs.getProperty(\"glide.report.calendar.max_events_displayed_per_cell\", 3),\n        maxMoreEventsPerDay: gs.getProperty(\"glide.report.calendar.max_more_events_per_day\", 30),\n        defaultEventDuration: gs.getProperty(\"glide.report.calendar.default_event_duration\", \"01:00:00\"),\n        maxDaysBack: gs.getProperty(\"glide.report.calendar.max_days_back\", 30),\n        enablePreviewOnHover: gs.getProperty(\"glide.report.calendar.enable_preview_on_hover\", true)\n    };\n\n    data.rectangleId = gs.generateGUID();\n    data.ch = chartHelpers;\n\n//Passing Change Details to Client Controller to show data in the modal on Click\n// From here\n    if (input && input.action === 'getChangeDetails' && input.sys_id) {\n        var gr = new GlideRecord('change_request');\n        if (gr.get(input.sys_id)) {\n            data.changeDetails = {\n                number: gr.getValue('number'),\n                short_description: gr.getValue('short_description') || 'No short description',\n\t\t\t\tdescription:gr.getValue('description') ||'No description'\n            };\n        } else {\n            data.changeDetails = {\n                error: 'Record not found'\n            };\n        }\n    }\n// Till here\n\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Notification Preferences/README.md",
    "content": "## Notification Preference by Category widget\n\nPortal Widget that shows user notification preferences for a certain category (changed within the options). User can switch his notification on or off. The notification description gets displayed under the notification name."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Notification Preferences/client.js",
    "content": "api.controller = function($http) {\n    /* widget controller */\n    var c = this;\n    var baseApiUrl = '/api/now/v1/notification/preference';\n    c.enableNotifications = true;\n    // initial call to get all the notifications\n    $http.get(baseApiUrl + '?category=' + c.data.category + '&sysparm_limit=100&sysparm_offset=0')\n        .then(function(response) {\n            if (response.data && response.statusText == 'OK') {\n              var tempNotificationArray = response.data.result.preferences;\n\t\t\t\t\t\t\tc.notificationArray = tempNotificationArray.map(function(obj){\n\t\t\t\t\t\t\t\t$http.get('/api/now/table/sysevent_email_action?sysparm_query=sys_id='+obj.notification.sys_id.toString()+'&sysparm_fields=description&sysparm_limit=1')\n\t\t\t\t\t\t\t\t.then(function(resp){\n\t\t\t\t\t\t\t\t\tobj.description = resp.data.result[0].description;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\treturn obj;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\n            }\n        });\n    c.toggleNotification = function(active, channel_id, notification_id, sys_id, table) {\n        var data = {};\n        data.active = active;\n        data.channel_id = channel_id;\n        data.notification_id = notification_id;\n        data.sys_id = (sys_id != '') ? sys_id : null;\n        data.table = table;\n        $http.post(baseApiUrl, {\n                preference: data\n            })\n            .then(function(response) {\n            });\n    };\n};"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Notification Preferences/css.css",
    "content": "\n.list-group-item {\n\tborder: none;\n  \n\t.btn-link {\n\t  padding-left: 0;\n\t  padding-right: 0;\n\t}\n  }\n  .input-switch{\n\tdisplay: inline-block;\n\tfloat: right;"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Notification Preferences/html.html",
    "content": "<div class=\"notification-content\">\n    <div class=\"panel panel-default\">\n      <div class=\"panel-heading\">\n        <h2 class=\"panel-title\">\n          <i class=\"fa fa-envelope-o m-r-sm\"></i>${Notifications}\n        </h2>\n      </div>\n      <div class=\"body padder-xs\">\n        <div class=\"list-group\">\n          <div class=\"list-group-item\" ng-repeat='notification in c.notificationArray'>\n            <label><strong>{{notification.name}}</strong></label>\n            <div class=\"input-switch\">\n              <input aria-labelledby=\"switch_label_{{notification.notification.sys_id}}\" \n                 id=\"switch{{notification.notification.sys_id}}\" \n                 type=\"checkbox\" \n                 name=\"switch{{notification.notification.sys_id}}\" \n                 data-ng-model=\"notification.records[0].logical_active\" \n                 data-ng-change=\"c.toggleNotification(notification.records[0].logical_active,notification.records[0].channel.sys_id,notification.notification.sys_id,notification.records[0].sys_id,notification.records[0].table)\" \n                 data-ng-disabled=\"notification.records[0].readonly\" >\n          <label aria-hidden=\"true\" class=\"switch\" for=\"switch{{notification.notification.sys_id}}\">&#8203;</label>\n            </div>\n            <div class='description' ng-bind-html='::notification.description'>\n            </div>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Notification Preferences/notification_preference.js",
    "content": "//client side script of the widget\n\napi.controller = function($http) {\n    /* widget controller */\n    var c = this;\n    var baseApiUrl = '/api/now/v1/notification/preference';\n    c.enableNotifications = true;\n    // initial call to get all the notifications\n    $http.get(baseApiUrl + '?category=' + c.data.category + '&sysparm_limit=100&sysparm_offset=0')\n        .then(function(response) {\n            if (response.data && response.statusText == 'OK') {\n              var tempNotificationArray = response.data.result.preferences;\n\t\t\t\t\t\t\tc.notificationArray = tempNotificationArray.map(function(obj){\n\t\t\t\t\t\t\t\t$http.get('/api/now/table/sysevent_email_action?sysparm_query=sys_id='+obj.notification.sys_id.toString()+'&sysparm_fields=description&sysparm_limit=1')\n\t\t\t\t\t\t\t\t.then(function(resp){\n\t\t\t\t\t\t\t\t\tobj.description = resp.data.result[0].description;\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\treturn obj;\n\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t\t\n            }\n        });\n    c.toggleNotification = function(active, channel_id, notification_id, sys_id, table) {\n        var data = {};\n        data.active = active;\n        data.channel_id = channel_id;\n        data.notification_id = notification_id;\n        data.sys_id = (sys_id != '') ? sys_id : null;\n        data.table = table;\n        $http.post(baseApiUrl, {\n                preference: data\n            })\n            .then(function(response) {\n            });\n    };\n};\n\n//CSS part of the widget\n\n.list-group-item {\n\tborder: none;\n  \n\t.btn-link {\n\t  padding-left: 0;\n\t  padding-right: 0;\n\t}\n  }\n  .input-switch{\n\tdisplay: inline-block;\n\tfloat: right;\n\n\n// HTML part of the widget\n\n<div class=\"notification-content\">\n  <div class=\"panel panel-default\">\n    <div class=\"panel-heading\">\n      <h2 class=\"panel-title\">\n        <i class=\"fa fa-envelope-o m-r-sm\"></i>${Notifications}\n      </h2>\n    </div>\n    <div class=\"body padder-xs\">\n      <div class=\"list-group\">\n        <div class=\"list-group-item\" ng-repeat='notification in c.notificationArray'>\n          <label><strong>{{notification.name}}</strong></label>\n          <div class=\"input-switch\">\n            <input aria-labelledby=\"switch_label_{{notification.notification.sys_id}}\" \n               id=\"switch{{notification.notification.sys_id}}\" \n               type=\"checkbox\" \n               name=\"switch{{notification.notification.sys_id}}\" \n               data-ng-model=\"notification.records[0].logical_active\" \n               data-ng-change=\"c.toggleNotification(notification.records[0].logical_active,notification.records[0].channel.sys_id,notification.notification.sys_id,notification.records[0].sys_id,notification.records[0].table)\" \n               data-ng-disabled=\"notification.records[0].readonly\" >\n        <label aria-hidden=\"true\" class=\"switch\" for=\"switch{{notification.notification.sys_id}}\">&#8203;</label>\n          </div>\n          <div class='description' ng-bind-html='::notification.description'>\n          </div>\n        </div>\n      </div>\n    </div>\n  </div>\n</div>\n\n//Server side script of the widget\n\n(function() {\n\tdata.category = options.categoryid || 'b69d02137f232200ee2e108c3ffa9142'; // sys id of the category\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Change Notification Preferences/server.js",
    "content": "(function() {\n\tdata.category = options.categoryid || 'b69d02137f232200ee2e108c3ffa9142'; // sys id of the category\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Check if user has specific role inside the widget/ClientScript.js",
    "content": "// Inject glideUserSession so we can check the exact role using\n// Note this is widget Client code\n\nfunction(glideUserSession) {\n    var c = this;\n\n    c.userHasApproverRole = true;\n\n    glideUserSession.loadCurrentUser().then(function (currentUser) {\n        //To check current user has specified role only. Equivalent of g_user.hasRoleExactly('approver_user');\n        c.userHasApproverRole = currentUser.hasRoleExactly('approver_user');\n    });\n\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Check if user has specific role inside the widget/README.md",
    "content": "# Check if user has a role within the widget code\n\nUse this code to check inside widget client code to see if user has a role or not. Can be used in scenarios where you want to hide to show part of the widget UI.\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Clickable SVG Image/README.md",
    "content": "# Can clickable svg image\n## main used echarts.js\n\n\n## *step1*\nPrepare a svg image, focus, you need set name for every root you want to click from svg image. there is a example svg image source code.\n```html\n<g name=\"7G\" id=\"g19657\">\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 225.8,250.9 h -11.9 c -1.7,0 -3.1,-1.4 -3.1,-3.1 V 239 c 0,-1.7 1.4,-3.1 3.1,-3.1 h 11.9 c 1.7,0 3.1,1.4 3.1,3.1 v 8.9 c -0.1,1.6 -1.4,3 -3.1,3 z\" id=\"path19653\" />\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 231.4,248.3 h -2.6 v -9.8 h 2.6 c 0.9,0 1.6,0.7 1.6,1.6 v 6.5 c 0.1,0.9 -0.7,1.7 -1.6,1.7 z\" id=\"path19655\" />\n</g>\n\n<g name=\"8G\" id=\"g19663\">\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 256,250.9 h -11.9 c -1.7,0 -3.1,-1.4 -3.1,-3.1 V 239 c 0,-1.7 1.4,-3.1 3.1,-3.1 H 256 c 1.7,0 3.1,1.4 3.1,3.1 v 8.9 c 0,1.6 -1.4,3 -3.1,3 z\" id=\"path19659\" />\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 261.7,248.3 h -2.6 v -9.8 h 2.6 c 0.9,0 1.6,0.7 1.6,1.6 v 6.5 c 0,0.9 -0.7,1.7 -1.6,1.7 z\" id=\"path19661\" />\n</g>\n\n<g name=\"9G\" id=\"g19669\">\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 286.2,250.9 h -11.9 c -1.7,0 -3.1,-1.4 -3.1,-3.1 V 239 c 0,-1.7 1.4,-3.1 3.1,-3.1 h 11.9 c 1.7,0 3.1,1.4 3.1,3.1 v 8.9 c 0,1.6 -1.4,3 -3.1,3 z\" id=\"path19665\" />\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 291.9,248.3 h -2.6 v -9.8 h 2.6 c 0.9,0 1.6,0.7 1.6,1.6 v 6.5 c 0.1,0.9 -0.7,1.7 -1.6,1.7 z\" id=\"path19667\" />\n</g>\n\n<g name=\"10G\" id=\"g19675\">\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 316.5,250.9 h -11.9 c -1.7,0 -3.1,-1.4 -3.1,-3.1 V 239 c 0,-1.7 1.4,-3.1 3.1,-3.1 h 11.9 c 1.7,0 3.1,1.4 3.1,3.1 v 8.9 c 0,1.6 -1.4,3 -3.1,3 z\" id=\"path19671\" />\n    <path fill=\"#cccccc\" stroke=\"#000000\" d=\"m 322.1,248.3 h -2.6 v -9.8 h 2.6 c 0.9,0 1.6,0.7 1.6,1.6 v 6.5 c 0.1,0.9 -0.6,1.7 -1.6,1.7 z\" id=\"path19673\" />\n</g>\n\n```\nJust focus the g tag, every set a unique name.\n\n## *step2*\nGo to your instance and direct to *System UI > Images*， upload your svg image here, and remember the name you typed.\n\n## *step3*\nUing the image in your widget.\n```javascript\n$.get('example.svg', function(svg) {\n\n\t\techarts.registerMap('demo', {\n\t\t\tsvg: svg\n\t\t});\n\n\t\tvar takenSeatNames = ['B2-001','B2-020','B2-003','B2-009','B2-031'];\n\n\t\toption = {\n\t\t\ttooltip: {},\n\t\t\tgeo: {\n\t\t\t\tmap: 'demo',\n\t\t\t\troam: true,\n\t\t\t\tselectedMode: 'single',\n\t\t\t\tlayoutCenter: ['50%', '50%'],\n\t\t\t\tlayoutSize: '95%',\n\t\t\t\ttooltip: {\n\t\t\t\t\tshow: true\n\t\t\t\t},\n\t\t\t\titemStyle: {\n\t\t\t\t\tcolor: '#fff'\n\t\t\t\t},\n\t\t\t\temphasis: {\n\t\t\t\t\titemStyle: {\n\t\t\t\t\t\tcolor: null,\n\t\t\t\t\t\tborderColor: 'green',\n\t\t\t\t\t\tborderWidth: 2\n\t\t\t\t\t},\n\t\t\t\t\tlabel: {\n\t\t\t\t\t\tshow: false\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tselect: {\n\t\t\t\t\titemStyle: {\n\t\t\t\t\t\t//color: '#fff'\n\t\t\t\t\t},\n\t\t\t\t\tlabel: {\n\t\t\t\t\t\tshow: false,\n\t\t\t\t\t\ttextBorderColor: '#fff',\n\t\t\t\t\t\ttextBorderWidth: 2\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tregions: makeTakenRegions(takenSeatNames)\n\t\t\t}\n\t\t};\n\n\t\tfunction makeTakenRegions(takenSeatNames) {\n\t\t\tvar regions = [];\n\t\t\tfor (var i = 0; i < takenSeatNames.length; i++) {\n\t\t\t\tregions.push({\n\t\t\t\t\tname: takenSeatNames[i],\n\t\t\t\t\tsilent: true,\n\t\t\t\t\titemStyle: {\n\t\t\t\t\t\tcolor: '#bf0e08'\n\t\t\t\t\t},\n\t\t\t\t\temphasis: {\n\t\t\t\t\t\titemStyle: {\n\t\t\t\t\t\t\tborderColor: '#aaa',\n\t\t\t\t\t\t\tborderWidth: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tselect: {\n\t\t\t\t\t\titemStyle: {\n\t\t\t\t\t\t\t//color: '#bf0e08'\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn regions;\n\t\t}\n```\n\n## *step4*\nadd echarts.js to your widget dependencies.\n\n# Now you can use it.\n\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Clickable SVG Image/client.js",
    "content": "api.controller = function() {\n\t/* widget controller */\n\tvar c = this;\n\n\tvar chartDom = document.getElementById('main');\n\tvar myChart = echarts.init(chartDom);\n\tvar option = {};\n\n\n\t$.get('example.svg', function(svg) {\n\n\t\techarts.registerMap('demo', {\n\t\t\tsvg: svg\n\t\t});\n\n\t\tvar takenSeatNames = ['B2-001','B2-020','B2-003','B2-009','B2-031'];\n\n\t\toption = {\n\t\t\ttooltip: {},\n\t\t\tgeo: {\n\t\t\t\tmap: 'demo',\n\t\t\t\troam: true,\n\t\t\t\tselectedMode: 'single',\n\t\t\t\tlayoutCenter: ['50%', '50%'],\n\t\t\t\tlayoutSize: '95%',\n\t\t\t\ttooltip: {\n\t\t\t\t\tshow: true\n\t\t\t\t},\n\t\t\t\titemStyle: {\n\t\t\t\t\tcolor: '#fff'\n\t\t\t\t},\n\t\t\t\temphasis: {\n\t\t\t\t\titemStyle: {\n\t\t\t\t\t\tcolor: null,\n\t\t\t\t\t\tborderColor: 'green',\n\t\t\t\t\t\tborderWidth: 2\n\t\t\t\t\t},\n\t\t\t\t\tlabel: {\n\t\t\t\t\t\tshow: false\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tselect: {\n\t\t\t\t\titemStyle: {\n\t\t\t\t\t\t//color: '#fff'\n\t\t\t\t\t},\n\t\t\t\t\tlabel: {\n\t\t\t\t\t\tshow: false,\n\t\t\t\t\t\ttextBorderColor: '#fff',\n\t\t\t\t\t\ttextBorderWidth: 2\n\t\t\t\t\t}\n\t\t\t\t},\n\t\t\t\tregions: makeTakenRegions(takenSeatNames)\n\t\t\t}\n\t\t};\n\n\t\tfunction makeTakenRegions(takenSeatNames) {\n\t\t\tvar regions = [];\n\t\t\tfor (var i = 0; i < takenSeatNames.length; i++) {\n\t\t\t\tregions.push({\n\t\t\t\t\tname: takenSeatNames[i],\n\t\t\t\t\tsilent: true,\n\t\t\t\t\titemStyle: {\n\t\t\t\t\t\tcolor: '#bf0e08'\n\t\t\t\t\t},\n\t\t\t\t\temphasis: {\n\t\t\t\t\t\titemStyle: {\n\t\t\t\t\t\t\tborderColor: '#aaa',\n\t\t\t\t\t\t\tborderWidth: 1\n\t\t\t\t\t\t}\n\t\t\t\t\t},\n\t\t\t\t\tselect: {\n\t\t\t\t\t\titemStyle: {\n\t\t\t\t\t\t\t//color: '#bf0e08'\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t\treturn regions;\n\t\t}\n\n\t\tmyChart.setOption(option);\n\n\t\t// Get selected\n\t\tmyChart.on('geoselectchanged', function(params) {\n\t\t\tvar selectedNames = params.allSelected[0].name.slice();\n\n\t\t\t//Remove taken\n\t\t\tfor (var i = selectedNames.length - 1; i >= 0; i--) {\n\t\t\t\tif (takenSeatNames.indexOf(selectedNames[i]) >= 0) {\n\t\t\t\t\tselectedNames.splice(i, 1);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t});\n};"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Clickable SVG Image/echarts.js",
    "content": "\n/*\n* Licensed to the Apache Software Foundation (ASF) under one\n* or more contributor license agreements.  See the NOTICE file\n* distributed with this work for additional information\n* regarding copyright ownership.  The ASF licenses this file\n* to you under the Apache License, Version 2.0 (the\n* \"License\"); you may not use this file except in compliance\n* with the License.  You may obtain a copy of the License at\n*\n*   http://www.apache.org/licenses/LICENSE-2.0\n*\n* Unless required by applicable law or agreed to in writing,\n* software distributed under the License is distributed on an\n* \"AS IS\" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY\n* KIND, either express or implied.  See the License for the\n* specific language governing permissions and limitations\n* under the License.\n*/\n\n!function(t,e){\"object\"==typeof exports&&\"undefined\"!=typeof module?e(exports):\"function\"==typeof define&&define.amd?define([\"exports\"],e):e((t=\"undefined\"!=typeof globalThis?globalThis:t||self).echarts={})}(this,(function(t){\"use strict\";\n/*! *****************************************************************************\n    Copyright (c) Microsoft Corporation.\n\n    Permission to use, copy, modify, and/or distribute this software for any\n    purpose with or without fee is hereby granted.\n\n    THE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\n    REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\n    AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\n    INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\n    LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\n    OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\n    PERFORMANCE OF THIS SOFTWARE.\n    ***************************************************************************** */var e=function(t,n){return(e=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(t,e){t.__proto__=e}||function(t,e){for(var n in e)Object.prototype.hasOwnProperty.call(e,n)&&(t[n]=e[n])})(t,n)};function n(t,n){function i(){this.constructor=t}e(t,n),t.prototype=null===n?Object.create(n):(i.prototype=n.prototype,new i)}var i=function(){return(i=Object.assign||function(t){for(var e,n=1,i=arguments.length;n<i;n++)for(var r in e=arguments[n])Object.prototype.hasOwnProperty.call(e,r)&&(t[r]=e[r]);return t}).apply(this,arguments)};function r(){for(var t=0,e=0,n=arguments.length;e<n;e++)t+=arguments[e].length;var i=Array(t),r=0;for(e=0;e<n;e++)for(var o=arguments[e],a=0,s=o.length;a<s;a++,r++)i[r]=o[a];return i}var o=function(){this.firefox=!1,this.ie=!1,this.edge=!1,this.newEdge=!1,this.weChat=!1},a=new function(){this.browser=new o,this.node=!1,this.wxa=!1,this.worker=!1,this.canvasSupported=!1,this.svgSupported=!1,this.touchEventsSupported=!1,this.pointerEventsSupported=!1,this.domSupported=!1,this.transformSupported=!1,this.transform3dSupported=!1};\"object\"==typeof wx&&\"function\"==typeof wx.getSystemInfoSync?(a.wxa=!0,a.canvasSupported=!0,a.touchEventsSupported=!0):\"undefined\"==typeof document&&\"undefined\"!=typeof self?(a.worker=!0,a.canvasSupported=!0):\"undefined\"==typeof navigator?(a.node=!0,a.canvasSupported=!0,a.svgSupported=!0):function(t,e){var n=e.browser,i=t.match(/Firefox\\/([\\d.]+)/),r=t.match(/MSIE\\s([\\d.]+)/)||t.match(/Trident\\/.+?rv:(([\\d.]+))/),o=t.match(/Edge?\\/([\\d.]+)/),a=/micromessenger/i.test(t);i&&(n.firefox=!0,n.version=i[1]);r&&(n.ie=!0,n.version=r[1]);o&&(n.edge=!0,n.version=o[1],n.newEdge=+o[1].split(\".\")[0]>18);a&&(n.weChat=!0);e.canvasSupported=!!document.createElement(\"canvas\").getContext,e.svgSupported=\"undefined\"!=typeof SVGRect,e.touchEventsSupported=\"ontouchstart\"in window&&!n.ie&&!n.edge,e.pointerEventsSupported=\"onpointerdown\"in window&&(n.edge||n.ie&&+n.version>=11),e.domSupported=\"undefined\"!=typeof document;var s=document.documentElement.style;e.transform3dSupported=(n.ie&&\"transition\"in s||n.edge||\"WebKitCSSMatrix\"in window&&\"m11\"in new WebKitCSSMatrix||\"MozPerspective\"in s)&&!(\"OTransition\"in s),e.transformSupported=e.transform3dSupported||n.ie&&+n.version>=9}(navigator.userAgent,a);var s={\"[object Function]\":!0,\"[object RegExp]\":!0,\"[object Date]\":!0,\"[object Error]\":!0,\"[object CanvasGradient]\":!0,\"[object CanvasPattern]\":!0,\"[object Image]\":!0,\"[object Canvas]\":!0},l={\"[object Int8Array]\":!0,\"[object Uint8Array]\":!0,\"[object Uint8ClampedArray]\":!0,\"[object Int16Array]\":!0,\"[object Uint16Array]\":!0,\"[object Int32Array]\":!0,\"[object Uint32Array]\":!0,\"[object Float32Array]\":!0,\"[object Float64Array]\":!0},u=Object.prototype.toString,h=Array.prototype,c=h.forEach,p=h.filter,d=h.slice,f=h.map,g=function(){}.constructor,y=g?g.prototype:null,v={};function m(t,e){v[t]=e}var _=2311;function x(){return _++}function b(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];\"undefined\"!=typeof console&&console.error.apply(console,t)}function w(t){if(null==t||\"object\"!=typeof t)return t;var e=t,n=u.call(t);if(\"[object Array]\"===n){if(!lt(t)){e=[];for(var i=0,r=t.length;i<r;i++)e[i]=w(t[i])}}else if(l[n]){if(!lt(t)){var o=t.constructor;if(o.from)e=o.from(t);else{e=new o(t.length);for(i=0,r=t.length;i<r;i++)e[i]=w(t[i])}}}else if(!s[n]&&!lt(t)&&!j(t))for(var a in e={},t)t.hasOwnProperty(a)&&(e[a]=w(t[a]));return e}function S(t,e,n){if(!X(e)||!X(t))return n?w(e):t;for(var i in e)if(e.hasOwnProperty(i)){var r=t[i],o=e[i];!X(o)||!X(r)||F(o)||F(r)||j(o)||j(r)||Y(o)||Y(r)||lt(o)||lt(r)?!n&&i in t||(t[i]=w(e[i])):S(r,o,n)}return t}function M(t,e){for(var n=t[0],i=1,r=t.length;i<r;i++)n=S(n,t[i],e);return n}function I(t,e){if(Object.assign)Object.assign(t,e);else for(var n in e)e.hasOwnProperty(n)&&(t[n]=e[n]);return t}function T(t,e,n){for(var i=E(e),r=0;r<i.length;r++){var o=i[r];(n?null!=e[o]:null==t[o])&&(t[o]=e[o])}return t}var C=function(){return v.createCanvas()};function D(t,e){if(t){if(t.indexOf)return t.indexOf(e);for(var n=0,i=t.length;n<i;n++)if(t[n]===e)return n}return-1}function A(t,e){var n=t.prototype;function i(){}for(var r in i.prototype=e.prototype,t.prototype=new i,n)n.hasOwnProperty(r)&&(t.prototype[r]=n[r]);t.prototype.constructor=t,t.superClass=e}function L(t,e,n){if(t=\"prototype\"in t?t.prototype:t,e=\"prototype\"in e?e.prototype:e,Object.getOwnPropertyNames)for(var i=Object.getOwnPropertyNames(e),r=0;r<i.length;r++){var o=i[r];\"constructor\"!==o&&(n?null!=e[o]:null==t[o])&&(t[o]=e[o])}else T(t,e,n)}function k(t){return!!t&&(\"string\"!=typeof t&&\"number\"==typeof t.length)}function P(t,e,n){if(t&&e)if(t.forEach&&t.forEach===c)t.forEach(e,n);else if(t.length===+t.length)for(var i=0,r=t.length;i<r;i++)e.call(n,t[i],i,t);else for(var o in t)t.hasOwnProperty(o)&&e.call(n,t[o],o,t)}function O(t,e,n){if(!t)return[];if(!e)return nt(t);if(t.map&&t.map===f)return t.map(e,n);for(var i=[],r=0,o=t.length;r<o;r++)i.push(e.call(n,t[r],r,t));return i}function R(t,e,n,i){if(t&&e){for(var r=0,o=t.length;r<o;r++)n=e.call(i,n,t[r],r,t);return n}}function N(t,e,n){if(!t)return[];if(!e)return nt(t);if(t.filter&&t.filter===p)return t.filter(e,n);for(var i=[],r=0,o=t.length;r<o;r++)e.call(n,t[r],r,t)&&i.push(t[r]);return i}function z(t,e,n){if(t&&e)for(var i=0,r=t.length;i<r;i++)if(e.call(n,t[i],i,t))return t[i]}function E(t){if(!t)return[];if(Object.keys)return Object.keys(t);var e=[];for(var n in t)t.hasOwnProperty(n)&&e.push(n);return e}v.createCanvas=function(){return document.createElement(\"canvas\")};var V=y&&G(y.bind)?y.call.bind(y.bind):function(t,e){for(var n=[],i=2;i<arguments.length;i++)n[i-2]=arguments[i];return function(){return t.apply(e,n.concat(d.call(arguments)))}};function B(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];return function(){return t.apply(this,e.concat(d.call(arguments)))}}function F(t){return Array.isArray?Array.isArray(t):\"[object Array]\"===u.call(t)}function G(t){return\"function\"==typeof t}function H(t){return\"string\"==typeof t}function W(t){return\"[object String]\"===u.call(t)}function U(t){return\"number\"==typeof t}function X(t){var e=typeof t;return\"function\"===e||!!t&&\"object\"===e}function Y(t){return!!s[u.call(t)]}function Z(t){return!!l[u.call(t)]}function j(t){return\"object\"==typeof t&&\"number\"==typeof t.nodeType&&\"object\"==typeof t.ownerDocument}function q(t){return null!=t.colorStops}function K(t){return null!=t.image}function $(t){return\"[object RegExp]\"===u.call(t)}function J(t){return t!=t}function Q(){for(var t=[],e=0;e<arguments.length;e++)t[e]=arguments[e];for(var n=0,i=t.length;n<i;n++)if(null!=t[n])return t[n]}function tt(t,e){return null!=t?t:e}function et(t,e,n){return null!=t?t:null!=e?e:n}function nt(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];return d.apply(t,e)}function it(t){if(\"number\"==typeof t)return[t,t,t,t];var e=t.length;return 2===e?[t[0],t[1],t[0],t[1]]:3===e?[t[0],t[1],t[2],t[1]]:t}function rt(t,e){if(!t)throw new Error(e)}function ot(t){return null==t?null:\"function\"==typeof t.trim?t.trim():t.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\"\")}var at=\"__ec_primitive__\";function st(t){t[at]=!0}function lt(t){return t[at]}var ut=function(){function t(e){this.data={};var n=F(e);this.data={};var i=this;function r(t,e){n?i.set(t,e):i.set(e,t)}e instanceof t?e.each(r):e&&P(e,r)}return t.prototype.get=function(t){return this.data.hasOwnProperty(t)?this.data[t]:null},t.prototype.set=function(t,e){return this.data[t]=e},t.prototype.each=function(t,e){for(var n in this.data)this.data.hasOwnProperty(n)&&t.call(e,this.data[n],n)},t.prototype.keys=function(){return E(this.data)},t.prototype.removeKey=function(t){delete this.data[t]},t}();function ht(t){return new ut(t)}function ct(t,e){for(var n=new t.constructor(t.length+e.length),i=0;i<t.length;i++)n[i]=t[i];var r=t.length;for(i=0;i<e.length;i++)n[i+r]=e[i];return n}function pt(t,e){var n;if(Object.create)n=Object.create(t);else{var i=function(){};i.prototype=t,n=new i}return e&&I(n,e),n}function dt(t,e){return t.hasOwnProperty(e)}function ft(){}var gt=Object.freeze({__proto__:null,$override:m,guid:x,logError:b,clone:w,merge:S,mergeAll:M,extend:I,defaults:T,createCanvas:C,indexOf:D,inherits:A,mixin:L,isArrayLike:k,each:P,map:O,reduce:R,filter:N,find:z,keys:E,bind:V,curry:B,isArray:F,isFunction:G,isString:H,isStringSafe:W,isNumber:U,isObject:X,isBuiltInObject:Y,isTypedArray:Z,isDom:j,isGradientObject:q,isImagePatternObject:K,isRegExp:$,eqNaN:J,retrieve:Q,retrieve2:tt,retrieve3:et,slice:nt,normalizeCssArray:it,assert:rt,trim:ot,setAsPrimitive:st,isPrimitive:lt,HashMap:ut,createHashMap:ht,concatArray:ct,createObject:pt,hasOwn:dt,noop:ft});function yt(t,e){return null==t&&(t=0),null==e&&(e=0),[t,e]}function vt(t,e){return t[0]=e[0],t[1]=e[1],t}function mt(t){return[t[0],t[1]]}function _t(t,e,n){return t[0]=e,t[1]=n,t}function xt(t,e,n){return t[0]=e[0]+n[0],t[1]=e[1]+n[1],t}function bt(t,e,n,i){return t[0]=e[0]+n[0]*i,t[1]=e[1]+n[1]*i,t}function wt(t,e,n){return t[0]=e[0]-n[0],t[1]=e[1]-n[1],t}function St(t){return Math.sqrt(It(t))}var Mt=St;function It(t){return t[0]*t[0]+t[1]*t[1]}var Tt=It;function Ct(t,e,n){return t[0]=e[0]*n,t[1]=e[1]*n,t}function Dt(t,e){var n=St(e);return 0===n?(t[0]=0,t[1]=0):(t[0]=e[0]/n,t[1]=e[1]/n),t}function At(t,e){return Math.sqrt((t[0]-e[0])*(t[0]-e[0])+(t[1]-e[1])*(t[1]-e[1]))}var Lt=At;function kt(t,e){return(t[0]-e[0])*(t[0]-e[0])+(t[1]-e[1])*(t[1]-e[1])}var Pt=kt;function Ot(t,e,n,i){return t[0]=e[0]+i*(n[0]-e[0]),t[1]=e[1]+i*(n[1]-e[1]),t}function Rt(t,e,n){var i=e[0],r=e[1];return t[0]=n[0]*i+n[2]*r+n[4],t[1]=n[1]*i+n[3]*r+n[5],t}function Nt(t,e,n){return t[0]=Math.min(e[0],n[0]),t[1]=Math.min(e[1],n[1]),t}function zt(t,e,n){return t[0]=Math.max(e[0],n[0]),t[1]=Math.max(e[1],n[1]),t}var Et=Object.freeze({__proto__:null,create:yt,copy:vt,clone:mt,set:_t,add:xt,scaleAndAdd:bt,sub:wt,len:St,length:Mt,lenSquare:It,lengthSquare:Tt,mul:function(t,e,n){return t[0]=e[0]*n[0],t[1]=e[1]*n[1],t},div:function(t,e,n){return t[0]=e[0]/n[0],t[1]=e[1]/n[1],t},dot:function(t,e){return t[0]*e[0]+t[1]*e[1]},scale:Ct,normalize:Dt,distance:At,dist:Lt,distanceSquare:kt,distSquare:Pt,negate:function(t,e){return t[0]=-e[0],t[1]=-e[1],t},lerp:Ot,applyTransform:Rt,min:Nt,max:zt}),Vt=function(t,e){this.target=t,this.topTarget=e&&e.topTarget},Bt=function(){function t(t){this.handler=t,t.on(\"mousedown\",this._dragStart,this),t.on(\"mousemove\",this._drag,this),t.on(\"mouseup\",this._dragEnd,this)}return t.prototype._dragStart=function(t){for(var e=t.target;e&&!e.draggable;)e=e.parent;e&&(this._draggingTarget=e,e.dragging=!0,this._x=t.offsetX,this._y=t.offsetY,this.handler.dispatchToElement(new Vt(e,t),\"dragstart\",t.event))},t.prototype._drag=function(t){var e=this._draggingTarget;if(e){var n=t.offsetX,i=t.offsetY,r=n-this._x,o=i-this._y;this._x=n,this._y=i,e.drift(r,o,t),this.handler.dispatchToElement(new Vt(e,t),\"drag\",t.event);var a=this.handler.findHover(n,i,e).target,s=this._dropTarget;this._dropTarget=a,e!==a&&(s&&a!==s&&this.handler.dispatchToElement(new Vt(s,t),\"dragleave\",t.event),a&&a!==s&&this.handler.dispatchToElement(new Vt(a,t),\"dragenter\",t.event))}},t.prototype._dragEnd=function(t){var e=this._draggingTarget;e&&(e.dragging=!1),this.handler.dispatchToElement(new Vt(e,t),\"dragend\",t.event),this._dropTarget&&this.handler.dispatchToElement(new Vt(this._dropTarget,t),\"drop\",t.event),this._draggingTarget=null,this._dropTarget=null},t}(),Ft=function(){function t(t){t&&(this._$eventProcessor=t)}return t.prototype.on=function(t,e,n,i){this._$handlers||(this._$handlers={});var r=this._$handlers;if(\"function\"==typeof e&&(i=n,n=e,e=null),!n||!t)return this;var o=this._$eventProcessor;null!=e&&o&&o.normalizeQuery&&(e=o.normalizeQuery(e)),r[t]||(r[t]=[]);for(var a=0;a<r[t].length;a++)if(r[t][a].h===n)return this;var s={h:n,query:e,ctx:i||this,callAtLast:n.zrEventfulCallAtLast},l=r[t].length-1,u=r[t][l];return u&&u.callAtLast?r[t].splice(l,0,s):r[t].push(s),this},t.prototype.isSilent=function(t){var e=this._$handlers;return!e||!e[t]||!e[t].length},t.prototype.off=function(t,e){var n=this._$handlers;if(!n)return this;if(!t)return this._$handlers={},this;if(e){if(n[t]){for(var i=[],r=0,o=n[t].length;r<o;r++)n[t][r].h!==e&&i.push(n[t][r]);n[t]=i}n[t]&&0===n[t].length&&delete n[t]}else delete n[t];return this},t.prototype.trigger=function(t){for(var e=[],n=1;n<arguments.length;n++)e[n-1]=arguments[n];if(!this._$handlers)return this;var i=this._$handlers[t],r=this._$eventProcessor;if(i)for(var o=e.length,a=i.length,s=0;s<a;s++){var l=i[s];if(!r||!r.filter||null==l.query||r.filter(t,l.query))switch(o){case 0:l.h.call(l.ctx);break;case 1:l.h.call(l.ctx,e[0]);break;case 2:l.h.call(l.ctx,e[0],e[1]);break;default:l.h.apply(l.ctx,e)}}return r&&r.afterTrigger&&r.afterTrigger(t),this},t.prototype.triggerWithContext=function(t){if(!this._$handlers)return this;var e=this._$handlers[t],n=this._$eventProcessor;if(e)for(var i=arguments,r=i.length,o=i[r-1],a=e.length,s=0;s<a;s++){var l=e[s];if(!n||!n.filter||null==l.query||n.filter(t,l.query))switch(r){case 0:l.h.call(o);break;case 1:l.h.call(o,i[0]);break;case 2:l.h.call(o,i[0],i[1]);break;default:l.h.apply(o,i.slice(1,r-1))}}return n&&n.afterTrigger&&n.afterTrigger(t),this},t}(),Gt=Math.log(2);function Ht(t,e,n,i,r,o){var a=i+\"-\"+r,s=t.length;if(o.hasOwnProperty(a))return o[a];if(1===e){var l=Math.round(Math.log((1<<s)-1&~r)/Gt);return t[n][l]}for(var u=i|1<<n,h=n+1;i&1<<h;)h++;for(var c=0,p=0,d=0;p<s;p++){var f=1<<p;f&r||(c+=(d%2?-1:1)*t[n][p]*Ht(t,e-1,h,u,r|f,o),d++)}return o[a]=c,c}function Wt(t,e){var n=[[t[0],t[1],1,0,0,0,-e[0]*t[0],-e[0]*t[1]],[0,0,0,t[0],t[1],1,-e[1]*t[0],-e[1]*t[1]],[t[2],t[3],1,0,0,0,-e[2]*t[2],-e[2]*t[3]],[0,0,0,t[2],t[3],1,-e[3]*t[2],-e[3]*t[3]],[t[4],t[5],1,0,0,0,-e[4]*t[4],-e[4]*t[5]],[0,0,0,t[4],t[5],1,-e[5]*t[4],-e[5]*t[5]],[t[6],t[7],1,0,0,0,-e[6]*t[6],-e[6]*t[7]],[0,0,0,t[6],t[7],1,-e[7]*t[6],-e[7]*t[7]]],i={},r=Ht(n,8,0,0,0,i);if(0!==r){for(var o=[],a=0;a<8;a++)for(var s=0;s<8;s++)null==o[s]&&(o[s]=0),o[s]+=((a+s)%2?-1:1)*Ht(n,7,0===a?1:0,1<<a,1<<s,i)/r*e[a];return function(t,e,n){var i=e*o[6]+n*o[7]+1;t[0]=(e*o[0]+n*o[1]+o[2])/i,t[1]=(e*o[3]+n*o[4]+o[5])/i}}}var Ut=[];function Xt(t,e,n,i,r){if(e.getBoundingClientRect&&a.domSupported&&!Yt(e)){var o=e.___zrEVENTSAVED||(e.___zrEVENTSAVED={}),s=function(t,e,n){for(var i=n?\"invTrans\":\"trans\",r=e[i],o=e.srcCoords,a=[],s=[],l=!0,u=0;u<4;u++){var h=t[u].getBoundingClientRect(),c=2*u,p=h.left,d=h.top;a.push(p,d),l=l&&o&&p===o[c]&&d===o[c+1],s.push(t[u].offsetLeft,t[u].offsetTop)}return l&&r?r:(e.srcCoords=a,e[i]=n?Wt(s,a):Wt(a,s))}(function(t,e){var n=e.markers;if(n)return n;n=e.markers=[];for(var i=[\"left\",\"right\"],r=[\"top\",\"bottom\"],o=0;o<4;o++){var a=document.createElement(\"div\"),s=o%2,l=(o>>1)%2;a.style.cssText=[\"position: absolute\",\"visibility: hidden\",\"padding: 0\",\"margin: 0\",\"border-width: 0\",\"user-select: none\",\"width:0\",\"height:0\",i[s]+\":0\",r[l]+\":0\",i[1-s]+\":auto\",r[1-l]+\":auto\",\"\"].join(\"!important;\"),t.appendChild(a),n.push(a)}return n}(e,o),o,r);if(s)return s(t,n,i),!0}return!1}function Yt(t){return\"CANVAS\"===t.nodeName.toUpperCase()}var Zt=\"undefined\"!=typeof window&&!!window.addEventListener,jt=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,qt=[];function Kt(t,e,n,i){return n=n||{},i||!a.canvasSupported?$t(t,e,n):a.browser.firefox&&null!=e.layerX&&e.layerX!==e.offsetX?(n.zrX=e.layerX,n.zrY=e.layerY):null!=e.offsetX?(n.zrX=e.offsetX,n.zrY=e.offsetY):$t(t,e,n),n}function $t(t,e,n){if(a.domSupported&&t.getBoundingClientRect){var i=e.clientX,r=e.clientY;if(Yt(t)){var o=t.getBoundingClientRect();return n.zrX=i-o.left,void(n.zrY=r-o.top)}if(Xt(qt,t,i,r))return n.zrX=qt[0],void(n.zrY=qt[1])}n.zrX=n.zrY=0}function Jt(t){return t||window.event}function Qt(t,e,n){if(null!=(e=Jt(e)).zrX)return e;var i=e.type;if(i&&i.indexOf(\"touch\")>=0){var r=\"touchend\"!==i?e.targetTouches[0]:e.changedTouches[0];r&&Kt(t,r,e,n)}else{Kt(t,e,e,n);var o=function(t){var e=t.wheelDelta;if(e)return e;var n=t.deltaX,i=t.deltaY;if(null==n||null==i)return e;return 3*(0!==i?Math.abs(i):Math.abs(n))*(i>0?-1:i<0?1:n>0?-1:1)}(e);e.zrDelta=o?o/120:-(e.detail||0)/3}var a=e.button;return null==e.which&&void 0!==a&&jt.test(e.type)&&(e.which=1&a?1:2&a?3:4&a?2:0),e}function te(t,e,n,i){Zt?t.addEventListener(e,n,i):t.attachEvent(\"on\"+e,n)}var ee=Zt?function(t){t.preventDefault(),t.stopPropagation(),t.cancelBubble=!0}:function(t){t.returnValue=!1,t.cancelBubble=!0};function ne(t){return 2===t.which||3===t.which}var ie=function(){function t(){this._track=[]}return t.prototype.recognize=function(t,e,n){return this._doTrack(t,e,n),this._recognize(t)},t.prototype.clear=function(){return this._track.length=0,this},t.prototype._doTrack=function(t,e,n){var i=t.touches;if(i){for(var r={points:[],touches:[],target:e,event:t},o=0,a=i.length;o<a;o++){var s=i[o],l=Kt(n,s,{});r.points.push([l.zrX,l.zrY]),r.touches.push(s)}this._track.push(r)}},t.prototype._recognize=function(t){for(var e in oe)if(oe.hasOwnProperty(e)){var n=oe[e](this._track,t);if(n)return n}},t}();function re(t){var e=t[1][0]-t[0][0],n=t[1][1]-t[0][1];return Math.sqrt(e*e+n*n)}var oe={pinch:function(t,e){var n=t.length;if(n){var i,r=(t[n-1]||{}).points,o=(t[n-2]||{}).points||r;if(o&&o.length>1&&r&&r.length>1){var a=re(r)/re(o);!isFinite(a)&&(a=1),e.pinchScale=a;var s=[((i=r)[0][0]+i[1][0])/2,(i[0][1]+i[1][1])/2];return e.pinchX=s[0],e.pinchY=s[1],{type:\"pinch\",target:t[0].target,event:e}}}}},ae=\"silent\";function se(){ee(this.event)}var le=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.handler=null,e}return n(e,t),e.prototype.dispose=function(){},e.prototype.setCursor=function(){},e}(Ft),ue=function(t,e){this.x=t,this.y=e},he=[\"click\",\"dblclick\",\"mousewheel\",\"mouseout\",\"mouseup\",\"mousedown\",\"mousemove\",\"contextmenu\"],ce=function(t){function e(e,n,i,r){var o=t.call(this)||this;return o._hovered=new ue(0,0),o.storage=e,o.painter=n,o.painterRoot=r,i=i||new le,o.proxy=null,o.setHandlerProxy(i),o._draggingMgr=new Bt(o),o}return n(e,t),e.prototype.setHandlerProxy=function(t){this.proxy&&this.proxy.dispose(),t&&(P(he,(function(e){t.on&&t.on(e,this[e],this)}),this),t.handler=this),this.proxy=t},e.prototype.mousemove=function(t){var e=t.zrX,n=t.zrY,i=de(this,e,n),r=this._hovered,o=r.target;o&&!o.__zr&&(o=(r=this.findHover(r.x,r.y)).target);var a=this._hovered=i?new ue(e,n):this.findHover(e,n),s=a.target,l=this.proxy;l.setCursor&&l.setCursor(s?s.cursor:\"default\"),o&&s!==o&&this.dispatchToElement(r,\"mouseout\",t),this.dispatchToElement(a,\"mousemove\",t),s&&s!==o&&this.dispatchToElement(a,\"mouseover\",t)},e.prototype.mouseout=function(t){var e=t.zrEventControl;\"only_globalout\"!==e&&this.dispatchToElement(this._hovered,\"mouseout\",t),\"no_globalout\"!==e&&this.trigger(\"globalout\",{type:\"globalout\",event:t})},e.prototype.resize=function(){this._hovered=new ue(0,0)},e.prototype.dispatch=function(t,e){var n=this[t];n&&n.call(this,e)},e.prototype.dispose=function(){this.proxy.dispose(),this.storage=null,this.proxy=null,this.painter=null},e.prototype.setCursorStyle=function(t){var e=this.proxy;e.setCursor&&e.setCursor(t)},e.prototype.dispatchToElement=function(t,e,n){var i=(t=t||{}).target;if(!i||!i.silent){for(var r=\"on\"+e,o=function(t,e,n){return{type:t,event:n,target:e.target,topTarget:e.topTarget,cancelBubble:!1,offsetX:n.zrX,offsetY:n.zrY,gestureEvent:n.gestureEvent,pinchX:n.pinchX,pinchY:n.pinchY,pinchScale:n.pinchScale,wheelDelta:n.zrDelta,zrByTouch:n.zrByTouch,which:n.which,stop:se}}(e,t,n);i&&(i[r]&&(o.cancelBubble=!!i[r].call(i,o)),i.trigger(e,o),i=i.__hostTarget?i.__hostTarget:i.parent,!o.cancelBubble););o.cancelBubble||(this.trigger(e,o),this.painter&&this.painter.eachOtherLayer&&this.painter.eachOtherLayer((function(t){\"function\"==typeof t[r]&&t[r].call(t,o),t.trigger&&t.trigger(e,o)})))}},e.prototype.findHover=function(t,e,n){for(var i=this.storage.getDisplayList(),r=new ue(t,e),o=i.length-1;o>=0;o--){var a=void 0;if(i[o]!==n&&!i[o].ignore&&(a=pe(i[o],t,e))&&(!r.topTarget&&(r.topTarget=i[o]),a!==ae)){r.target=i[o];break}}return r},e.prototype.processGesture=function(t,e){this._gestureMgr||(this._gestureMgr=new ie);var n=this._gestureMgr;\"start\"===e&&n.clear();var i=n.recognize(t,this.findHover(t.zrX,t.zrY,null).target,this.proxy.dom);if(\"end\"===e&&n.clear(),i){var r=i.type;t.gestureEvent=r;var o=new ue;o.target=i.target,this.dispatchToElement(o,r,i.event)}},e}(Ft);function pe(t,e,n){if(t[t.rectHover?\"rectContain\":\"contain\"](e,n)){for(var i=t,r=void 0,o=!1;i;){if(i.ignoreClip&&(o=!0),!o){var a=i.getClipPath();if(a&&!a.contain(e,n))return!1;i.silent&&(r=!0)}var s=i.__hostTarget;i=s||i.parent}return!r||ae}return!1}function de(t,e,n){var i=t.painter;return e<0||e>i.getWidth()||n<0||n>i.getHeight()}P([\"click\",\"mousedown\",\"mouseup\",\"mousewheel\",\"dblclick\",\"contextmenu\"],(function(t){ce.prototype[t]=function(e){var n,i,r=e.zrX,o=e.zrY,a=de(this,r,o);if(\"mouseup\"===t&&a||(i=(n=this.findHover(r,o)).target),\"mousedown\"===t)this._downEl=i,this._downPoint=[e.zrX,e.zrY],this._upEl=i;else if(\"mouseup\"===t)this._upEl=i;else if(\"click\"===t){if(this._downEl!==this._upEl||!this._downPoint||Lt(this._downPoint,[e.zrX,e.zrY])>4)return;this._downPoint=null}this.dispatchToElement(n,t,e)}}));function fe(t,e,n,i){var r=e+1;if(r===n)return 1;if(i(t[r++],t[e])<0){for(;r<n&&i(t[r],t[r-1])<0;)r++;!function(t,e,n){n--;for(;e<n;){var i=t[e];t[e++]=t[n],t[n--]=i}}(t,e,r)}else for(;r<n&&i(t[r],t[r-1])>=0;)r++;return r-e}function ge(t,e,n,i,r){for(i===e&&i++;i<n;i++){for(var o,a=t[i],s=e,l=i;s<l;)r(a,t[o=s+l>>>1])<0?l=o:s=o+1;var u=i-s;switch(u){case 3:t[s+3]=t[s+2];case 2:t[s+2]=t[s+1];case 1:t[s+1]=t[s];break;default:for(;u>0;)t[s+u]=t[s+u-1],u--}t[s]=a}}function ye(t,e,n,i,r,o){var a=0,s=0,l=1;if(o(t,e[n+r])>0){for(s=i-r;l<s&&o(t,e[n+r+l])>0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),a+=r,l+=r}else{for(s=r+1;l<s&&o(t,e[n+r-l])<=0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s);var u=a;a=r-l,l=r-u}for(a++;a<l;){var h=a+(l-a>>>1);o(t,e[n+h])>0?a=h+1:l=h}return l}function ve(t,e,n,i,r,o){var a=0,s=0,l=1;if(o(t,e[n+r])<0){for(s=r+1;l<s&&o(t,e[n+r-l])<0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s);var u=a;a=r-l,l=r-u}else{for(s=i-r;l<s&&o(t,e[n+r+l])>=0;)a=l,(l=1+(l<<1))<=0&&(l=s);l>s&&(l=s),a+=r,l+=r}for(a++;a<l;){var h=a+(l-a>>>1);o(t,e[n+h])<0?l=h:a=h+1}return l}function me(t,e){var n,i,r=7,o=0;t.length;var a=[];function s(s){var l=n[s],u=i[s],h=n[s+1],c=i[s+1];i[s]=u+c,s===o-3&&(n[s+1]=n[s+2],i[s+1]=i[s+2]),o--;var p=ve(t[h],t,l,u,0,e);l+=p,0!==(u-=p)&&0!==(c=ye(t[l+u-1],t,h,c,c-1,e))&&(u<=c?function(n,i,o,s){var l=0;for(l=0;l<i;l++)a[l]=t[n+l];var u=0,h=o,c=n;if(t[c++]=t[h++],0==--s){for(l=0;l<i;l++)t[c+l]=a[u+l];return}if(1===i){for(l=0;l<s;l++)t[c+l]=t[h+l];return void(t[c+s]=a[u])}var p,d,f,g=r;for(;;){p=0,d=0,f=!1;do{if(e(t[h],a[u])<0){if(t[c++]=t[h++],d++,p=0,0==--s){f=!0;break}}else if(t[c++]=a[u++],p++,d=0,1==--i){f=!0;break}}while((p|d)<g);if(f)break;do{if(0!==(p=ve(t[h],a,u,i,0,e))){for(l=0;l<p;l++)t[c+l]=a[u+l];if(c+=p,u+=p,(i-=p)<=1){f=!0;break}}if(t[c++]=t[h++],0==--s){f=!0;break}if(0!==(d=ye(a[u],t,h,s,0,e))){for(l=0;l<d;l++)t[c+l]=t[h+l];if(c+=d,h+=d,0===(s-=d)){f=!0;break}}if(t[c++]=a[u++],1==--i){f=!0;break}g--}while(p>=7||d>=7);if(f)break;g<0&&(g=0),g+=2}if((r=g)<1&&(r=1),1===i){for(l=0;l<s;l++)t[c+l]=t[h+l];t[c+s]=a[u]}else{if(0===i)throw new Error;for(l=0;l<i;l++)t[c+l]=a[u+l]}}(l,u,h,c):function(n,i,o,s){var l=0;for(l=0;l<s;l++)a[l]=t[o+l];var u=n+i-1,h=s-1,c=o+s-1,p=0,d=0;if(t[c--]=t[u--],0==--i){for(p=c-(s-1),l=0;l<s;l++)t[p+l]=a[l];return}if(1===s){for(d=(c-=i)+1,p=(u-=i)+1,l=i-1;l>=0;l--)t[d+l]=t[p+l];return void(t[c]=a[h])}var f=r;for(;;){var g=0,y=0,v=!1;do{if(e(a[h],t[u])<0){if(t[c--]=t[u--],g++,y=0,0==--i){v=!0;break}}else if(t[c--]=a[h--],y++,g=0,1==--s){v=!0;break}}while((g|y)<f);if(v)break;do{if(0!==(g=i-ve(a[h],t,n,i,i-1,e))){for(i-=g,d=(c-=g)+1,p=(u-=g)+1,l=g-1;l>=0;l--)t[d+l]=t[p+l];if(0===i){v=!0;break}}if(t[c--]=a[h--],1==--s){v=!0;break}if(0!==(y=s-ye(t[u],a,0,s,s-1,e))){for(s-=y,d=(c-=y)+1,p=(h-=y)+1,l=0;l<y;l++)t[d+l]=a[p+l];if(s<=1){v=!0;break}}if(t[c--]=t[u--],0==--i){v=!0;break}f--}while(g>=7||y>=7);if(v)break;f<0&&(f=0),f+=2}(r=f)<1&&(r=1);if(1===s){for(d=(c-=i)+1,p=(u-=i)+1,l=i-1;l>=0;l--)t[d+l]=t[p+l];t[c]=a[h]}else{if(0===s)throw new Error;for(p=c-(s-1),l=0;l<s;l++)t[p+l]=a[l]}}(l,u,h,c))}return n=[],i=[],{mergeRuns:function(){for(;o>1;){var t=o-2;if(t>=1&&i[t-1]<=i[t]+i[t+1]||t>=2&&i[t-2]<=i[t]+i[t-1])i[t-1]<i[t+1]&&t--;else if(i[t]>i[t+1])break;s(t)}},forceMergeRuns:function(){for(;o>1;){var t=o-2;t>0&&i[t-1]<i[t+1]&&t--,s(t)}},pushRun:function(t,e){n[o]=t,i[o]=e,o+=1}}}function _e(t,e,n,i){n||(n=0),i||(i=t.length);var r=i-n;if(!(r<2)){var o=0;if(r<32)ge(t,n,i,n+(o=fe(t,n,i,e)),e);else{var a=me(t,e),s=function(t){for(var e=0;t>=32;)e|=1&t,t>>=1;return t+e}(r);do{if((o=fe(t,n,i,e))<s){var l=r;l>s&&(l=s),ge(t,n,n+l,n+o,e),o=l}a.pushRun(n,o),a.mergeRuns(),r-=o,n+=o}while(0!==r);a.forceMergeRuns()}}}var xe=!1;function be(){xe||(xe=!0,console.warn(\"z / z2 / zlevel of displayable is invalid, which may cause unexpected errors\"))}function we(t,e){return t.zlevel===e.zlevel?t.z===e.z?t.z2-e.z2:t.z-e.z:t.zlevel-e.zlevel}var Se=function(){function t(){this._roots=[],this._displayList=[],this._displayListLen=0,this.displayableSortFunc=we}return t.prototype.traverse=function(t,e){for(var n=0;n<this._roots.length;n++)this._roots[n].traverse(t,e)},t.prototype.getDisplayList=function(t,e){e=e||!1;var n=this._displayList;return!t&&n.length||this.updateDisplayList(e),n},t.prototype.updateDisplayList=function(t){this._displayListLen=0;for(var e=this._roots,n=this._displayList,i=0,r=e.length;i<r;i++)this._updateAndAddDisplayable(e[i],null,t);n.length=this._displayListLen,a.canvasSupported&&_e(n,we)},t.prototype._updateAndAddDisplayable=function(t,e,n){if(!t.ignore||n){t.beforeUpdate(),t.update(),t.afterUpdate();var i=t.getClipPath();if(t.ignoreClip)e=null;else if(i){e=e?e.slice():[];for(var r=i,o=t;r;)r.parent=o,r.updateTransform(),e.push(r),o=r,r=r.getClipPath()}if(t.childrenRef){for(var a=t.childrenRef(),s=0;s<a.length;s++){var l=a[s];t.__dirty&&(l.__dirty|=1),this._updateAndAddDisplayable(l,e,n)}t.__dirty=0}else{var u=t;e&&e.length?u.__clipPaths=e:u.__clipPaths&&u.__clipPaths.length>0&&(u.__clipPaths=[]),isNaN(u.z)&&(be(),u.z=0),isNaN(u.z2)&&(be(),u.z2=0),isNaN(u.zlevel)&&(be(),u.zlevel=0),this._displayList[this._displayListLen++]=u}var h=t.getDecalElement&&t.getDecalElement();h&&this._updateAndAddDisplayable(h,e,n);var c=t.getTextGuideLine();c&&this._updateAndAddDisplayable(c,e,n);var p=t.getTextContent();p&&this._updateAndAddDisplayable(p,e,n)}},t.prototype.addRoot=function(t){t.__zr&&t.__zr.storage===this||this._roots.push(t)},t.prototype.delRoot=function(t){if(t instanceof Array)for(var e=0,n=t.length;e<n;e++)this.delRoot(t[e]);else{var i=D(this._roots,t);i>=0&&this._roots.splice(i,1)}},t.prototype.delAllRoots=function(){this._roots=[],this._displayList=[],this._displayListLen=0},t.prototype.getRoots=function(){return this._roots},t.prototype.dispose=function(){this._displayList=null,this._roots=null},t}(),Me=\"undefined\"!=typeof window&&(window.requestAnimationFrame&&window.requestAnimationFrame.bind(window)||window.msRequestAnimationFrame&&window.msRequestAnimationFrame.bind(window)||window.mozRequestAnimationFrame||window.webkitRequestAnimationFrame)||function(t){return setTimeout(t,16)},Ie={linear:function(t){return t},quadraticIn:function(t){return t*t},quadraticOut:function(t){return t*(2-t)},quadraticInOut:function(t){return(t*=2)<1?.5*t*t:-.5*(--t*(t-2)-1)},cubicIn:function(t){return t*t*t},cubicOut:function(t){return--t*t*t+1},cubicInOut:function(t){return(t*=2)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},quarticIn:function(t){return t*t*t*t},quarticOut:function(t){return 1- --t*t*t*t},quarticInOut:function(t){return(t*=2)<1?.5*t*t*t*t:-.5*((t-=2)*t*t*t-2)},quinticIn:function(t){return t*t*t*t*t},quinticOut:function(t){return--t*t*t*t*t+1},quinticInOut:function(t){return(t*=2)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},sinusoidalIn:function(t){return 1-Math.cos(t*Math.PI/2)},sinusoidalOut:function(t){return Math.sin(t*Math.PI/2)},sinusoidalInOut:function(t){return.5*(1-Math.cos(Math.PI*t))},exponentialIn:function(t){return 0===t?0:Math.pow(1024,t-1)},exponentialOut:function(t){return 1===t?1:1-Math.pow(2,-10*t)},exponentialInOut:function(t){return 0===t?0:1===t?1:(t*=2)<1?.5*Math.pow(1024,t-1):.5*(2-Math.pow(2,-10*(t-1)))},circularIn:function(t){return 1-Math.sqrt(1-t*t)},circularOut:function(t){return Math.sqrt(1- --t*t)},circularInOut:function(t){return(t*=2)<1?-.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},elasticIn:function(t){var e,n=.1;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=.4*Math.asin(1/n)/(2*Math.PI),-n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/.4))},elasticOut:function(t){var e,n=.1;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=.4*Math.asin(1/n)/(2*Math.PI),n*Math.pow(2,-10*t)*Math.sin((t-e)*(2*Math.PI)/.4)+1)},elasticInOut:function(t){var e,n=.1,i=.4;return 0===t?0:1===t?1:(!n||n<1?(n=1,e=.1):e=i*Math.asin(1/n)/(2*Math.PI),(t*=2)<1?n*Math.pow(2,10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*-.5:n*Math.pow(2,-10*(t-=1))*Math.sin((t-e)*(2*Math.PI)/i)*.5+1)},backIn:function(t){var e=1.70158;return t*t*((e+1)*t-e)},backOut:function(t){var e=1.70158;return--t*t*((e+1)*t+e)+1},backInOut:function(t){var e=2.5949095;return(t*=2)<1?t*t*((e+1)*t-e)*.5:.5*((t-=2)*t*((e+1)*t+e)+2)},bounceIn:function(t){return 1-Ie.bounceOut(1-t)},bounceOut:function(t){return t<1/2.75?7.5625*t*t:t<2/2.75?7.5625*(t-=1.5/2.75)*t+.75:t<2.5/2.75?7.5625*(t-=2.25/2.75)*t+.9375:7.5625*(t-=2.625/2.75)*t+.984375},bounceInOut:function(t){return t<.5?.5*Ie.bounceIn(2*t):.5*Ie.bounceOut(2*t-1)+.5}},Te=function(){function t(t){this._initialized=!1,this._startTime=0,this._pausedTime=0,this._paused=!1,this._life=t.life||1e3,this._delay=t.delay||0,this.loop=null!=t.loop&&t.loop,this.gap=t.gap||0,this.easing=t.easing||\"linear\",this.onframe=t.onframe,this.ondestroy=t.ondestroy,this.onrestart=t.onrestart}return t.prototype.step=function(t,e){if(this._initialized||(this._startTime=t+this._delay,this._initialized=!0),!this._paused){var n=(t-this._startTime-this._pausedTime)/this._life;n<0&&(n=0),n=Math.min(n,1);var i=this.easing,r=\"string\"==typeof i?Ie[i]:i,o=\"function\"==typeof r?r(n):n;if(this.onframe&&this.onframe(o),1===n){if(!this.loop)return!0;this._restart(t),this.onrestart&&this.onrestart()}return!1}this._pausedTime+=e},t.prototype._restart=function(t){var e=(t-this._startTime-this._pausedTime)%this._life;this._startTime=t-e+this.gap,this._pausedTime=0},t.prototype.pause=function(){this._paused=!0},t.prototype.resume=function(){this._paused=!1},t}(),Ce=function(t){this.value=t},De=function(){function t(){this._len=0}return t.prototype.insert=function(t){var e=new Ce(t);return this.insertEntry(e),e},t.prototype.insertEntry=function(t){this.head?(this.tail.next=t,t.prev=this.tail,t.next=null,this.tail=t):this.head=this.tail=t,this._len++},t.prototype.remove=function(t){var e=t.prev,n=t.next;e?e.next=n:this.head=n,n?n.prev=e:this.tail=e,t.next=t.prev=null,this._len--},t.prototype.len=function(){return this._len},t.prototype.clear=function(){this.head=this.tail=null,this._len=0},t}(),Ae=function(){function t(t){this._list=new De,this._maxSize=10,this._map={},this._maxSize=t}return t.prototype.put=function(t,e){var n=this._list,i=this._map,r=null;if(null==i[t]){var o=n.len(),a=this._lastRemovedEntry;if(o>=this._maxSize&&o>0){var s=n.head;n.remove(s),delete i[s.key],r=s.value,this._lastRemovedEntry=s}a?a.value=e:a=new Ce(e),a.key=t,n.insertEntry(a),i[t]=a}return r},t.prototype.get=function(t){var e=this._map[t],n=this._list;if(null!=e)return e!==n.tail&&(n.remove(e),n.insertEntry(e)),e.value},t.prototype.clear=function(){this._list.clear(),this._map={}},t.prototype.len=function(){return this._list.len()},t}(),Le={transparent:[0,0,0,0],aliceblue:[240,248,255,1],antiquewhite:[250,235,215,1],aqua:[0,255,255,1],aquamarine:[127,255,212,1],azure:[240,255,255,1],beige:[245,245,220,1],bisque:[255,228,196,1],black:[0,0,0,1],blanchedalmond:[255,235,205,1],blue:[0,0,255,1],blueviolet:[138,43,226,1],brown:[165,42,42,1],burlywood:[222,184,135,1],cadetblue:[95,158,160,1],chartreuse:[127,255,0,1],chocolate:[210,105,30,1],coral:[255,127,80,1],cornflowerblue:[100,149,237,1],cornsilk:[255,248,220,1],crimson:[220,20,60,1],cyan:[0,255,255,1],darkblue:[0,0,139,1],darkcyan:[0,139,139,1],darkgoldenrod:[184,134,11,1],darkgray:[169,169,169,1],darkgreen:[0,100,0,1],darkgrey:[169,169,169,1],darkkhaki:[189,183,107,1],darkmagenta:[139,0,139,1],darkolivegreen:[85,107,47,1],darkorange:[255,140,0,1],darkorchid:[153,50,204,1],darkred:[139,0,0,1],darksalmon:[233,150,122,1],darkseagreen:[143,188,143,1],darkslateblue:[72,61,139,1],darkslategray:[47,79,79,1],darkslategrey:[47,79,79,1],darkturquoise:[0,206,209,1],darkviolet:[148,0,211,1],deeppink:[255,20,147,1],deepskyblue:[0,191,255,1],dimgray:[105,105,105,1],dimgrey:[105,105,105,1],dodgerblue:[30,144,255,1],firebrick:[178,34,34,1],floralwhite:[255,250,240,1],forestgreen:[34,139,34,1],fuchsia:[255,0,255,1],gainsboro:[220,220,220,1],ghostwhite:[248,248,255,1],gold:[255,215,0,1],goldenrod:[218,165,32,1],gray:[128,128,128,1],green:[0,128,0,1],greenyellow:[173,255,47,1],grey:[128,128,128,1],honeydew:[240,255,240,1],hotpink:[255,105,180,1],indianred:[205,92,92,1],indigo:[75,0,130,1],ivory:[255,255,240,1],khaki:[240,230,140,1],lavender:[230,230,250,1],lavenderblush:[255,240,245,1],lawngreen:[124,252,0,1],lemonchiffon:[255,250,205,1],lightblue:[173,216,230,1],lightcoral:[240,128,128,1],lightcyan:[224,255,255,1],lightgoldenrodyellow:[250,250,210,1],lightgray:[211,211,211,1],lightgreen:[144,238,144,1],lightgrey:[211,211,211,1],lightpink:[255,182,193,1],lightsalmon:[255,160,122,1],lightseagreen:[32,178,170,1],lightskyblue:[135,206,250,1],lightslategray:[119,136,153,1],lightslategrey:[119,136,153,1],lightsteelblue:[176,196,222,1],lightyellow:[255,255,224,1],lime:[0,255,0,1],limegreen:[50,205,50,1],linen:[250,240,230,1],magenta:[255,0,255,1],maroon:[128,0,0,1],mediumaquamarine:[102,205,170,1],mediumblue:[0,0,205,1],mediumorchid:[186,85,211,1],mediumpurple:[147,112,219,1],mediumseagreen:[60,179,113,1],mediumslateblue:[123,104,238,1],mediumspringgreen:[0,250,154,1],mediumturquoise:[72,209,204,1],mediumvioletred:[199,21,133,1],midnightblue:[25,25,112,1],mintcream:[245,255,250,1],mistyrose:[255,228,225,1],moccasin:[255,228,181,1],navajowhite:[255,222,173,1],navy:[0,0,128,1],oldlace:[253,245,230,1],olive:[128,128,0,1],olivedrab:[107,142,35,1],orange:[255,165,0,1],orangered:[255,69,0,1],orchid:[218,112,214,1],palegoldenrod:[238,232,170,1],palegreen:[152,251,152,1],paleturquoise:[175,238,238,1],palevioletred:[219,112,147,1],papayawhip:[255,239,213,1],peachpuff:[255,218,185,1],peru:[205,133,63,1],pink:[255,192,203,1],plum:[221,160,221,1],powderblue:[176,224,230,1],purple:[128,0,128,1],red:[255,0,0,1],rosybrown:[188,143,143,1],royalblue:[65,105,225,1],saddlebrown:[139,69,19,1],salmon:[250,128,114,1],sandybrown:[244,164,96,1],seagreen:[46,139,87,1],seashell:[255,245,238,1],sienna:[160,82,45,1],silver:[192,192,192,1],skyblue:[135,206,235,1],slateblue:[106,90,205,1],slategray:[112,128,144,1],slategrey:[112,128,144,1],snow:[255,250,250,1],springgreen:[0,255,127,1],steelblue:[70,130,180,1],tan:[210,180,140,1],teal:[0,128,128,1],thistle:[216,191,216,1],tomato:[255,99,71,1],turquoise:[64,224,208,1],violet:[238,130,238,1],wheat:[245,222,179,1],white:[255,255,255,1],whitesmoke:[245,245,245,1],yellow:[255,255,0,1],yellowgreen:[154,205,50,1]};function ke(t){return(t=Math.round(t))<0?0:t>255?255:t}function Pe(t){return t<0?0:t>1?1:t}function Oe(t){var e=t;return e.length&&\"%\"===e.charAt(e.length-1)?ke(parseFloat(e)/100*255):ke(parseInt(e,10))}function Re(t){var e=t;return e.length&&\"%\"===e.charAt(e.length-1)?Pe(parseFloat(e)/100):Pe(parseFloat(e))}function Ne(t,e,n){return n<0?n+=1:n>1&&(n-=1),6*n<1?t+(e-t)*n*6:2*n<1?e:3*n<2?t+(e-t)*(2/3-n)*6:t}function ze(t,e,n){return t+(e-t)*n}function Ee(t,e,n,i,r){return t[0]=e,t[1]=n,t[2]=i,t[3]=r,t}function Ve(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t}var Be=new Ae(20),Fe=null;function Ge(t,e){Fe&&Ve(Fe,e),Fe=Be.put(t,Fe||e.slice())}function He(t,e){if(t){e=e||[];var n=Be.get(t);if(n)return Ve(e,n);var i=(t+=\"\").replace(/ /g,\"\").toLowerCase();if(i in Le)return Ve(e,Le[i]),Ge(t,e),e;var r,o=i.length;if(\"#\"===i.charAt(0))return 4===o||5===o?(r=parseInt(i.slice(1,4),16))>=0&&r<=4095?(Ee(e,(3840&r)>>4|(3840&r)>>8,240&r|(240&r)>>4,15&r|(15&r)<<4,5===o?parseInt(i.slice(4),16)/15:1),Ge(t,e),e):void Ee(e,0,0,0,1):7===o||9===o?(r=parseInt(i.slice(1,7),16))>=0&&r<=16777215?(Ee(e,(16711680&r)>>16,(65280&r)>>8,255&r,9===o?parseInt(i.slice(7),16)/255:1),Ge(t,e),e):void Ee(e,0,0,0,1):void 0;var a=i.indexOf(\"(\"),s=i.indexOf(\")\");if(-1!==a&&s+1===o){var l=i.substr(0,a),u=i.substr(a+1,s-(a+1)).split(\",\"),h=1;switch(l){case\"rgba\":if(4!==u.length)return 3===u.length?Ee(e,+u[0],+u[1],+u[2],1):Ee(e,0,0,0,1);h=Re(u.pop());case\"rgb\":return 3!==u.length?void Ee(e,0,0,0,1):(Ee(e,Oe(u[0]),Oe(u[1]),Oe(u[2]),h),Ge(t,e),e);case\"hsla\":return 4!==u.length?void Ee(e,0,0,0,1):(u[3]=Re(u[3]),We(u,e),Ge(t,e),e);case\"hsl\":return 3!==u.length?void Ee(e,0,0,0,1):(We(u,e),Ge(t,e),e);default:return}}Ee(e,0,0,0,1)}}function We(t,e){var n=(parseFloat(t[0])%360+360)%360/360,i=Re(t[1]),r=Re(t[2]),o=r<=.5?r*(i+1):r+i-r*i,a=2*r-o;return Ee(e=e||[],ke(255*Ne(a,o,n+1/3)),ke(255*Ne(a,o,n)),ke(255*Ne(a,o,n-1/3)),1),4===t.length&&(e[3]=t[3]),e}function Ue(t,e){var n=He(t);if(n){for(var i=0;i<3;i++)n[i]=e<0?n[i]*(1-e)|0:(255-n[i])*e+n[i]|0,n[i]>255?n[i]=255:n[i]<0&&(n[i]=0);return Je(n,4===n.length?\"rgba\":\"rgb\")}}function Xe(t){var e=He(t);if(e)return((1<<24)+(e[0]<<16)+(e[1]<<8)+ +e[2]).toString(16).slice(1)}function Ye(t,e,n){if(e&&e.length&&t>=0&&t<=1){n=n||[];var i=t*(e.length-1),r=Math.floor(i),o=Math.ceil(i),a=e[r],s=e[o],l=i-r;return n[0]=ke(ze(a[0],s[0],l)),n[1]=ke(ze(a[1],s[1],l)),n[2]=ke(ze(a[2],s[2],l)),n[3]=Pe(ze(a[3],s[3],l)),n}}var Ze=Ye;function je(t,e,n){if(e&&e.length&&t>=0&&t<=1){var i=t*(e.length-1),r=Math.floor(i),o=Math.ceil(i),a=He(e[r]),s=He(e[o]),l=i-r,u=Je([ke(ze(a[0],s[0],l)),ke(ze(a[1],s[1],l)),ke(ze(a[2],s[2],l)),Pe(ze(a[3],s[3],l))],\"rgba\");return n?{color:u,leftIndex:r,rightIndex:o,value:i}:u}}var qe=je;function Ke(t,e,n,i){var r=He(t);if(t)return r=function(t){if(t){var e,n,i=t[0]/255,r=t[1]/255,o=t[2]/255,a=Math.min(i,r,o),s=Math.max(i,r,o),l=s-a,u=(s+a)/2;if(0===l)e=0,n=0;else{n=u<.5?l/(s+a):l/(2-s-a);var h=((s-i)/6+l/2)/l,c=((s-r)/6+l/2)/l,p=((s-o)/6+l/2)/l;i===s?e=p-c:r===s?e=1/3+h-p:o===s&&(e=2/3+c-h),e<0&&(e+=1),e>1&&(e-=1)}var d=[360*e,n,u];return null!=t[3]&&d.push(t[3]),d}}(r),null!=e&&(r[0]=function(t){return(t=Math.round(t))<0?0:t>360?360:t}(e)),null!=n&&(r[1]=Re(n)),null!=i&&(r[2]=Re(i)),Je(We(r),\"rgba\")}function $e(t,e){var n=He(t);if(n&&null!=e)return n[3]=Pe(e),Je(n,\"rgba\")}function Je(t,e){if(t&&t.length){var n=t[0]+\",\"+t[1]+\",\"+t[2];return\"rgba\"!==e&&\"hsva\"!==e&&\"hsla\"!==e||(n+=\",\"+t[3]),e+\"(\"+n+\")\"}}function Qe(t,e){var n=He(t);return n?(.299*n[0]+.587*n[1]+.114*n[2])*n[3]/255+(1-n[3])*e:0}var tn=Object.freeze({__proto__:null,parse:He,lift:Ue,toHex:Xe,fastLerp:Ye,fastMapToColor:Ze,lerp:je,mapToColor:qe,modifyHSL:Ke,modifyAlpha:$e,stringify:Je,lum:Qe,random:function(){return\"rgb(\"+Math.round(255*Math.random())+\",\"+Math.round(255*Math.random())+\",\"+Math.round(255*Math.random())+\")\"}}),en=Array.prototype.slice;function nn(t,e,n){return(e-t)*n+t}function rn(t,e,n,i){for(var r=e.length,o=0;o<r;o++)t[o]=nn(e[o],n[o],i)}function on(t,e,n,i){for(var r=e.length,o=0;o<r;o++)t[o]=e[o]+n[o]*i;return t}function an(t,e,n,i){for(var r=e.length,o=r&&e[0].length,a=0;a<r;a++){t[a]||(t[a]=[]);for(var s=0;s<o;s++)t[a][s]=e[a][s]+n[a][s]*i}return t}function sn(t,e,n){var i=t,r=e;if(i.push&&r.push){var o=i.length,a=r.length;if(o!==a)if(o>a)i.length=a;else for(var s=o;s<a;s++)i.push(1===n?r[s]:en.call(r[s]));var l=i[0]&&i[0].length;for(s=0;s<i.length;s++)if(1===n)isNaN(i[s])&&(i[s]=r[s]);else for(var u=0;u<l;u++)isNaN(i[s][u])&&(i[s][u]=r[s][u])}}function ln(t,e){var n=t.length;if(n!==e.length)return!1;for(var i=0;i<n;i++)if(t[i]!==e[i])return!1;return!0}function un(t,e,n,i,r,o,a){var s=.5*(n-t),l=.5*(i-e);return(2*(e-n)+s+l)*a+(-3*(e-n)-2*s-l)*o+s*r+e}function hn(t,e,n,i,r,o,a,s){for(var l=e.length,u=0;u<l;u++)t[u]=un(e[u],n[u],i[u],r[u],o,a,s)}function cn(t){if(k(t)){var e=t.length;if(k(t[0])){for(var n=[],i=0;i<e;i++)n.push(en.call(t[i]));return n}return en.call(t)}return t}function pn(t){return t[0]=Math.floor(t[0]),t[1]=Math.floor(t[1]),t[2]=Math.floor(t[2]),\"rgba(\"+t.join(\",\")+\")\"}var dn,fn,gn=[0,0,0,0],yn=function(){function t(t){this.keyframes=[],this.maxTime=0,this.arrDim=0,this.interpolable=!0,this._needsSort=!1,this._isAllValueEqual=!0,this._lastFrame=0,this._lastFramePercent=0,this.propName=t}return t.prototype.isFinished=function(){return this._finished},t.prototype.setFinished=function(){this._finished=!0,this._additiveTrack&&this._additiveTrack.setFinished()},t.prototype.needsAnimate=function(){return!this._isAllValueEqual&&this.keyframes.length>=2&&this.interpolable},t.prototype.getAdditiveTrack=function(){return this._additiveTrack},t.prototype.addKeyframe=function(t,e){t>=this.maxTime?this.maxTime=t:this._needsSort=!0;var n=this.keyframes,i=n.length;if(this.interpolable)if(k(e)){var r=function(t){return k(t&&t[0])?2:1}(e);if(i>0&&this.arrDim!==r)return void(this.interpolable=!1);if(1===r&&\"number\"!=typeof e[0]||2===r&&\"number\"!=typeof e[0][0])return void(this.interpolable=!1);if(i>0){var o=n[i-1];this._isAllValueEqual&&(1===r&&ln(e,o.value)||(this._isAllValueEqual=!1))}this.arrDim=r}else{if(this.arrDim>0)return void(this.interpolable=!1);if(\"string\"==typeof e){var a=He(e);a?(e=a,this.isValueColor=!0):this.interpolable=!1}else if(\"number\"!=typeof e||isNaN(e))return void(this.interpolable=!1);if(this._isAllValueEqual&&i>0){o=n[i-1];(this.isValueColor&&!ln(o.value,e)||o.value!==e)&&(this._isAllValueEqual=!1)}}var s={time:t,value:e,percent:0};return this.keyframes.push(s),s},t.prototype.prepare=function(t){var e=this.keyframes;this._needsSort&&e.sort((function(t,e){return t.time-e.time}));for(var n=this.arrDim,i=e.length,r=e[i-1],o=0;o<i;o++)e[o].percent=e[o].time/this.maxTime,n>0&&o!==i-1&&sn(e[o].value,r.value,n);if(t&&this.needsAnimate()&&t.needsAnimate()&&n===t.arrDim&&this.isValueColor===t.isValueColor&&!t._finished){this._additiveTrack=t;var a=e[0].value;for(o=0;o<i;o++)0===n?this.isValueColor?e[o].additiveValue=on([],e[o].value,a,-1):e[o].additiveValue=e[o].value-a:1===n?e[o].additiveValue=on([],e[o].value,a,-1):2===n&&(e[o].additiveValue=an([],e[o].value,a,-1))}},t.prototype.step=function(t,e){if(!this._finished){this._additiveTrack&&this._additiveTrack._finished&&(this._additiveTrack=null);var n,i=null!=this._additiveTrack,r=i?\"additiveValue\":\"value\",o=this.keyframes,a=this.keyframes.length,s=this.propName,l=this.arrDim,u=this.isValueColor;if(e<0)n=0;else if(e<this._lastFramePercent){for(n=Math.min(this._lastFrame+1,a-1);n>=0&&!(o[n].percent<=e);n--);n=Math.min(n,a-2)}else{for(n=this._lastFrame;n<a&&!(o[n].percent>e);n++);n=Math.min(n-1,a-2)}var h=o[n+1],c=o[n];if(c&&h){this._lastFrame=n,this._lastFramePercent=e;var p=h.percent-c.percent;if(0!==p){var d=(e-c.percent)/p,f=i?this._additiveValue:u?gn:t[s];if((l>0||u)&&!f&&(f=this._additiveValue=[]),this.useSpline){var g=o[n][r],y=o[0===n?n:n-1][r],v=o[n>a-2?a-1:n+1][r],m=o[n>a-3?a-1:n+2][r];if(l>0)1===l?hn(f,y,g,v,m,d,d*d,d*d*d):function(t,e,n,i,r,o,a,s){for(var l=e.length,u=e[0].length,h=0;h<l;h++){t[h]||(t[1]=[]);for(var c=0;c<u;c++)t[h][c]=un(e[h][c],n[h][c],i[h][c],r[h][c],o,a,s)}}(f,y,g,v,m,d,d*d,d*d*d);else if(u)hn(f,y,g,v,m,d,d*d,d*d*d),i||(t[s]=pn(f));else{var _=void 0;_=this.interpolable?un(y,g,v,m,d,d*d,d*d*d):v,i?this._additiveValue=_:t[s]=_}}else if(l>0)1===l?rn(f,c[r],h[r],d):function(t,e,n,i){for(var r=e.length,o=r&&e[0].length,a=0;a<r;a++){t[a]||(t[a]=[]);for(var s=0;s<o;s++)t[a][s]=nn(e[a][s],n[a][s],i)}}(f,c[r],h[r],d);else if(u)rn(f,c[r],h[r],d),i||(t[s]=pn(f));else{_=void 0;_=this.interpolable?nn(c[r],h[r],d):function(t,e,n){return n>.5?e:t}(c[r],h[r],d),i?this._additiveValue=_:t[s]=_}i&&this._addToTarget(t)}}}},t.prototype._addToTarget=function(t){var e=this.arrDim,n=this.propName,i=this._additiveValue;0===e?this.isValueColor?(He(t[n],gn),on(gn,gn,i,1),t[n]=pn(gn)):t[n]=t[n]+i:1===e?on(t[n],t[n],i,1):2===e&&an(t[n],t[n],i,1)},t}(),vn=function(){function t(t,e,n){this._tracks={},this._trackKeys=[],this._delay=0,this._maxTime=0,this._paused=!1,this._started=0,this._clip=null,this._target=t,this._loop=e,e&&n?b(\"Can' use additive animation on looped animation.\"):this._additiveAnimators=n}return t.prototype.getTarget=function(){return this._target},t.prototype.changeTarget=function(t){this._target=t},t.prototype.when=function(t,e){return this.whenWithKeys(t,e,E(e))},t.prototype.whenWithKeys=function(t,e,n){for(var i=this._tracks,r=0;r<n.length;r++){var o=n[r],a=i[o];if(!a){a=i[o]=new yn(o);var s=void 0,l=this._getAdditiveTrack(o);if(l){var u=l.keyframes[l.keyframes.length-1];s=u&&u.value,l.isValueColor&&s&&(s=pn(s))}else s=this._target[o];if(null==s)continue;0!==t&&a.addKeyframe(0,cn(s)),this._trackKeys.push(o)}a.addKeyframe(t,cn(e[o]))}return this._maxTime=Math.max(this._maxTime,t),this},t.prototype.pause=function(){this._clip.pause(),this._paused=!0},t.prototype.resume=function(){this._clip.resume(),this._paused=!1},t.prototype.isPaused=function(){return!!this._paused},t.prototype._doneCallback=function(){this._setTracksFinished(),this._clip=null;var t=this._doneList;if(t)for(var e=t.length,n=0;n<e;n++)t[n].call(this)},t.prototype._abortedCallback=function(){this._setTracksFinished();var t=this.animation,e=this._abortedList;if(t&&t.removeClip(this._clip),this._clip=null,e)for(var n=0;n<e.length;n++)e[n].call(this)},t.prototype._setTracksFinished=function(){for(var t=this._tracks,e=this._trackKeys,n=0;n<e.length;n++)t[e[n]].setFinished()},t.prototype._getAdditiveTrack=function(t){var e,n=this._additiveAnimators;if(n)for(var i=0;i<n.length;i++){var r=n[i].getTrack(t);r&&(e=r)}return e},t.prototype.start=function(t,e){if(!(this._started>0)){this._started=1;for(var n=this,i=[],r=0;r<this._trackKeys.length;r++){var o=this._trackKeys[r],a=this._tracks[o],s=this._getAdditiveTrack(o),l=a.keyframes;if(a.prepare(s),a.needsAnimate())i.push(a);else if(!a.interpolable){var u=l[l.length-1];u&&(n._target[a.propName]=u.value)}}if(i.length||e){var h=new Te({life:this._maxTime,loop:this._loop,delay:this._delay,onframe:function(t){n._started=2;var e=n._additiveAnimators;if(e){for(var r=!1,o=0;o<e.length;o++)if(e[o]._clip){r=!0;break}r||(n._additiveAnimators=null)}for(o=0;o<i.length;o++)i[o].step(n._target,t);var a=n._onframeList;if(a)for(o=0;o<a.length;o++)a[o](n._target,t)},ondestroy:function(){n._doneCallback()}});this._clip=h,this.animation&&this.animation.addClip(h),t&&\"spline\"!==t&&(h.easing=t)}else this._doneCallback();return this}},t.prototype.stop=function(t){if(this._clip){var e=this._clip;t&&e.onframe(1),this._abortedCallback()}},t.prototype.delay=function(t){return this._delay=t,this},t.prototype.during=function(t){return t&&(this._onframeList||(this._onframeList=[]),this._onframeList.push(t)),this},t.prototype.done=function(t){return t&&(this._doneList||(this._doneList=[]),this._doneList.push(t)),this},t.prototype.aborted=function(t){return t&&(this._abortedList||(this._abortedList=[]),this._abortedList.push(t)),this},t.prototype.getClip=function(){return this._clip},t.prototype.getTrack=function(t){return this._tracks[t]},t.prototype.stopTracks=function(t,e){if(!t.length||!this._clip)return!0;for(var n=this._tracks,i=this._trackKeys,r=0;r<t.length;r++){var o=n[t[r]];o&&(e?o.step(this._target,1):1===this._started&&o.step(this._target,0),o.setFinished())}var a=!0;for(r=0;r<i.length;r++)if(!n[i[r]].isFinished()){a=!1;break}return a&&this._abortedCallback(),a},t.prototype.saveFinalToTarget=function(t,e){if(t){e=e||this._trackKeys;for(var n=0;n<e.length;n++){var i=e[n],r=this._tracks[i];if(r&&!r.isFinished()){var o=r.keyframes,a=o[o.length-1];if(a){var s=cn(a.value);r.isValueColor&&(s=pn(s)),t[i]=s}}}}},t.prototype.__changeFinalValue=function(t,e){e=e||E(t);for(var n=0;n<e.length;n++){var i=e[n],r=this._tracks[i];if(r){var o=r.keyframes;if(o.length>1){var a=o.pop();r.addKeyframe(a.time,t[i]),r.prepare(r.getAdditiveTrack())}}}},t}(),mn=function(t){function e(e){var n=t.call(this)||this;return n._running=!1,n._time=0,n._pausedTime=0,n._pauseStart=0,n._paused=!1,e=e||{},n.stage=e.stage||{},n.onframe=e.onframe||function(){},n}return n(e,t),e.prototype.addClip=function(t){t.animation&&this.removeClip(t),this._clipsHead?(this._clipsTail.next=t,t.prev=this._clipsTail,t.next=null,this._clipsTail=t):this._clipsHead=this._clipsTail=t,t.animation=this},e.prototype.addAnimator=function(t){t.animation=this;var e=t.getClip();e&&this.addClip(e)},e.prototype.removeClip=function(t){if(t.animation){var e=t.prev,n=t.next;e?e.next=n:this._clipsHead=n,n?n.prev=e:this._clipsTail=e,t.next=t.prev=t.animation=null}},e.prototype.removeAnimator=function(t){var e=t.getClip();e&&this.removeClip(e),t.animation=null},e.prototype.update=function(t){for(var e=(new Date).getTime()-this._pausedTime,n=e-this._time,i=this._clipsHead;i;){var r=i.next;i.step(e,n)?(i.ondestroy&&i.ondestroy(),this.removeClip(i),i=r):i=r}this._time=e,t||(this.onframe(n),this.trigger(\"frame\",n),this.stage.update&&this.stage.update())},e.prototype._startLoop=function(){var t=this;this._running=!0,Me((function e(){t._running&&(Me(e),!t._paused&&t.update())}))},e.prototype.start=function(){this._running||(this._time=(new Date).getTime(),this._pausedTime=0,this._startLoop())},e.prototype.stop=function(){this._running=!1},e.prototype.pause=function(){this._paused||(this._pauseStart=(new Date).getTime(),this._paused=!0)},e.prototype.resume=function(){this._paused&&(this._pausedTime+=(new Date).getTime()-this._pauseStart,this._paused=!1)},e.prototype.clear=function(){for(var t=this._clipsHead;t;){var e=t.next;t.prev=t.next=t.animation=null,t=e}this._clipsHead=this._clipsTail=null},e.prototype.isFinished=function(){return null==this._clipsHead},e.prototype.animate=function(t,e){e=e||{},this.start();var n=new vn(t,e.loop);return this.addAnimator(n),n},e}(Ft),_n=a.domSupported,xn=(fn={pointerdown:1,pointerup:1,pointermove:1,pointerout:1},{mouse:dn=[\"click\",\"dblclick\",\"mousewheel\",\"wheel\",\"mouseout\",\"mouseup\",\"mousedown\",\"mousemove\",\"contextmenu\"],touch:[\"touchstart\",\"touchend\",\"touchmove\"],pointer:O(dn,(function(t){var e=t.replace(\"mouse\",\"pointer\");return fn.hasOwnProperty(e)?e:t}))}),bn=[\"mousemove\",\"mouseup\"],wn=[\"pointermove\",\"pointerup\"],Sn=!1;function Mn(t){var e=t.pointerType;return\"pen\"===e||\"touch\"===e}function In(t){t&&(t.zrByTouch=!0)}function Tn(t,e){for(var n=e,i=!1;n&&9!==n.nodeType&&!(i=n.domBelongToZr||n!==e&&n===t.painterRoot);)n=n.parentNode;return i}var Cn=function(t,e){this.stopPropagation=ft,this.stopImmediatePropagation=ft,this.preventDefault=ft,this.type=e.type,this.target=this.currentTarget=t.dom,this.pointerType=e.pointerType,this.clientX=e.clientX,this.clientY=e.clientY},Dn={mousedown:function(t){t=Qt(this.dom,t),this.__mayPointerCapture=[t.zrX,t.zrY],this.trigger(\"mousedown\",t)},mousemove:function(t){t=Qt(this.dom,t);var e=this.__mayPointerCapture;!e||t.zrX===e[0]&&t.zrY===e[1]||this.__togglePointerCapture(!0),this.trigger(\"mousemove\",t)},mouseup:function(t){t=Qt(this.dom,t),this.__togglePointerCapture(!1),this.trigger(\"mouseup\",t)},mouseout:function(t){Tn(this,(t=Qt(this.dom,t)).toElement||t.relatedTarget)||(this.__pointerCapturing&&(t.zrEventControl=\"no_globalout\"),this.trigger(\"mouseout\",t))},wheel:function(t){Sn=!0,t=Qt(this.dom,t),this.trigger(\"mousewheel\",t)},mousewheel:function(t){Sn||(t=Qt(this.dom,t),this.trigger(\"mousewheel\",t))},touchstart:function(t){In(t=Qt(this.dom,t)),this.__lastTouchMoment=new Date,this.handler.processGesture(t,\"start\"),Dn.mousemove.call(this,t),Dn.mousedown.call(this,t)},touchmove:function(t){In(t=Qt(this.dom,t)),this.handler.processGesture(t,\"change\"),Dn.mousemove.call(this,t)},touchend:function(t){In(t=Qt(this.dom,t)),this.handler.processGesture(t,\"end\"),Dn.mouseup.call(this,t),+new Date-+this.__lastTouchMoment<300&&Dn.click.call(this,t)},pointerdown:function(t){Dn.mousedown.call(this,t)},pointermove:function(t){Mn(t)||Dn.mousemove.call(this,t)},pointerup:function(t){Dn.mouseup.call(this,t)},pointerout:function(t){Mn(t)||Dn.mouseout.call(this,t)}};P([\"click\",\"dblclick\",\"contextmenu\"],(function(t){Dn[t]=function(e){e=Qt(this.dom,e),this.trigger(t,e)}}));var An={pointermove:function(t){Mn(t)||An.mousemove.call(this,t)},pointerup:function(t){An.mouseup.call(this,t)},mousemove:function(t){this.trigger(\"mousemove\",t)},mouseup:function(t){var e=this.__pointerCapturing;this.__togglePointerCapture(!1),this.trigger(\"mouseup\",t),e&&(t.zrEventControl=\"only_globalout\",this.trigger(\"mouseout\",t))}};function Ln(t,e){var n=e.domHandlers;a.pointerEventsSupported?P(xn.pointer,(function(i){Pn(e,i,(function(e){n[i].call(t,e)}))})):(a.touchEventsSupported&&P(xn.touch,(function(i){Pn(e,i,(function(r){n[i].call(t,r),function(t){t.touching=!0,null!=t.touchTimer&&(clearTimeout(t.touchTimer),t.touchTimer=null),t.touchTimer=setTimeout((function(){t.touching=!1,t.touchTimer=null}),700)}(e)}))})),P(xn.mouse,(function(i){Pn(e,i,(function(r){r=Jt(r),e.touching||n[i].call(t,r)}))})))}function kn(t,e){function n(n){Pn(e,n,(function(i){i=Jt(i),Tn(t,i.target)||(i=function(t,e){return Qt(t.dom,new Cn(t,e),!0)}(t,i),e.domHandlers[n].call(t,i))}),{capture:!0})}a.pointerEventsSupported?P(wn,n):a.touchEventsSupported||P(bn,n)}function Pn(t,e,n,i){t.mounted[e]=n,t.listenerOpts[e]=i,te(t.domTarget,e,n,i)}function On(t){var e,n,i,r,o=t.mounted;for(var a in o)o.hasOwnProperty(a)&&(e=t.domTarget,n=a,i=o[a],r=t.listenerOpts[a],Zt?e.removeEventListener(n,i,r):e.detachEvent(\"on\"+n,i));t.mounted={}}var Rn=function(t,e){this.mounted={},this.listenerOpts={},this.touching=!1,this.domTarget=t,this.domHandlers=e},Nn=function(t){function e(e,n){var i=t.call(this)||this;return i.__pointerCapturing=!1,i.dom=e,i.painterRoot=n,i._localHandlerScope=new Rn(e,Dn),_n&&(i._globalHandlerScope=new Rn(document,An)),Ln(i,i._localHandlerScope),i}return n(e,t),e.prototype.dispose=function(){On(this._localHandlerScope),_n&&On(this._globalHandlerScope)},e.prototype.setCursor=function(t){this.dom.style&&(this.dom.style.cursor=t||\"default\")},e.prototype.__togglePointerCapture=function(t){if(this.__mayPointerCapture=null,_n&&+this.__pointerCapturing^+t){this.__pointerCapturing=t;var e=this._globalHandlerScope;t?kn(this,e):On(e)}},e}(Ft),zn=1;\"undefined\"!=typeof window&&(zn=Math.max(window.devicePixelRatio||window.screen&&window.screen.deviceXDPI/window.screen.logicalXDPI||1,1));var En=zn,Vn=\"#333\",Bn=\"#ccc\";function Fn(){return[1,0,0,1,0,0]}function Gn(t){return t[0]=1,t[1]=0,t[2]=0,t[3]=1,t[4]=0,t[5]=0,t}function Hn(t,e){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4],t[5]=e[5],t}function Wn(t,e,n){var i=e[0]*n[0]+e[2]*n[1],r=e[1]*n[0]+e[3]*n[1],o=e[0]*n[2]+e[2]*n[3],a=e[1]*n[2]+e[3]*n[3],s=e[0]*n[4]+e[2]*n[5]+e[4],l=e[1]*n[4]+e[3]*n[5]+e[5];return t[0]=i,t[1]=r,t[2]=o,t[3]=a,t[4]=s,t[5]=l,t}function Un(t,e,n){return t[0]=e[0],t[1]=e[1],t[2]=e[2],t[3]=e[3],t[4]=e[4]+n[0],t[5]=e[5]+n[1],t}function Xn(t,e,n){var i=e[0],r=e[2],o=e[4],a=e[1],s=e[3],l=e[5],u=Math.sin(n),h=Math.cos(n);return t[0]=i*h+a*u,t[1]=-i*u+a*h,t[2]=r*h+s*u,t[3]=-r*u+h*s,t[4]=h*o+u*l,t[5]=h*l-u*o,t}function Yn(t,e,n){var i=n[0],r=n[1];return t[0]=e[0]*i,t[1]=e[1]*r,t[2]=e[2]*i,t[3]=e[3]*r,t[4]=e[4]*i,t[5]=e[5]*r,t}function Zn(t,e){var n=e[0],i=e[2],r=e[4],o=e[1],a=e[3],s=e[5],l=n*a-o*i;return l?(l=1/l,t[0]=a*l,t[1]=-o*l,t[2]=-i*l,t[3]=n*l,t[4]=(i*s-a*r)*l,t[5]=(o*r-n*s)*l,t):null}function jn(t){var e=[1,0,0,1,0,0];return Hn(e,t),e}var qn=Object.freeze({__proto__:null,create:Fn,identity:Gn,copy:Hn,mul:Wn,translate:Un,rotate:Xn,scale:Yn,invert:Zn,clone:jn}),Kn=Gn,$n=5e-5;function Jn(t){return t>$n||t<-5e-5}var Qn,ti,ei=[],ni=[],ii=[1,0,0,1,0,0],ri=Math.abs,oi=function(){function t(){}return t.prototype.setPosition=function(t){this.x=t[0],this.y=t[1]},t.prototype.setScale=function(t){this.scaleX=t[0],this.scaleY=t[1]},t.prototype.setSkew=function(t){this.skewX=t[0],this.skewY=t[1]},t.prototype.setOrigin=function(t){this.originX=t[0],this.originY=t[1]},t.prototype.needLocalTransform=function(){return Jn(this.rotation)||Jn(this.x)||Jn(this.y)||Jn(this.scaleX-1)||Jn(this.scaleY-1)},t.prototype.updateTransform=function(){var t=this.parent,e=t&&t.transform,n=this.needLocalTransform(),i=this.transform;n||e?(i=i||[1,0,0,1,0,0],n?this.getLocalTransform(i):Kn(i),e&&(n?Wn(i,t.transform,i):Hn(i,t.transform)),this.transform=i,this._resolveGlobalScaleRatio(i)):i&&Kn(i)},t.prototype._resolveGlobalScaleRatio=function(t){var e=this.globalScaleRatio;if(null!=e&&1!==e){this.getGlobalScale(ei);var n=ei[0]<0?-1:1,i=ei[1]<0?-1:1,r=((ei[0]-n)*e+n)/ei[0]||0,o=((ei[1]-i)*e+i)/ei[1]||0;t[0]*=r,t[1]*=r,t[2]*=o,t[3]*=o}this.invTransform=this.invTransform||[1,0,0,1,0,0],Zn(this.invTransform,t)},t.prototype.getLocalTransform=function(e){return t.getLocalTransform(this,e)},t.prototype.getComputedTransform=function(){for(var t=this,e=[];t;)e.push(t),t=t.parent;for(;t=e.pop();)t.updateTransform();return this.transform},t.prototype.setLocalTransform=function(t){if(t){var e=t[0]*t[0]+t[1]*t[1],n=t[2]*t[2]+t[3]*t[3],i=Math.atan2(t[1],t[0]),r=Math.PI/2+i-Math.atan2(t[3],t[2]);n=Math.sqrt(n)*Math.cos(r),e=Math.sqrt(e),this.skewX=r,this.skewY=0,this.rotation=-i,this.x=+t[4],this.y=+t[5],this.scaleX=e,this.scaleY=n,this.originX=0,this.originY=0}},t.prototype.decomposeTransform=function(){if(this.transform){var t=this.parent,e=this.transform;t&&t.transform&&(Wn(ni,t.invTransform,e),e=ni);var n=this.originX,i=this.originY;(n||i)&&(ii[4]=n,ii[5]=i,Wn(ni,e,ii),ni[4]-=n,ni[5]-=i,e=ni),this.setLocalTransform(e)}},t.prototype.getGlobalScale=function(t){var e=this.transform;return t=t||[],e?(t[0]=Math.sqrt(e[0]*e[0]+e[1]*e[1]),t[1]=Math.sqrt(e[2]*e[2]+e[3]*e[3]),e[0]<0&&(t[0]=-t[0]),e[3]<0&&(t[1]=-t[1]),t):(t[0]=1,t[1]=1,t)},t.prototype.transformCoordToLocal=function(t,e){var n=[t,e],i=this.invTransform;return i&&Rt(n,n,i),n},t.prototype.transformCoordToGlobal=function(t,e){var n=[t,e],i=this.transform;return i&&Rt(n,n,i),n},t.prototype.getLineScale=function(){var t=this.transform;return t&&ri(t[0]-1)>1e-10&&ri(t[3]-1)>1e-10?Math.sqrt(ri(t[0]*t[3]-t[2]*t[1])):1},t.getLocalTransform=function(t,e){e=e||[];var n=t.originX||0,i=t.originY||0,r=t.scaleX,o=t.scaleY,a=t.rotation||0,s=t.x,l=t.y,u=t.skewX?Math.tan(t.skewX):0,h=t.skewY?Math.tan(-t.skewY):0;return n||i?(e[4]=-n*r-u*i*o,e[5]=-i*o-h*n*r):e[4]=e[5]=0,e[0]=r,e[3]=o,e[1]=h*r,e[2]=u*o,a&&Xn(e,e,a),e[4]+=n+s,e[5]+=i+l,e},t.initDefaultProps=function(){var e=t.prototype;e.x=0,e.y=0,e.scaleX=1,e.scaleY=1,e.originX=0,e.originY=0,e.skewX=0,e.skewY=0,e.rotation=0,e.globalScaleRatio=1}(),t}(),ai=function(){function t(t,e){this.x=t||0,this.y=e||0}return t.prototype.copy=function(t){return this.x=t.x,this.y=t.y,this},t.prototype.clone=function(){return new t(this.x,this.y)},t.prototype.set=function(t,e){return this.x=t,this.y=e,this},t.prototype.equal=function(t){return t.x===this.x&&t.y===this.y},t.prototype.add=function(t){return this.x+=t.x,this.y+=t.y,this},t.prototype.scale=function(t){this.x*=t,this.y*=t},t.prototype.scaleAndAdd=function(t,e){this.x+=t.x*e,this.y+=t.y*e},t.prototype.sub=function(t){return this.x-=t.x,this.y-=t.y,this},t.prototype.dot=function(t){return this.x*t.x+this.y*t.y},t.prototype.len=function(){return Math.sqrt(this.x*this.x+this.y*this.y)},t.prototype.lenSquare=function(){return this.x*this.x+this.y*this.y},t.prototype.normalize=function(){var t=this.len();return this.x/=t,this.y/=t,this},t.prototype.distance=function(t){var e=this.x-t.x,n=this.y-t.y;return Math.sqrt(e*e+n*n)},t.prototype.distanceSquare=function(t){var e=this.x-t.x,n=this.y-t.y;return e*e+n*n},t.prototype.negate=function(){return this.x=-this.x,this.y=-this.y,this},t.prototype.transform=function(t){if(t){var e=this.x,n=this.y;return this.x=t[0]*e+t[2]*n+t[4],this.y=t[1]*e+t[3]*n+t[5],this}},t.prototype.toArray=function(t){return t[0]=this.x,t[1]=this.y,t},t.prototype.fromArray=function(t){this.x=t[0],this.y=t[1]},t.set=function(t,e,n){t.x=e,t.y=n},t.copy=function(t,e){t.x=e.x,t.y=e.y},t.len=function(t){return Math.sqrt(t.x*t.x+t.y*t.y)},t.lenSquare=function(t){return t.x*t.x+t.y*t.y},t.dot=function(t,e){return t.x*e.x+t.y*e.y},t.add=function(t,e,n){t.x=e.x+n.x,t.y=e.y+n.y},t.sub=function(t,e,n){t.x=e.x-n.x,t.y=e.y-n.y},t.scale=function(t,e,n){t.x=e.x*n,t.y=e.y*n},t.scaleAndAdd=function(t,e,n,i){t.x=e.x+n.x*i,t.y=e.y+n.y*i},t.lerp=function(t,e,n,i){var r=1-i;t.x=r*e.x+i*n.x,t.y=r*e.y+i*n.y},t}(),si=Math.min,li=Math.max,ui=new ai,hi=new ai,ci=new ai,pi=new ai,di=new ai,fi=new ai,gi=function(){function t(t,e,n,i){n<0&&(t+=n,n=-n),i<0&&(e+=i,i=-i),this.x=t,this.y=e,this.width=n,this.height=i}return t.prototype.union=function(t){var e=si(t.x,this.x),n=si(t.y,this.y);isFinite(this.x)&&isFinite(this.width)?this.width=li(t.x+t.width,this.x+this.width)-e:this.width=t.width,isFinite(this.y)&&isFinite(this.height)?this.height=li(t.y+t.height,this.y+this.height)-n:this.height=t.height,this.x=e,this.y=n},t.prototype.applyTransform=function(e){t.applyTransform(this,this,e)},t.prototype.calculateTransform=function(t){var e=this,n=t.width/e.width,i=t.height/e.height,r=[1,0,0,1,0,0];return Un(r,r,[-e.x,-e.y]),Yn(r,r,[n,i]),Un(r,r,[t.x,t.y]),r},t.prototype.intersect=function(e,n){if(!e)return!1;e instanceof t||(e=t.create(e));var i=this,r=i.x,o=i.x+i.width,a=i.y,s=i.y+i.height,l=e.x,u=e.x+e.width,h=e.y,c=e.y+e.height,p=!(o<l||u<r||s<h||c<a);if(n){var d=1/0,f=0,g=Math.abs(o-l),y=Math.abs(u-r),v=Math.abs(s-h),m=Math.abs(c-a),_=Math.min(g,y),x=Math.min(v,m);o<l||u<r?_>f&&(f=_,g<y?ai.set(fi,-g,0):ai.set(fi,y,0)):_<d&&(d=_,g<y?ai.set(di,g,0):ai.set(di,-y,0)),s<h||c<a?x>f&&(f=x,v<m?ai.set(fi,0,-v):ai.set(fi,0,m)):_<d&&(d=_,v<m?ai.set(di,0,v):ai.set(di,0,-m))}return n&&ai.copy(n,p?di:fi),p},t.prototype.contain=function(t,e){var n=this;return t>=n.x&&t<=n.x+n.width&&e>=n.y&&e<=n.y+n.height},t.prototype.clone=function(){return new t(this.x,this.y,this.width,this.height)},t.prototype.copy=function(e){t.copy(this,e)},t.prototype.plain=function(){return{x:this.x,y:this.y,width:this.width,height:this.height}},t.prototype.isFinite=function(){return isFinite(this.x)&&isFinite(this.y)&&isFinite(this.width)&&isFinite(this.height)},t.prototype.isZero=function(){return 0===this.width||0===this.height},t.create=function(e){return new t(e.x,e.y,e.width,e.height)},t.copy=function(t,e){t.x=e.x,t.y=e.y,t.width=e.width,t.height=e.height},t.applyTransform=function(e,n,i){if(i){if(i[1]<1e-5&&i[1]>-1e-5&&i[2]<1e-5&&i[2]>-1e-5){var r=i[0],o=i[3],a=i[4],s=i[5];return e.x=n.x*r+a,e.y=n.y*o+s,e.width=n.width*r,e.height=n.height*o,e.width<0&&(e.x+=e.width,e.width=-e.width),void(e.height<0&&(e.y+=e.height,e.height=-e.height))}ui.x=ci.x=n.x,ui.y=pi.y=n.y,hi.x=pi.x=n.x+n.width,hi.y=ci.y=n.y+n.height,ui.transform(i),pi.transform(i),hi.transform(i),ci.transform(i),e.x=si(ui.x,hi.x,ci.x,pi.x),e.y=si(ui.y,hi.y,ci.y,pi.y);var l=li(ui.x,hi.x,ci.x,pi.x),u=li(ui.y,hi.y,ci.y,pi.y);e.width=l-e.x,e.height=u-e.y}else e!==n&&t.copy(e,n)},t}(),yi={},vi=\"12px sans-serif\";var mi={measureText:function(t,e){return Qn||(Qn=C().getContext(\"2d\")),ti!==e&&(ti=Qn.font=e||vi),Qn.measureText(t)}};function _i(t,e){var n=yi[e=e||vi];n||(n=yi[e]=new Ae(500));var i=n.get(t);return null==i&&(i=mi.measureText(t,e).width,n.put(t,i)),i}function xi(t,e,n,i){var r=_i(t,e),o=Mi(e),a=wi(0,r,n),s=Si(0,o,i);return new gi(a,s,r,o)}function bi(t,e,n,i){var r=((t||\"\")+\"\").split(\"\\n\");if(1===r.length)return xi(r[0],e,n,i);for(var o=new gi(0,0,0,0),a=0;a<r.length;a++){var s=xi(r[a],e,n,i);0===a?o.copy(s):o.union(s)}return o}function wi(t,e,n){return\"right\"===n?t-=e:\"center\"===n&&(t-=e/2),t}function Si(t,e,n){return\"middle\"===n?t-=e/2:\"bottom\"===n&&(t-=e),t}function Mi(t){return _i(\"国\",t)}function Ii(t,e){return\"string\"==typeof t?t.lastIndexOf(\"%\")>=0?parseFloat(t)/100*e:parseFloat(t):t}function Ti(t,e,n){var i=e.position||\"inside\",r=null!=e.distance?e.distance:5,o=n.height,a=n.width,s=o/2,l=n.x,u=n.y,h=\"left\",c=\"top\";if(i instanceof Array)l+=Ii(i[0],n.width),u+=Ii(i[1],n.height),h=null,c=null;else switch(i){case\"left\":l-=r,u+=s,h=\"right\",c=\"middle\";break;case\"right\":l+=r+a,u+=s,c=\"middle\";break;case\"top\":l+=a/2,u-=r,h=\"center\",c=\"bottom\";break;case\"bottom\":l+=a/2,u+=o+r,h=\"center\";break;case\"inside\":l+=a/2,u+=s,h=\"center\",c=\"middle\";break;case\"insideLeft\":l+=r,u+=s,c=\"middle\";break;case\"insideRight\":l+=a-r,u+=s,h=\"right\",c=\"middle\";break;case\"insideTop\":l+=a/2,u+=r,h=\"center\";break;case\"insideBottom\":l+=a/2,u+=o-r,h=\"center\",c=\"bottom\";break;case\"insideTopLeft\":l+=r,u+=r;break;case\"insideTopRight\":l+=a-r,u+=r,h=\"right\";break;case\"insideBottomLeft\":l+=r,u+=o-r,c=\"bottom\";break;case\"insideBottomRight\":l+=a-r,u+=o-r,h=\"right\",c=\"bottom\"}return(t=t||{}).x=l,t.y=u,t.align=h,t.verticalAlign=c,t}var Ci=\"__zr_normal__\",Di=[\"x\",\"y\",\"scaleX\",\"scaleY\",\"originX\",\"originY\",\"rotation\",\"ignore\"],Ai={x:!0,y:!0,scaleX:!0,scaleY:!0,originX:!0,originY:!0,rotation:!0,ignore:!1},Li={},ki=new gi(0,0,0,0),Pi=function(){function t(t){this.id=x(),this.animators=[],this.currentStates=[],this.states={},this._init(t)}return t.prototype._init=function(t){this.attr(t)},t.prototype.drift=function(t,e,n){switch(this.draggable){case\"horizontal\":e=0;break;case\"vertical\":t=0}var i=this.transform;i||(i=this.transform=[1,0,0,1,0,0]),i[4]+=t,i[5]+=e,this.decomposeTransform(),this.markRedraw()},t.prototype.beforeUpdate=function(){},t.prototype.afterUpdate=function(){},t.prototype.update=function(){this.updateTransform(),this.__dirty&&this.updateInnerText()},t.prototype.updateInnerText=function(t){var e=this._textContent;if(e&&(!e.ignore||t)){this.textConfig||(this.textConfig={});var n=this.textConfig,i=n.local,r=e.attachedTransform,o=void 0,a=void 0,s=!1;r.parent=i?this:null;var l=!1;if(r.x=e.x,r.y=e.y,r.originX=e.originX,r.originY=e.originY,r.rotation=e.rotation,r.scaleX=e.scaleX,r.scaleY=e.scaleY,null!=n.position){var u=ki;n.layoutRect?u.copy(n.layoutRect):u.copy(this.getBoundingRect()),i||u.applyTransform(this.transform),this.calculateTextPosition?this.calculateTextPosition(Li,n,u):Ti(Li,n,u),r.x=Li.x,r.y=Li.y,o=Li.align,a=Li.verticalAlign;var h=n.origin;if(h&&null!=n.rotation){var c=void 0,p=void 0;\"center\"===h?(c=.5*u.width,p=.5*u.height):(c=Ii(h[0],u.width),p=Ii(h[1],u.height)),l=!0,r.originX=-r.x+c+(i?0:u.x),r.originY=-r.y+p+(i?0:u.y)}}null!=n.rotation&&(r.rotation=n.rotation);var d=n.offset;d&&(r.x+=d[0],r.y+=d[1],l||(r.originX=-d[0],r.originY=-d[1]));var f=null==n.inside?\"string\"==typeof n.position&&n.position.indexOf(\"inside\")>=0:n.inside,g=this._innerTextDefaultStyle||(this._innerTextDefaultStyle={}),y=void 0,v=void 0,m=void 0;f&&this.canBeInsideText()?(y=n.insideFill,v=n.insideStroke,null!=y&&\"auto\"!==y||(y=this.getInsideTextFill()),null!=v&&\"auto\"!==v||(v=this.getInsideTextStroke(y),m=!0)):(y=n.outsideFill,v=n.outsideStroke,null!=y&&\"auto\"!==y||(y=this.getOutsideFill()),null!=v&&\"auto\"!==v||(v=this.getOutsideStroke(y),m=!0)),(y=y||\"#000\")===g.fill&&v===g.stroke&&m===g.autoStroke&&o===g.align&&a===g.verticalAlign||(s=!0,g.fill=y,g.stroke=v,g.autoStroke=m,g.align=o,g.verticalAlign=a,e.setDefaultTextStyle(g)),e.__dirty|=1,s&&e.dirtyStyle(!0)}},t.prototype.canBeInsideText=function(){return!0},t.prototype.getInsideTextFill=function(){return\"#fff\"},t.prototype.getInsideTextStroke=function(t){return\"#000\"},t.prototype.getOutsideFill=function(){return this.__zr&&this.__zr.isDarkMode()?Bn:Vn},t.prototype.getOutsideStroke=function(t){var e=this.__zr&&this.__zr.getBackgroundColor(),n=\"string\"==typeof e&&He(e);n||(n=[255,255,255,1]);for(var i=n[3],r=this.__zr.isDarkMode(),o=0;o<3;o++)n[o]=n[o]*i+(r?0:255)*(1-i);return n[3]=1,Je(n,\"rgba\")},t.prototype.traverse=function(t,e){},t.prototype.attrKV=function(t,e){\"textConfig\"===t?this.setTextConfig(e):\"textContent\"===t?this.setTextContent(e):\"clipPath\"===t?this.setClipPath(e):\"extra\"===t?(this.extra=this.extra||{},I(this.extra,e)):this[t]=e},t.prototype.hide=function(){this.ignore=!0,this.markRedraw()},t.prototype.show=function(){this.ignore=!1,this.markRedraw()},t.prototype.attr=function(t,e){if(\"string\"==typeof t)this.attrKV(t,e);else if(X(t))for(var n=E(t),i=0;i<n.length;i++){var r=n[i];this.attrKV(r,t[r])}return this.markRedraw(),this},t.prototype.saveCurrentToNormalState=function(t){this._innerSaveToNormal(t);for(var e=this._normalState,n=0;n<this.animators.length;n++){var i=this.animators[n],r=i.__fromStateTransition;if(!r||r===Ci){var o=i.targetName,a=o?e[o]:e;i.saveFinalToTarget(a)}}},t.prototype._innerSaveToNormal=function(t){var e=this._normalState;e||(e=this._normalState={}),t.textConfig&&!e.textConfig&&(e.textConfig=this.textConfig),this._savePrimaryToNormal(t,e,Di)},t.prototype._savePrimaryToNormal=function(t,e,n){for(var i=0;i<n.length;i++){var r=n[i];null==t[r]||r in e||(e[r]=this[r])}},t.prototype.hasState=function(){return this.currentStates.length>0},t.prototype.getState=function(t){return this.states[t]},t.prototype.ensureState=function(t){var e=this.states;return e[t]||(e[t]={}),e[t]},t.prototype.clearStates=function(t){this.useState(Ci,!1,t)},t.prototype.useState=function(t,e,n,i){var r=t===Ci;if(this.hasState()||!r){var o=this.currentStates,a=this.stateTransition;if(!(D(o,t)>=0)||!e&&1!==o.length){var s;if(this.stateProxy&&!r&&(s=this.stateProxy(t)),s||(s=this.states&&this.states[t]),s||r){r||this.saveCurrentToNormalState(s);var l=!!(s&&s.hoverLayer||i);l&&this._toggleHoverLayerFlag(!0),this._applyStateObj(t,s,this._normalState,e,!n&&!this.__inHover&&a&&a.duration>0,a);var u=this._textContent,h=this._textGuide;return u&&u.useState(t,e,n,l),h&&h.useState(t,e,n,l),r?(this.currentStates=[],this._normalState={}):e?this.currentStates.push(t):this.currentStates=[t],this._updateAnimationTargets(),this.markRedraw(),!l&&this.__inHover&&(this._toggleHoverLayerFlag(!1),this.__dirty&=-2),s}b(\"State \"+t+\" not exists.\")}}},t.prototype.useStates=function(t,e,n){if(t.length){var i=[],r=this.currentStates,o=t.length,a=o===r.length;if(a)for(var s=0;s<o;s++)if(t[s]!==r[s]){a=!1;break}if(a)return;for(s=0;s<o;s++){var l=t[s],u=void 0;this.stateProxy&&(u=this.stateProxy(l,t)),u||(u=this.states[l]),u&&i.push(u)}var h=i[o-1],c=!!(h&&h.hoverLayer||n);c&&this._toggleHoverLayerFlag(!0);var p=this._mergeStates(i),d=this.stateTransition;this.saveCurrentToNormalState(p),this._applyStateObj(t.join(\",\"),p,this._normalState,!1,!e&&!this.__inHover&&d&&d.duration>0,d);var f=this._textContent,g=this._textGuide;f&&f.useStates(t,e,c),g&&g.useStates(t,e,c),this._updateAnimationTargets(),this.currentStates=t.slice(),this.markRedraw(),!c&&this.__inHover&&(this._toggleHoverLayerFlag(!1),this.__dirty&=-2)}else this.clearStates()},t.prototype._updateAnimationTargets=function(){for(var t=0;t<this.animators.length;t++){var e=this.animators[t];e.targetName&&e.changeTarget(this[e.targetName])}},t.prototype.removeState=function(t){var e=D(this.currentStates,t);if(e>=0){var n=this.currentStates.slice();n.splice(e,1),this.useStates(n)}},t.prototype.replaceState=function(t,e,n){var i=this.currentStates.slice(),r=D(i,t),o=D(i,e)>=0;r>=0?o?i.splice(r,1):i[r]=e:n&&!o&&i.push(e),this.useStates(i)},t.prototype.toggleState=function(t,e){e?this.useState(t,!0):this.removeState(t)},t.prototype._mergeStates=function(t){for(var e,n={},i=0;i<t.length;i++){var r=t[i];I(n,r),r.textConfig&&I(e=e||{},r.textConfig)}return e&&(n.textConfig=e),n},t.prototype._applyStateObj=function(t,e,n,i,r,o){var a=!(e&&i);e&&e.textConfig?(this.textConfig=I({},i?this.textConfig:n.textConfig),I(this.textConfig,e.textConfig)):a&&n.textConfig&&(this.textConfig=n.textConfig);for(var s={},l=!1,u=0;u<Di.length;u++){var h=Di[u],c=r&&Ai[h];e&&null!=e[h]?c?(l=!0,s[h]=e[h]):this[h]=e[h]:a&&null!=n[h]&&(c?(l=!0,s[h]=n[h]):this[h]=n[h])}if(!r)for(u=0;u<this.animators.length;u++){var p=this.animators[u],d=p.targetName;p.__changeFinalValue(d?(e||n)[d]:e||n)}l&&this._transitionState(t,s,o)},t.prototype._attachComponent=function(t){if(t.__zr&&!t.__hostTarget)throw new Error(\"Text element has been added to zrender.\");if(t===this)throw new Error(\"Recursive component attachment.\");var e=this.__zr;e&&t.addSelfToZr(e),t.__zr=e,t.__hostTarget=this},t.prototype._detachComponent=function(t){t.__zr&&t.removeSelfFromZr(t.__zr),t.__zr=null,t.__hostTarget=null},t.prototype.getClipPath=function(){return this._clipPath},t.prototype.setClipPath=function(t){this._clipPath&&this._clipPath!==t&&this.removeClipPath(),this._attachComponent(t),this._clipPath=t,this.markRedraw()},t.prototype.removeClipPath=function(){var t=this._clipPath;t&&(this._detachComponent(t),this._clipPath=null,this.markRedraw())},t.prototype.getTextContent=function(){return this._textContent},t.prototype.setTextContent=function(t){var e=this._textContent;if(e!==t){if(e&&e!==t&&this.removeTextContent(),t.__zr&&!t.__hostTarget)throw new Error(\"Text element has been added to zrender.\");t.attachedTransform=new oi,this._attachComponent(t),this._textContent=t,this.markRedraw()}},t.prototype.setTextConfig=function(t){this.textConfig||(this.textConfig={}),I(this.textConfig,t),this.markRedraw()},t.prototype.removeTextConfig=function(){this.textConfig=null,this.markRedraw()},t.prototype.removeTextContent=function(){var t=this._textContent;t&&(t.attachedTransform=null,this._detachComponent(t),this._textContent=null,this._innerTextDefaultStyle=null,this.markRedraw())},t.prototype.getTextGuideLine=function(){return this._textGuide},t.prototype.setTextGuideLine=function(t){this._textGuide&&this._textGuide!==t&&this.removeTextGuideLine(),this._attachComponent(t),this._textGuide=t,this.markRedraw()},t.prototype.removeTextGuideLine=function(){var t=this._textGuide;t&&(this._detachComponent(t),this._textGuide=null,this.markRedraw())},t.prototype.markRedraw=function(){this.__dirty|=1;var t=this.__zr;t&&(this.__inHover?t.refreshHover():t.refresh()),this.__hostTarget&&this.__hostTarget.markRedraw()},t.prototype.dirty=function(){this.markRedraw()},t.prototype._toggleHoverLayerFlag=function(t){this.__inHover=t;var e=this._textContent,n=this._textGuide;e&&(e.__inHover=t),n&&(n.__inHover=t)},t.prototype.addSelfToZr=function(t){this.__zr=t;var e=this.animators;if(e)for(var n=0;n<e.length;n++)t.animation.addAnimator(e[n]);this._clipPath&&this._clipPath.addSelfToZr(t),this._textContent&&this._textContent.addSelfToZr(t),this._textGuide&&this._textGuide.addSelfToZr(t)},t.prototype.removeSelfFromZr=function(t){this.__zr=null;var e=this.animators;if(e)for(var n=0;n<e.length;n++)t.animation.removeAnimator(e[n]);this._clipPath&&this._clipPath.removeSelfFromZr(t),this._textContent&&this._textContent.removeSelfFromZr(t),this._textGuide&&this._textGuide.removeSelfFromZr(t)},t.prototype.animate=function(t,e){var n=t?this[t]:this;if(n){var i=new vn(n,e);return this.addAnimator(i,t),i}b('Property \"'+t+'\" is not existed in element '+this.id)},t.prototype.addAnimator=function(t,e){var n=this.__zr,i=this;t.during((function(){i.updateDuringAnimation(e)})).done((function(){var e=i.animators,n=D(e,t);n>=0&&e.splice(n,1)})),this.animators.push(t),n&&n.animation.addAnimator(t),n&&n.wakeUp()},t.prototype.updateDuringAnimation=function(t){this.markRedraw()},t.prototype.stopAnimation=function(t,e){for(var n=this.animators,i=n.length,r=[],o=0;o<i;o++){var a=n[o];t&&t!==a.scope?r.push(a):a.stop(e)}return this.animators=r,this},t.prototype.animateTo=function(t,e,n){Oi(this,t,e,n)},t.prototype.animateFrom=function(t,e,n){Oi(this,t,e,n,!0)},t.prototype._transitionState=function(t,e,n,i){for(var r=Oi(this,e,n,i),o=0;o<r.length;o++)r[o].__fromStateTransition=t},t.prototype.getBoundingRect=function(){return null},t.prototype.getPaintRect=function(){return null},t.initDefaultProps=function(){var e=t.prototype;e.type=\"element\",e.name=\"\",e.ignore=!1,e.silent=!1,e.isGroup=!1,e.draggable=!1,e.dragging=!1,e.ignoreClip=!1,e.__inHover=!1,e.__dirty=1;var n={};function i(t,e,i){n[t+e+i]||(console.warn(\"DEPRECATED: '\"+t+\"' has been deprecated. use '\"+e+\"', '\"+i+\"' instead\"),n[t+e+i]=!0)}function r(t,n,r,o){function a(t,e){Object.defineProperty(e,0,{get:function(){return t[r]},set:function(e){t[r]=e}}),Object.defineProperty(e,1,{get:function(){return t[o]},set:function(e){t[o]=e}})}Object.defineProperty(e,t,{get:function(){(i(t,r,o),this[n])||a(this,this[n]=[]);return this[n]},set:function(e){i(t,r,o),this[r]=e[0],this[o]=e[1],this[n]=e,a(this,e)}})}Object.defineProperty&&(!a.browser.ie||a.browser.version>8)&&(r(\"position\",\"_legacyPos\",\"x\",\"y\"),r(\"scale\",\"_legacyScale\",\"scaleX\",\"scaleY\"),r(\"origin\",\"_legacyOrigin\",\"originX\",\"originY\"))}(),t}();function Oi(t,e,n,i,r){var o=[];zi(t,\"\",t,e,n=n||{},i,o,r);var a=o.length,s=!1,l=n.done,u=n.aborted,h=function(){s=!0,--a<=0&&(s?l&&l():u&&u())},c=function(){--a<=0&&(s?l&&l():u&&u())};a||l&&l(),o.length>0&&n.during&&o[0].during((function(t,e){n.during(e)}));for(var p=0;p<o.length;p++){var d=o[p];h&&d.done(h),c&&d.aborted(c),d.start(n.easing,n.force)}return o}function Ri(t,e,n){for(var i=0;i<n;i++)t[i]=e[i]}function Ni(t,e,n){if(k(e[n]))if(k(t[n])||(t[n]=[]),Z(e[n])){var i=e[n].length;t[n].length!==i&&(t[n]=new e[n].constructor(i),Ri(t[n],e[n],i))}else{var r=e[n],o=t[n],a=r.length;if(k(r[0]))for(var s=r[0].length,l=0;l<a;l++)o[l]?Ri(o[l],r[l],s):o[l]=Array.prototype.slice.call(r[l]);else Ri(o,r,a);o.length=r.length}else t[n]=e[n]}function zi(t,e,n,i,r,o,a,s){for(var l=[],u=[],h=E(i),c=r.duration,p=r.delay,d=r.additive,f=r.setToFinal,g=!X(o),y=0;y<h.length;y++){if(null!=n[I=h[y]]&&null!=i[I]&&(g||o[I]))if(X(i[I])&&!k(i[I])){if(e){s||(n[I]=i[I],t.updateDuringAnimation(e));continue}zi(t,I,n[I],i[I],r,o&&o[I],a,s)}else l.push(I),u.push(I);else s||(n[I]=i[I],t.updateDuringAnimation(e),u.push(I))}var v=l.length;if(v>0||r.force&&!a.length){for(var m=t.animators,_=[],x=0;x<m.length;x++)m[x].targetName===e&&_.push(m[x]);if(!d&&_.length)for(x=0;x<_.length;x++){if(_[x].stopTracks(u)){var b=D(m,_[x]);m.splice(b,1)}}var w=void 0,S=void 0,M=void 0;if(s){S={},f&&(w={});for(x=0;x<v;x++){S[I=l[x]]=n[I],f?w[I]=i[I]:n[I]=i[I]}}else if(f){M={};for(x=0;x<v;x++){var I;M[I=l[x]]=cn(n[I]),Ni(n,i,I)}}var T=new vn(n,!1,d?_:null);T.targetName=e,r.scope&&(T.scope=r.scope),f&&w&&T.whenWithKeys(0,w,l),M&&T.whenWithKeys(0,M,l),T.whenWithKeys(null==c?500:c,s?S:i,l).delay(p||0),t.addAnimator(T,e),a.push(T)}}L(Pi,Ft),L(Pi,oi);var Ei=function(t){function e(e){var n=t.call(this)||this;return n.isGroup=!0,n._children=[],n.attr(e),n}return n(e,t),e.prototype.childrenRef=function(){return this._children},e.prototype.children=function(){return this._children.slice()},e.prototype.childAt=function(t){return this._children[t]},e.prototype.childOfName=function(t){for(var e=this._children,n=0;n<e.length;n++)if(e[n].name===t)return e[n]},e.prototype.childCount=function(){return this._children.length},e.prototype.add=function(t){if(t&&(t!==this&&t.parent!==this&&(this._children.push(t),this._doAdd(t)),t.__hostTarget))throw\"This elemenet has been used as an attachment\";return this},e.prototype.addBefore=function(t,e){if(t&&t!==this&&t.parent!==this&&e&&e.parent===this){var n=this._children,i=n.indexOf(e);i>=0&&(n.splice(i,0,t),this._doAdd(t))}return this},e.prototype.replaceAt=function(t,e){var n=this._children,i=n[e];if(t&&t!==this&&t.parent!==this&&t!==i){n[e]=t,i.parent=null;var r=this.__zr;r&&i.removeSelfFromZr(r),this._doAdd(t)}return this},e.prototype._doAdd=function(t){t.parent&&t.parent.remove(t),t.parent=this;var e=this.__zr;e&&e!==t.__zr&&t.addSelfToZr(e),e&&e.refresh()},e.prototype.remove=function(t){var e=this.__zr,n=this._children,i=D(n,t);return i<0||(n.splice(i,1),t.parent=null,e&&t.removeSelfFromZr(e),e&&e.refresh()),this},e.prototype.removeAll=function(){for(var t=this._children,e=this.__zr,n=0;n<t.length;n++){var i=t[n];e&&i.removeSelfFromZr(e),i.parent=null}return t.length=0,this},e.prototype.eachChild=function(t,e){for(var n=this._children,i=0;i<n.length;i++){var r=n[i];t.call(e,r,i)}return this},e.prototype.traverse=function(t,e){for(var n=0;n<this._children.length;n++){var i=this._children[n],r=t.call(e,i);i.isGroup&&!r&&i.traverse(t,e)}return this},e.prototype.addSelfToZr=function(e){t.prototype.addSelfToZr.call(this,e);for(var n=0;n<this._children.length;n++){this._children[n].addSelfToZr(e)}},e.prototype.removeSelfFromZr=function(e){t.prototype.removeSelfFromZr.call(this,e);for(var n=0;n<this._children.length;n++){this._children[n].removeSelfFromZr(e)}},e.prototype.getBoundingRect=function(t){for(var e=new gi(0,0,0,0),n=t||this._children,i=[],r=null,o=0;o<n.length;o++){var a=n[o];if(!a.ignore&&!a.invisible){var s=a.getBoundingRect(),l=a.getLocalTransform(i);l?(gi.applyTransform(e,s,l),(r=r||e.clone()).union(e)):(r=r||s.clone()).union(s)}}return r||e},e}(Pi);Ei.prototype.type=\"group\";\n/*!\n    * ZRender, a high performance 2d drawing library.\n    *\n    * Copyright (c) 2013, Baidu Inc.\n    * All rights reserved.\n    *\n    * LICENSE\n    * https://github.com/ecomfe/zrender/blob/master/LICENSE.txt\n    */\nvar Vi=!a.canvasSupported,Bi={},Fi={};var Gi=function(){function t(t,e,n){var i=this;this._sleepAfterStill=10,this._stillFrameAccum=0,this._needsRefresh=!0,this._needsRefreshHover=!0,this._darkMode=!1,n=n||{},this.dom=e,this.id=t;var r=new Se,o=n.renderer||\"canvas\";if(Vi)throw new Error(\"IE8 support has been dropped since 5.0\");if(Bi[o]||(o=E(Bi)[0]),!Bi[o])throw new Error(\"Renderer '\"+o+\"' is not imported. Please import it first.\");n.useDirtyRect=null!=n.useDirtyRect&&n.useDirtyRect;var s=new Bi[o](e,r,n,t);this.storage=r,this.painter=s;var l=a.node||a.worker?null:new Nn(s.getViewportRoot(),s.root);this.handler=new ce(r,s,l,s.root),this.animation=new mn({stage:{update:function(){return i._flush(!0)}}}),this.animation.start()}return t.prototype.add=function(t){t&&(this.storage.addRoot(t),t.addSelfToZr(this),this.refresh())},t.prototype.remove=function(t){t&&(this.storage.delRoot(t),t.removeSelfFromZr(this),this.refresh())},t.prototype.configLayer=function(t,e){this.painter.configLayer&&this.painter.configLayer(t,e),this.refresh()},t.prototype.setBackgroundColor=function(t){this.painter.setBackgroundColor&&this.painter.setBackgroundColor(t),this.refresh(),this._backgroundColor=t,this._darkMode=function(t){if(!t)return!1;if(\"string\"==typeof t)return Qe(t,1)<.4;if(t.colorStops){for(var e=t.colorStops,n=0,i=e.length,r=0;r<i;r++)n+=Qe(e[r].color,1);return(n/=i)<.4}return!1}(t)},t.prototype.getBackgroundColor=function(){return this._backgroundColor},t.prototype.setDarkMode=function(t){this._darkMode=t},t.prototype.isDarkMode=function(){return this._darkMode},t.prototype.refreshImmediately=function(t){t||this.animation.update(!0),this._needsRefresh=!1,this.painter.refresh(),this._needsRefresh=!1},t.prototype.refresh=function(){this._needsRefresh=!0,this.animation.start()},t.prototype.flush=function(){this._flush(!1)},t.prototype._flush=function(t){var e,n=(new Date).getTime();this._needsRefresh&&(e=!0,this.refreshImmediately(t)),this._needsRefreshHover&&(e=!0,this.refreshHoverImmediately());var i=(new Date).getTime();e?(this._stillFrameAccum=0,this.trigger(\"rendered\",{elapsedTime:i-n})):this._sleepAfterStill>0&&(this._stillFrameAccum++,this._stillFrameAccum>this._sleepAfterStill&&this.animation.stop())},t.prototype.setSleepAfterStill=function(t){this._sleepAfterStill=t},t.prototype.wakeUp=function(){this.animation.start(),this._stillFrameAccum=0},t.prototype.addHover=function(t){},t.prototype.removeHover=function(t){},t.prototype.clearHover=function(){},t.prototype.refreshHover=function(){this._needsRefreshHover=!0},t.prototype.refreshHoverImmediately=function(){this._needsRefreshHover=!1,this.painter.refreshHover&&\"canvas\"===this.painter.getType()&&this.painter.refreshHover()},t.prototype.resize=function(t){t=t||{},this.painter.resize(t.width,t.height),this.handler.resize()},t.prototype.clearAnimation=function(){this.animation.clear()},t.prototype.getWidth=function(){return this.painter.getWidth()},t.prototype.getHeight=function(){return this.painter.getHeight()},t.prototype.pathToImage=function(t,e){if(this.painter.pathToImage)return this.painter.pathToImage(t,e)},t.prototype.setCursorStyle=function(t){this.handler.setCursorStyle(t)},t.prototype.findHover=function(t,e){return this.handler.findHover(t,e)},t.prototype.on=function(t,e,n){return this.handler.on(t,e,n),this},t.prototype.off=function(t,e){this.handler.off(t,e)},t.prototype.trigger=function(t,e){this.handler.trigger(t,e)},t.prototype.clear=function(){for(var t=this.storage.getRoots(),e=0;e<t.length;e++)t[e]instanceof Ei&&t[e].removeSelfFromZr(this);this.storage.delAllRoots(),this.painter.clear()},t.prototype.dispose=function(){var t;this.animation.stop(),this.clear(),this.storage.dispose(),this.painter.dispose(),this.handler.dispose(),this.animation=this.storage=this.painter=this.handler=null,t=this.id,delete Fi[t]},t}();function Hi(t,e){var n=new Gi(x(),t,e);return Fi[n.id]=n,n}function Wi(t,e){Bi[t]=e}var Ui=Object.freeze({__proto__:null,init:Hi,dispose:function(t){t.dispose()},disposeAll:function(){for(var t in Fi)Fi.hasOwnProperty(t)&&Fi[t].dispose();Fi={}},getInstance:function(t){return Fi[t]},registerPainter:Wi,version:\"5.1.1\"}),Xi=1e-4;function Yi(t,e,n,i){var r=e[0],o=e[1],a=n[0],s=n[1],l=o-r,u=s-a;if(0===l)return 0===u?a:(a+s)/2;if(i)if(l>0){if(t<=r)return a;if(t>=o)return s}else{if(t>=r)return a;if(t<=o)return s}else{if(t===r)return a;if(t===o)return s}return(t-r)/l*u+a}function Zi(t,e){switch(t){case\"center\":case\"middle\":t=\"50%\";break;case\"left\":case\"top\":t=\"0%\";break;case\"right\":case\"bottom\":t=\"100%\"}return\"string\"==typeof t?(n=t,n.replace(/^\\s+|\\s+$/g,\"\")).match(/%$/)?parseFloat(t)/100*e:parseFloat(t):null==t?NaN:+t;var n}function ji(t,e,n){return null==e&&(e=10),e=Math.min(Math.max(0,e),20),t=(+t).toFixed(e),n?t:+t}function qi(t){return t.sort((function(t,e){return t-e})),t}function Ki(t){if(t=+t,isNaN(t))return 0;if(t>1e-14)for(var e=1,n=0;n<15;n++,e*=10)if(Math.round(t*e)/e===t)return n;return $i(t)}function $i(t){var e=t.toString().toLowerCase(),n=e.indexOf(\"e\"),i=n>0?+e.slice(n+1):0,r=n>0?n:e.length,o=e.indexOf(\".\"),a=o<0?0:r-1-o;return Math.max(0,a-i)}function Ji(t,e){var n=Math.log,i=Math.LN10,r=Math.floor(n(t[1]-t[0])/i),o=Math.round(n(Math.abs(e[1]-e[0]))/i),a=Math.min(Math.max(-r+o,0),20);return isFinite(a)?a:20}function Qi(t,e,n){if(!t[e])return 0;var i=R(t,(function(t,e){return t+(isNaN(e)?0:e)}),0);if(0===i)return 0;for(var r=Math.pow(10,n),o=O(t,(function(t){return(isNaN(t)?0:t)/i*r*100})),a=100*r,s=O(o,(function(t){return Math.floor(t)})),l=R(s,(function(t,e){return t+e}),0),u=O(o,(function(t,e){return t-s[e]}));l<a;){for(var h=Number.NEGATIVE_INFINITY,c=null,p=0,d=u.length;p<d;++p)u[p]>h&&(h=u[p],c=p);++s[c],u[c]=0,++l}return s[e]/r}function tr(t,e){var n=Math.max(Ki(t),Ki(e)),i=t+e;return n>20?i:ji(i,n)}var er=9007199254740991;function nr(t){var e=2*Math.PI;return(t%e+e)%e}function ir(t){return t>-1e-4&&t<Xi}var rr=/^(?:(\\d{4})(?:[-\\/](\\d{1,2})(?:[-\\/](\\d{1,2})(?:[T ](\\d{1,2})(?::(\\d{1,2})(?::(\\d{1,2})(?:[.,](\\d+))?)?)?(Z|[\\+\\-]\\d\\d:?\\d\\d)?)?)?)?)?$/;function or(t){if(t instanceof Date)return t;if(\"string\"==typeof t){var e=rr.exec(t);if(!e)return new Date(NaN);if(e[8]){var n=+e[4]||0;return\"Z\"!==e[8].toUpperCase()&&(n-=+e[8].slice(0,3)),new Date(Date.UTC(+e[1],+(e[2]||1)-1,+e[3]||1,n,+(e[5]||0),+e[6]||0,+e[7]||0))}return new Date(+e[1],+(e[2]||1)-1,+e[3]||1,+e[4]||0,+(e[5]||0),+e[6]||0,+e[7]||0)}return null==t?new Date(NaN):new Date(Math.round(t))}function ar(t){return Math.pow(10,sr(t))}function sr(t){if(0===t)return 0;var e=Math.floor(Math.log(t)/Math.LN10);return t/Math.pow(10,e)>=10&&e++,e}function lr(t,e){var n=sr(t),i=Math.pow(10,n),r=t/i;return t=(e?r<1.5?1:r<2.5?2:r<4?3:r<7?5:10:r<1?1:r<2?2:r<3?3:r<5?5:10)*i,n>=-20?+t.toFixed(n<0?-n:0):t}function ur(t,e){var n=(t.length-1)*e+1,i=Math.floor(n),r=+t[i-1],o=n-i;return o?r+o*(t[i]-r):r}function hr(t){t.sort((function(t,e){return s(t,e,0)?-1:1}));for(var e=-1/0,n=1,i=0;i<t.length;){for(var r=t[i].interval,o=t[i].close,a=0;a<2;a++)r[a]<=e&&(r[a]=e,o[a]=a?1:1-n),e=r[a],n=o[a];r[0]===r[1]&&o[0]*o[1]!=1?t.splice(i,1):i++}return t;function s(t,e,n){return t.interval[n]<e.interval[n]||t.interval[n]===e.interval[n]&&(t.close[n]-e.close[n]==(n?-1:1)||!n&&s(t,e,1))}}function cr(t){var e=parseFloat(t);return e==t&&(0!==e||\"string\"!=typeof t||t.indexOf(\"x\")<=0)?e:NaN}function pr(t){return!isNaN(cr(t))}function dr(){return Math.round(9*Math.random())}function fr(t,e){return 0===e?t:fr(e,t%e)}function gr(t,e){return null==t?e:null==e?t:t*e/fr(t,e)}\"undefined\"!=typeof console&&console.warn&&console.log;function yr(t){0}function vr(t){throw new Error(t)}var mr=\"series\\0\",_r=\"\\0_ec_\\0\";function xr(t){return t instanceof Array?t:null==t?[]:[t]}function br(t,e,n){if(t){t[e]=t[e]||{},t.emphasis=t.emphasis||{},t.emphasis[e]=t.emphasis[e]||{};for(var i=0,r=n.length;i<r;i++){var o=n[i];!t.emphasis[e].hasOwnProperty(o)&&t[e].hasOwnProperty(o)&&(t.emphasis[e][o]=t[e][o])}}}var wr=[\"fontStyle\",\"fontWeight\",\"fontSize\",\"fontFamily\",\"rich\",\"tag\",\"color\",\"textBorderColor\",\"textBorderWidth\",\"width\",\"height\",\"lineHeight\",\"align\",\"verticalAlign\",\"baseline\",\"shadowColor\",\"shadowBlur\",\"shadowOffsetX\",\"shadowOffsetY\",\"textShadowColor\",\"textShadowBlur\",\"textShadowOffsetX\",\"textShadowOffsetY\",\"backgroundColor\",\"borderColor\",\"borderWidth\",\"borderRadius\",\"padding\"];function Sr(t){return!X(t)||F(t)||t instanceof Date?t:t.value}function Mr(t,e,n){var i=\"normalMerge\"===n,r=\"replaceMerge\"===n,o=\"replaceAll\"===n;t=t||[],e=(e||[]).slice();var a=ht();P(e,(function(t,n){X(t)||(e[n]=null)}));var s,l,u=function(t,e,n){var i=[];if(\"replaceAll\"===n)return i;for(var r=0;r<t.length;r++){var o=t[r];o&&null!=o.id&&e.set(o.id,r),i.push({existing:\"replaceMerge\"===n||Ar(o)?null:o,newOption:null,keyInfo:null,brandNew:null})}return i}(t,a,n);return(i||r)&&function(t,e,n,i){P(i,(function(r,o){if(r&&null!=r.id){var a=Tr(r.id),s=n.get(a);if(null!=s){var l=t[s];rt(!l.newOption,'Duplicated option on id \"'+a+'\".'),l.newOption=r,l.existing=e[s],i[o]=null}}}))}(u,t,a,e),i&&function(t,e){P(e,(function(n,i){if(n&&null!=n.name)for(var r=0;r<t.length;r++){var o=t[r].existing;if(!t[r].newOption&&o&&(null==o.id||null==n.id)&&!Ar(n)&&!Ar(o)&&Ir(\"name\",o,n))return t[r].newOption=n,void(e[i]=null)}}))}(u,e),i||r?function(t,e,n){P(e,(function(e){if(e){for(var i,r=0;(i=t[r])&&(i.newOption||Ar(i.existing)||i.existing&&null!=e.id&&!Ir(\"id\",e,i.existing));)r++;i?(i.newOption=e,i.brandNew=n):t.push({newOption:e,brandNew:n,existing:null,keyInfo:null}),r++}}))}(u,e,r):o&&function(t,e){P(e,(function(e){t.push({newOption:e,brandNew:!0,existing:null,keyInfo:null})}))}(u,e),s=u,l=ht(),P(s,(function(t){var e=t.existing;e&&l.set(e.id,t)})),P(s,(function(t){var e=t.newOption;rt(!e||null==e.id||!l.get(e.id)||l.get(e.id)===t,\"id duplicates: \"+(e&&e.id)),e&&null!=e.id&&l.set(e.id,t),!t.keyInfo&&(t.keyInfo={})})),P(s,(function(t,e){var n=t.existing,i=t.newOption,r=t.keyInfo;if(X(i)){if(r.name=null!=i.name?Tr(i.name):n?n.name:mr+e,n)r.id=Tr(n.id);else if(null!=i.id)r.id=Tr(i.id);else{var o=0;do{r.id=\"\\0\"+r.name+\"\\0\"+o++}while(l.get(r.id))}l.set(r.id,t)}})),u}function Ir(t,e,n){var i=Cr(e[t],null),r=Cr(n[t],null);return null!=i&&null!=r&&i===r}function Tr(t){return Cr(t,\"\")}function Cr(t,e){if(null==t)return e;var n=typeof t;return\"string\"===n?t:\"number\"===n||W(t)?t+\"\":e}function Dr(t){var e=t.name;return!(!e||!e.indexOf(mr))}function Ar(t){return t&&null!=t.id&&0===Tr(t.id).indexOf(_r)}function Lr(t,e){return null!=e.dataIndexInside?e.dataIndexInside:null!=e.dataIndex?F(e.dataIndex)?O(e.dataIndex,(function(e){return t.indexOfRawIndex(e)})):t.indexOfRawIndex(e.dataIndex):null!=e.name?F(e.name)?O(e.name,(function(e){return t.indexOfName(e)})):t.indexOfName(e.name):void 0}function kr(){var t=\"__ec_inner_\"+Pr++;return function(e){return e[t]||(e[t]={})}}var Pr=dr();function Or(t,e,n){var i=Rr(e,n),r=i.mainTypeSpecified,o=i.queryOptionMap,a=i.others,s=n?n.defaultMainType:null;return!r&&s&&o.set(s,{}),o.each((function(e,i){var r=Er(t,i,e,{useDefault:s===i,enableAll:!n||null==n.enableAll||n.enableAll,enableNone:!n||null==n.enableNone||n.enableNone});a[i+\"Models\"]=r.models,a[i+\"Model\"]=r.models[0]})),a}function Rr(t,e){var n;if(H(t)){var i={};i[t+\"Index\"]=0,n=i}else n=t;var r=ht(),o={},a=!1;return P(n,(function(t,n){if(\"dataIndex\"!==n&&\"dataIndexInside\"!==n){var i=n.match(/^(\\w+)(Index|Id|Name)$/)||[],s=i[1],l=(i[2]||\"\").toLowerCase();if(s&&l&&!(e&&e.includeMainTypes&&D(e.includeMainTypes,s)<0))a=a||!!s,(r.get(s)||r.set(s,{}))[l]=t}else o[n]=t})),{mainTypeSpecified:a,queryOptionMap:r,others:o}}var Nr={useDefault:!0,enableAll:!1,enableNone:!1},zr={useDefault:!1,enableAll:!0,enableNone:!0};function Er(t,e,n,i){i=i||Nr;var r=n.index,o=n.id,a=n.name,s={models:null,specified:null!=r||null!=o||null!=a};if(!s.specified){var l=void 0;return s.models=i.useDefault&&(l=t.getComponent(e))?[l]:[],s}return\"none\"===r||!1===r?(rt(i.enableNone,'`\"none\"` or `false` is not a valid value on index option.'),s.models=[],s):(\"all\"===r&&(rt(i.enableAll,'`\"all\"` is not a valid value on index option.'),r=o=a=null),s.models=t.queryComponents({mainType:e,index:r,id:o,name:a}),s)}function Vr(t,e,n){t.setAttribute?t.setAttribute(e,n):t[e]=n}function Br(t,e){var n=ht(),i=[];return P(t,(function(t){var r=e(t);(n.get(r)||(i.push(r),n.set(r,[]))).push(t)})),{keys:i,buckets:n}}function Fr(t,e,n,i,r){var o=null==e||\"auto\"===e;if(null==i)return i;if(\"number\"==typeof i)return ji(d=nn(n||0,i,r),o?Math.max(Ki(n||0),Ki(i)):e);if(\"string\"==typeof i)return r<1?n:i;for(var a=[],s=n,l=i,u=Math.max(s?s.length:0,l.length),h=0;h<u;++h){if(\"ordinal\"===t.getDimensionInfo(h).type)a[h]=(r<1&&s?s:l)[h];else{var c=s&&s[h]?s[h]:0,p=l[h],d=nn(c,p,r);a[h]=ji(d,o?Math.max(Ki(c),Ki(p)):e)}}return a}var Gr=\"___EC__COMPONENT__CONTAINER___\",Hr=\"___EC__EXTENDED_CLASS___\";function Wr(t){var e={main:\"\",sub:\"\"};if(t){var n=t.split(\".\");e.main=n[0]||\"\",e.sub=n[1]||\"\"}return e}function Ur(t,e){t.$constructor=t,t.extend=function(t){var e=this;function n(){for(var i=[],o=0;o<arguments.length;o++)i[o]=arguments[o];if(t.$constructor)t.$constructor.apply(this,arguments);else{if(Xr(e)){var a=pt(n.prototype,new(e.bind.apply(e,r([void 0],i))));return a}e.apply(this,arguments)}}return n[Hr]=!0,I(n.prototype,t),n.extend=this.extend,n.superCall=jr,n.superApply=qr,A(n,this),n.superClass=e,n}}function Xr(t){return\"function\"==typeof t&&/^class\\s/.test(Function.prototype.toString.call(t))}function Yr(t,e){t.extend=e.extend}var Zr=Math.round(10*Math.random());function jr(t,e){for(var n=[],i=2;i<arguments.length;i++)n[i-2]=arguments[i];return this.superClass.prototype[e].apply(t,n)}function qr(t,e,n){return this.superClass.prototype[e].apply(t,n)}function Kr(t){var e={};t.registerClass=function(t){var n,i=t.type||t.prototype.type;if(i){rt(/^[a-zA-Z0-9_]+([.][a-zA-Z0-9_]+)?$/.test(n=i),'componentType \"'+n+'\" illegal'),t.prototype.type=i;var r=Wr(i);if(r.sub){if(r.sub!==Gr){(function(t){var n=e[t.main];n&&n[Gr]||((n=e[t.main]={})[Gr]=!0);return n}(r))[r.sub]=t}}else e[r.main]=t}return t},t.getClass=function(t,n,i){var r=e[t];if(r&&r[Gr]&&(r=n?r[n]:null),i&&!r)throw new Error(n?\"Component \"+t+\".\"+(n||\"\")+\" is used but not imported.\":t+\".type should be specified.\");return r},t.getClassesByMainType=function(t){var n=Wr(t),i=[],r=e[n.main];return r&&r[Gr]?P(r,(function(t,e){e!==Gr&&i.push(t)})):i.push(r),i},t.hasClass=function(t){var n=Wr(t);return!!e[n.main]},t.getAllClassMainTypes=function(){var t=[];return P(e,(function(e,n){t.push(n)})),t},t.hasSubTypes=function(t){var n=Wr(t),i=e[n.main];return i&&i[Gr]}}function $r(t,e){for(var n=0;n<t.length;n++)t[n][1]||(t[n][1]=t[n][0]);return e=e||!1,function(n,i,r){for(var o={},a=0;a<t.length;a++){var s=t[a][1];if(!(i&&D(i,s)>=0||r&&D(r,s)<0)){var l=n.getShallow(s,e);null!=l&&(o[t[a][0]]=l)}}return o}}var Jr=$r([[\"fill\",\"color\"],[\"shadowBlur\"],[\"shadowOffsetX\"],[\"shadowOffsetY\"],[\"opacity\"],[\"shadowColor\"]]),Qr=function(){function t(){}return t.prototype.getAreaStyle=function(t,e){return Jr(this,t,e)},t}(),to=new Ae(50);function eo(t){if(\"string\"==typeof t){var e=to.get(t);return e&&e.image}return t}function no(t,e,n,i,r){if(t){if(\"string\"==typeof t){if(e&&e.__zrImageSrc===t||!n)return e;var o=to.get(t),a={hostEl:n,cb:i,cbPayload:r};return o?!ro(e=o.image)&&o.pending.push(a):((e=new Image).onload=e.onerror=io,to.put(t,e.__cachedImgObj={image:e,pending:[a]}),e.src=e.__zrImageSrc=t),e}return t}return e}function io(){var t=this.__cachedImgObj;this.onload=this.onerror=this.__cachedImgObj=null;for(var e=0;e<t.pending.length;e++){var n=t.pending[e],i=n.cb;i&&i(this,n.cbPayload),n.hostEl.dirty()}t.pending.length=0}function ro(t){return t&&t.width&&t.height}var oo=/\\{([a-zA-Z0-9_]+)\\|([^}]*)\\}/g;function ao(t,e,n,i,r){if(!e)return\"\";var o=(t+\"\").split(\"\\n\");r=so(e,n,i,r);for(var a=0,s=o.length;a<s;a++)o[a]=lo(o[a],r);return o.join(\"\\n\")}function so(t,e,n,i){var r=I({},i=i||{});r.font=e,n=tt(n,\"...\"),r.maxIterations=tt(i.maxIterations,2);var o=r.minChar=tt(i.minChar,0);r.cnCharWidth=_i(\"国\",e);var a=r.ascCharWidth=_i(\"a\",e);r.placeholder=tt(i.placeholder,\"\");for(var s=t=Math.max(0,t-1),l=0;l<o&&s>=a;l++)s-=a;var u=_i(n,e);return u>s&&(n=\"\",u=0),s=t-u,r.ellipsis=n,r.ellipsisWidth=u,r.contentWidth=s,r.containerWidth=t,r}function lo(t,e){var n=e.containerWidth,i=e.font,r=e.contentWidth;if(!n)return\"\";var o=_i(t,i);if(o<=n)return t;for(var a=0;;a++){if(o<=r||a>=e.maxIterations){t+=e.ellipsis;break}var s=0===a?uo(t,r,e.ascCharWidth,e.cnCharWidth):o>0?Math.floor(t.length*r/o):0;o=_i(t=t.substr(0,s),i)}return\"\"===t&&(t=e.placeholder),t}function uo(t,e,n,i){for(var r=0,o=0,a=t.length;o<a&&r<e;o++){var s=t.charCodeAt(o);r+=0<=s&&s<=127?n:i}return o}var ho=function(){},co=function(t){this.tokens=[],t&&(this.tokens=t)},po=function(){this.width=0,this.height=0,this.contentWidth=0,this.contentHeight=0,this.outerWidth=0,this.outerHeight=0,this.lines=[]};function fo(t,e,n,i,r){var o,a,s=\"\"===e,l=r&&n.rich[r]||{},u=t.lines,h=l.font||n.font,c=!1;if(i){var p=l.padding,d=p?p[1]+p[3]:0;if(null!=l.width&&\"auto\"!==l.width){var f=Ii(l.width,i.width)+d;u.length>0&&f+i.accumWidth>i.width&&(o=e.split(\"\\n\"),c=!0),i.accumWidth=f}else{var g=vo(e,h,i.width,i.breakAll,i.accumWidth);i.accumWidth=g.accumWidth+d,a=g.linesWidths,o=g.lines}}else o=e.split(\"\\n\");for(var y=0;y<o.length;y++){var v=o[y],m=new ho;if(m.styleName=r,m.text=v,m.isLineHolder=!v&&!s,\"number\"==typeof l.width?m.width=l.width:m.width=a?a[y]:_i(v,h),y||c)u.push(new co([m]));else{var _=(u[u.length-1]||(u[0]=new co)).tokens,x=_.length;1===x&&_[0].isLineHolder?_[0]=m:(v||!x||s)&&_.push(m)}}}var go=R(\",&?/;] \".split(\"\"),(function(t,e){return t[e]=!0,t}),{});function yo(t){return!function(t){var e=t.charCodeAt(0);return e>=33&&e<=255}(t)||!!go[t]}function vo(t,e,n,i,r){for(var o=[],a=[],s=\"\",l=\"\",u=0,h=0,c=0;c<t.length;c++){var p=t.charAt(c);if(\"\\n\"!==p){var d=_i(p,e),f=!i&&!yo(p);(o.length?h+d>n:r+h+d>n)?h?(s||l)&&(f?(s||(s=l,l=\"\",h=u=0),o.push(s),a.push(h-u),l+=p,s=\"\",h=u+=d):(l&&(s+=l,h+=u,l=\"\",u=0),o.push(s),a.push(h),s=p,h=d)):f?(o.push(l),a.push(u),l=p,u=d):(o.push(p),a.push(d)):(h+=d,f?(l+=p,u+=d):(l&&(s+=l,l=\"\",u=0),s+=p))}else l&&(s+=l,h+=u),o.push(s),a.push(h),s=\"\",l=\"\",u=0,h=0}return o.length||s||(s=t,l=\"\",u=0),l&&(s+=l),s&&(o.push(s),a.push(h)),1===o.length&&(h+=r),{accumWidth:h,lines:o,linesWidths:a}}var mo=\"__zr_style_\"+Math.round(10*Math.random()),_o={shadowBlur:0,shadowOffsetX:0,shadowOffsetY:0,shadowColor:\"#000\",opacity:1,blend:\"source-over\"},xo={style:{shadowBlur:!0,shadowOffsetX:!0,shadowOffsetY:!0,shadowColor:!0,opacity:!0}};_o[mo]=!0;var bo=[\"z\",\"z2\",\"invisible\"],wo=[\"invisible\"],So=function(t){function e(e){return t.call(this,e)||this}var i;return n(e,t),e.prototype._init=function(e){for(var n=E(e),i=0;i<n.length;i++){var r=n[i];\"style\"===r?this.useStyle(e[r]):t.prototype.attrKV.call(this,r,e[r])}this.style||this.useStyle({})},e.prototype.beforeBrush=function(){},e.prototype.afterBrush=function(){},e.prototype.innerBeforeBrush=function(){},e.prototype.innerAfterBrush=function(){},e.prototype.shouldBePainted=function(t,e,n,i){var r=this.transform;if(this.ignore||this.invisible||0===this.style.opacity||this.culling&&function(t,e,n){Mo.copy(t.getBoundingRect()),t.transform&&Mo.applyTransform(t.transform);return Io.width=e,Io.height=n,!Mo.intersect(Io)}(this,t,e)||r&&!r[0]&&!r[3])return!1;if(n&&this.__clipPaths)for(var o=0;o<this.__clipPaths.length;++o)if(this.__clipPaths[o].isZeroArea())return!1;if(i&&this.parent)for(var a=this.parent;a;){if(a.ignore)return!1;a=a.parent}return!0},e.prototype.contain=function(t,e){return this.rectContain(t,e)},e.prototype.traverse=function(t,e){t.call(e,this)},e.prototype.rectContain=function(t,e){var n=this.transformCoordToLocal(t,e);return this.getBoundingRect().contain(n[0],n[1])},e.prototype.getPaintRect=function(){var t=this._paintRect;if(!this._paintRect||this.__dirty){var e=this.transform,n=this.getBoundingRect(),i=this.style,r=i.shadowBlur||0,o=i.shadowOffsetX||0,a=i.shadowOffsetY||0;t=this._paintRect||(this._paintRect=new gi(0,0,0,0)),e?gi.applyTransform(t,n,e):t.copy(n),(r||o||a)&&(t.width+=2*r+Math.abs(o),t.height+=2*r+Math.abs(a),t.x=Math.min(t.x,t.x+o-r),t.y=Math.min(t.y,t.y+a-r));var s=this.dirtyRectTolerance;t.isZero()||(t.x=Math.floor(t.x-s),t.y=Math.floor(t.y-s),t.width=Math.ceil(t.width+1+2*s),t.height=Math.ceil(t.height+1+2*s))}return t},e.prototype.setPrevPaintRect=function(t){t?(this._prevPaintRect=this._prevPaintRect||new gi(0,0,0,0),this._prevPaintRect.copy(t)):this._prevPaintRect=null},e.prototype.getPrevPaintRect=function(){return this._prevPaintRect},e.prototype.animateStyle=function(t){return this.animate(\"style\",t)},e.prototype.updateDuringAnimation=function(t){\"style\"===t?this.dirtyStyle():this.markRedraw()},e.prototype.attrKV=function(e,n){\"style\"!==e?t.prototype.attrKV.call(this,e,n):this.style?this.setStyle(n):this.useStyle(n)},e.prototype.setStyle=function(t,e){return\"string\"==typeof t?this.style[t]=e:I(this.style,t),this.dirtyStyle(),this},e.prototype.dirtyStyle=function(t){t||this.markRedraw(),this.__dirty|=2,this._rect&&(this._rect=null)},e.prototype.dirty=function(){this.dirtyStyle()},e.prototype.styleChanged=function(){return!!(2&this.__dirty)},e.prototype.styleUpdated=function(){this.__dirty&=-3},e.prototype.createStyle=function(t){return pt(_o,t)},e.prototype.useStyle=function(t){t[mo]||(t=this.createStyle(t)),this.__inHover?this.__hoverStyle=t:this.style=t,this.dirtyStyle()},e.prototype.isStyleObject=function(t){return t[mo]},e.prototype._innerSaveToNormal=function(e){t.prototype._innerSaveToNormal.call(this,e);var n=this._normalState;e.style&&!n.style&&(n.style=this._mergeStyle(this.createStyle(),this.style)),this._savePrimaryToNormal(e,n,bo)},e.prototype._applyStateObj=function(e,n,i,r,o,a){t.prototype._applyStateObj.call(this,e,n,i,r,o,a);var s,l=!(n&&r);if(n&&n.style?o?r?s=n.style:(s=this._mergeStyle(this.createStyle(),i.style),this._mergeStyle(s,n.style)):(s=this._mergeStyle(this.createStyle(),r?this.style:i.style),this._mergeStyle(s,n.style)):l&&(s=i.style),s)if(o){var u=this.style;if(this.style=this.createStyle(l?{}:u),l)for(var h=E(u),c=0;c<h.length;c++){(d=h[c])in s&&(s[d]=s[d],this.style[d]=u[d])}var p=E(s);for(c=0;c<p.length;c++){var d=p[c];this.style[d]=this.style[d]}this._transitionState(e,{style:s},a,this.getAnimationStyleProps())}else this.useStyle(s);var f=this.__inHover?wo:bo;for(c=0;c<f.length;c++){d=f[c];n&&null!=n[d]?this[d]=n[d]:l&&null!=i[d]&&(this[d]=i[d])}},e.prototype._mergeStates=function(e){for(var n,i=t.prototype._mergeStates.call(this,e),r=0;r<e.length;r++){var o=e[r];o.style&&(n=n||{},this._mergeStyle(n,o.style))}return n&&(i.style=n),i},e.prototype._mergeStyle=function(t,e){return I(t,e),t},e.prototype.getAnimationStyleProps=function(){return xo},e.initDefaultProps=((i=e.prototype).type=\"displayable\",i.invisible=!1,i.z=0,i.z2=0,i.zlevel=0,i.culling=!1,i.cursor=\"pointer\",i.rectHover=!1,i.incremental=!1,i._rect=null,i.dirtyRectTolerance=0,void(i.__dirty=3)),e}(Pi),Mo=new gi(0,0,0,0),Io=new gi(0,0,0,0);var To=Math.pow,Co=Math.sqrt,Do=1e-8,Ao=1e-4,Lo=Co(3),ko=1/3,Po=yt(),Oo=yt(),Ro=yt();function No(t){return t>-1e-8&&t<Do}function zo(t){return t>Do||t<-1e-8}function Eo(t,e,n,i,r){var o=1-r;return o*o*(o*t+3*r*e)+r*r*(r*i+3*o*n)}function Vo(t,e,n,i,r){var o=1-r;return 3*(((e-t)*o+2*(n-e)*r)*o+(i-n)*r*r)}function Bo(t,e,n,i,r,o){var a=i+3*(e-n)-t,s=3*(n-2*e+t),l=3*(e-t),u=t-r,h=s*s-3*a*l,c=s*l-9*a*u,p=l*l-3*s*u,d=0;if(No(h)&&No(c)){if(No(s))o[0]=0;else(M=-l/s)>=0&&M<=1&&(o[d++]=M)}else{var f=c*c-4*h*p;if(No(f)){var g=c/h,y=-g/2;(M=-s/a+g)>=0&&M<=1&&(o[d++]=M),y>=0&&y<=1&&(o[d++]=y)}else if(f>0){var v=Co(f),m=h*s+1.5*a*(-c+v),_=h*s+1.5*a*(-c-v);(M=(-s-((m=m<0?-To(-m,ko):To(m,ko))+(_=_<0?-To(-_,ko):To(_,ko))))/(3*a))>=0&&M<=1&&(o[d++]=M)}else{var x=(2*h*s-3*a*c)/(2*Co(h*h*h)),b=Math.acos(x)/3,w=Co(h),S=Math.cos(b),M=(-s-2*w*S)/(3*a),I=(y=(-s+w*(S+Lo*Math.sin(b)))/(3*a),(-s+w*(S-Lo*Math.sin(b)))/(3*a));M>=0&&M<=1&&(o[d++]=M),y>=0&&y<=1&&(o[d++]=y),I>=0&&I<=1&&(o[d++]=I)}}return d}function Fo(t,e,n,i,r){var o=6*n-12*e+6*t,a=9*e+3*i-3*t-9*n,s=3*e-3*t,l=0;if(No(a)){if(zo(o))(h=-s/o)>=0&&h<=1&&(r[l++]=h)}else{var u=o*o-4*a*s;if(No(u))r[0]=-o/(2*a);else if(u>0){var h,c=Co(u),p=(-o-c)/(2*a);(h=(-o+c)/(2*a))>=0&&h<=1&&(r[l++]=h),p>=0&&p<=1&&(r[l++]=p)}}return l}function Go(t,e,n,i,r,o){var a=(e-t)*r+t,s=(n-e)*r+e,l=(i-n)*r+n,u=(s-a)*r+a,h=(l-s)*r+s,c=(h-u)*r+u;o[0]=t,o[1]=a,o[2]=u,o[3]=c,o[4]=c,o[5]=h,o[6]=l,o[7]=i}function Ho(t,e,n,i,r,o,a,s,l,u,h){var c,p,d,f,g,y=.005,v=1/0;Po[0]=l,Po[1]=u;for(var m=0;m<1;m+=.05)Oo[0]=Eo(t,n,r,a,m),Oo[1]=Eo(e,i,o,s,m),(f=Pt(Po,Oo))<v&&(c=m,v=f);v=1/0;for(var _=0;_<32&&!(y<Ao);_++)p=c-y,d=c+y,Oo[0]=Eo(t,n,r,a,p),Oo[1]=Eo(e,i,o,s,p),f=Pt(Oo,Po),p>=0&&f<v?(c=p,v=f):(Ro[0]=Eo(t,n,r,a,d),Ro[1]=Eo(e,i,o,s,d),g=Pt(Ro,Po),d<=1&&g<v?(c=d,v=g):y*=.5);return h&&(h[0]=Eo(t,n,r,a,c),h[1]=Eo(e,i,o,s,c)),Co(v)}function Wo(t,e,n,i,r,o,a,s,l){for(var u=t,h=e,c=0,p=1/l,d=1;d<=l;d++){var f=d*p,g=Eo(t,n,r,a,f),y=Eo(e,i,o,s,f),v=g-u,m=y-h;c+=Math.sqrt(v*v+m*m),u=g,h=y}return c}function Uo(t,e,n,i){var r=1-i;return r*(r*t+2*i*e)+i*i*n}function Xo(t,e,n,i){return 2*((1-i)*(e-t)+i*(n-e))}function Yo(t,e,n){var i=t+n-2*e;return 0===i?.5:(t-e)/i}function Zo(t,e,n,i,r){var o=(e-t)*i+t,a=(n-e)*i+e,s=(a-o)*i+o;r[0]=t,r[1]=o,r[2]=s,r[3]=s,r[4]=a,r[5]=n}function jo(t,e,n,i,r,o,a,s,l){var u,h=.005,c=1/0;Po[0]=a,Po[1]=s;for(var p=0;p<1;p+=.05){Oo[0]=Uo(t,n,r,p),Oo[1]=Uo(e,i,o,p),(y=Pt(Po,Oo))<c&&(u=p,c=y)}c=1/0;for(var d=0;d<32&&!(h<Ao);d++){var f=u-h,g=u+h;Oo[0]=Uo(t,n,r,f),Oo[1]=Uo(e,i,o,f);var y=Pt(Oo,Po);if(f>=0&&y<c)u=f,c=y;else{Ro[0]=Uo(t,n,r,g),Ro[1]=Uo(e,i,o,g);var v=Pt(Ro,Po);g<=1&&v<c?(u=g,c=v):h*=.5}}return l&&(l[0]=Uo(t,n,r,u),l[1]=Uo(e,i,o,u)),Co(c)}function qo(t,e,n,i,r,o,a){for(var s=t,l=e,u=0,h=1/a,c=1;c<=a;c++){var p=c*h,d=Uo(t,n,r,p),f=Uo(e,i,o,p),g=d-s,y=f-l;u+=Math.sqrt(g*g+y*y),s=d,l=f}return u}var Ko=Math.min,$o=Math.max,Jo=Math.sin,Qo=Math.cos,ta=2*Math.PI,ea=yt(),na=yt(),ia=yt();function ra(t,e,n){if(0!==t.length){for(var i=t[0],r=i[0],o=i[0],a=i[1],s=i[1],l=1;l<t.length;l++)i=t[l],r=Ko(r,i[0]),o=$o(o,i[0]),a=Ko(a,i[1]),s=$o(s,i[1]);e[0]=r,e[1]=a,n[0]=o,n[1]=s}}function oa(t,e,n,i,r,o){r[0]=Ko(t,n),r[1]=Ko(e,i),o[0]=$o(t,n),o[1]=$o(e,i)}var aa=[],sa=[];function la(t,e,n,i,r,o,a,s,l,u){var h=Fo,c=Eo,p=h(t,n,r,a,aa);l[0]=1/0,l[1]=1/0,u[0]=-1/0,u[1]=-1/0;for(var d=0;d<p;d++){var f=c(t,n,r,a,aa[d]);l[0]=Ko(f,l[0]),u[0]=$o(f,u[0])}p=h(e,i,o,s,sa);for(d=0;d<p;d++){var g=c(e,i,o,s,sa[d]);l[1]=Ko(g,l[1]),u[1]=$o(g,u[1])}l[0]=Ko(t,l[0]),u[0]=$o(t,u[0]),l[0]=Ko(a,l[0]),u[0]=$o(a,u[0]),l[1]=Ko(e,l[1]),u[1]=$o(e,u[1]),l[1]=Ko(s,l[1]),u[1]=$o(s,u[1])}function ua(t,e,n,i,r,o,a,s){var l=Yo,u=Uo,h=$o(Ko(l(t,n,r),1),0),c=$o(Ko(l(e,i,o),1),0),p=u(t,n,r,h),d=u(e,i,o,c);a[0]=Ko(t,r,p),a[1]=Ko(e,o,d),s[0]=$o(t,r,p),s[1]=$o(e,o,d)}function ha(t,e,n,i,r,o,a,s,l){var u=Nt,h=zt,c=Math.abs(r-o);if(c%ta<1e-4&&c>1e-4)return s[0]=t-n,s[1]=e-i,l[0]=t+n,void(l[1]=e+i);if(ea[0]=Qo(r)*n+t,ea[1]=Jo(r)*i+e,na[0]=Qo(o)*n+t,na[1]=Jo(o)*i+e,u(s,ea,na),h(l,ea,na),(r%=ta)<0&&(r+=ta),(o%=ta)<0&&(o+=ta),r>o&&!a?o+=ta:r<o&&a&&(r+=ta),a){var p=o;o=r,r=p}for(var d=0;d<o;d+=Math.PI/2)d>r&&(ia[0]=Qo(d)*n+t,ia[1]=Jo(d)*i+e,u(s,ia,s),h(l,ia,l))}var ca={M:1,L:2,C:3,Q:4,A:5,Z:6,R:7},pa=[],da=[],fa=[],ga=[],ya=[],va=[],ma=Math.min,_a=Math.max,xa=Math.cos,ba=Math.sin,wa=Math.sqrt,Sa=Math.abs,Ma=Math.PI,Ia=2*Ma,Ta=\"undefined\"!=typeof Float32Array,Ca=[];function Da(t){return Math.round(t/Ma*1e8)/1e8%2*Ma}function Aa(t,e){var n=Da(t[0]);n<0&&(n+=Ia);var i=n-t[0],r=t[1];r+=i,!e&&r-n>=Ia?r=n+Ia:e&&n-r>=Ia?r=n-Ia:!e&&n>r?r=n+(Ia-Da(n-r)):e&&n<r&&(r=n-(Ia-Da(r-n))),t[0]=n,t[1]=r}var La=function(){function t(t){this.dpr=1,this._xi=0,this._yi=0,this._x0=0,this._y0=0,this._len=0,t&&(this._saveData=!1),this._saveData&&(this.data=[])}return t.prototype.increaseVersion=function(){this._version++},t.prototype.getVersion=function(){return this._version},t.prototype.setScale=function(t,e,n){(n=n||0)>0&&(this._ux=Sa(n/En/t)||0,this._uy=Sa(n/En/e)||0)},t.prototype.setDPR=function(t){this.dpr=t},t.prototype.setContext=function(t){this._ctx=t},t.prototype.getContext=function(){return this._ctx},t.prototype.beginPath=function(){return this._ctx&&this._ctx.beginPath(),this.reset(),this},t.prototype.reset=function(){this._saveData&&(this._len=0),this._lineDash&&(this._lineDash=null,this._dashOffset=0),this._pathSegLen&&(this._pathSegLen=null,this._pathLen=0),this._version++},t.prototype.moveTo=function(t,e){return this._drawPendingPt(),this.addData(ca.M,t,e),this._ctx&&this._ctx.moveTo(t,e),this._x0=t,this._y0=e,this._xi=t,this._yi=e,this},t.prototype.lineTo=function(t,e){var n=Sa(t-this._xi),i=Sa(e-this._yi),r=n>this._ux||i>this._uy;if(this.addData(ca.L,t,e),this._ctx&&r&&(this._needsDash?this._dashedLineTo(t,e):this._ctx.lineTo(t,e)),r)this._xi=t,this._yi=e,this._pendingPtDist=0;else{var o=n*n+i*i;o>this._pendingPtDist&&(this._pendingPtX=t,this._pendingPtY=e,this._pendingPtDist=o)}return this},t.prototype.bezierCurveTo=function(t,e,n,i,r,o){return this.addData(ca.C,t,e,n,i,r,o),this._ctx&&(this._needsDash?this._dashedBezierTo(t,e,n,i,r,o):this._ctx.bezierCurveTo(t,e,n,i,r,o)),this._xi=r,this._yi=o,this},t.prototype.quadraticCurveTo=function(t,e,n,i){return this.addData(ca.Q,t,e,n,i),this._ctx&&(this._needsDash?this._dashedQuadraticTo(t,e,n,i):this._ctx.quadraticCurveTo(t,e,n,i)),this._xi=n,this._yi=i,this},t.prototype.arc=function(t,e,n,i,r,o){Ca[0]=i,Ca[1]=r,Aa(Ca,o),i=Ca[0];var a=(r=Ca[1])-i;return this.addData(ca.A,t,e,n,n,i,a,0,o?0:1),this._ctx&&this._ctx.arc(t,e,n,i,r,o),this._xi=xa(r)*n+t,this._yi=ba(r)*n+e,this},t.prototype.arcTo=function(t,e,n,i,r){return this._ctx&&this._ctx.arcTo(t,e,n,i,r),this},t.prototype.rect=function(t,e,n,i){return this._ctx&&this._ctx.rect(t,e,n,i),this.addData(ca.R,t,e,n,i),this},t.prototype.closePath=function(){this._drawPendingPt(),this.addData(ca.Z);var t=this._ctx,e=this._x0,n=this._y0;return t&&(this._needsDash&&this._dashedLineTo(e,n),t.closePath()),this._xi=e,this._yi=n,this},t.prototype.fill=function(t){t&&t.fill(),this.toStatic()},t.prototype.stroke=function(t){t&&t.stroke(),this.toStatic()},t.prototype.setLineDash=function(t){if(t instanceof Array){this._lineDash=t,this._dashIdx=0;for(var e=0,n=0;n<t.length;n++)e+=t[n];this._dashSum=e,this._needsDash=!0}else this._lineDash=null,this._needsDash=!1;return this},t.prototype.setLineDashOffset=function(t){return this._dashOffset=t,this},t.prototype.len=function(){return this._len},t.prototype.setData=function(t){var e=t.length;this.data&&this.data.length===e||!Ta||(this.data=new Float32Array(e));for(var n=0;n<e;n++)this.data[n]=t[n];this._len=e},t.prototype.appendPath=function(t){t instanceof Array||(t=[t]);for(var e=t.length,n=0,i=this._len,r=0;r<e;r++)n+=t[r].len();Ta&&this.data instanceof Float32Array&&(this.data=new Float32Array(i+n));for(r=0;r<e;r++)for(var o=t[r].data,a=0;a<o.length;a++)this.data[i++]=o[a];this._len=i},t.prototype.addData=function(t,e,n,i,r,o,a,s,l){if(this._saveData){var u=this.data;this._len+arguments.length>u.length&&(this._expandData(),u=this.data);for(var h=0;h<arguments.length;h++)u[this._len++]=arguments[h]}},t.prototype._drawPendingPt=function(){this._pendingPtDist>0&&(this._ctx&&this._ctx.lineTo(this._pendingPtX,this._pendingPtY),this._pendingPtDist=0)},t.prototype._expandData=function(){if(!(this.data instanceof Array)){for(var t=[],e=0;e<this._len;e++)t[e]=this.data[e];this.data=t}},t.prototype._dashedLineTo=function(t,e){var n,i,r=this._dashSum,o=this._lineDash,a=this._ctx,s=this._dashOffset,l=this._xi,u=this._yi,h=t-l,c=e-u,p=wa(h*h+c*c),d=l,f=u,g=o.length;for(s<0&&(s=r+s),d-=(s%=r)*(h/=p),f-=s*(c/=p);h>0&&d<=t||h<0&&d>=t||0===h&&(c>0&&f<=e||c<0&&f>=e);)d+=h*(n=o[i=this._dashIdx]),f+=c*n,this._dashIdx=(i+1)%g,h>0&&d<l||h<0&&d>l||c>0&&f<u||c<0&&f>u||a[i%2?\"moveTo\":\"lineTo\"](h>=0?ma(d,t):_a(d,t),c>=0?ma(f,e):_a(f,e));h=d-t,c=f-e,this._dashOffset=-wa(h*h+c*c)},t.prototype._dashedBezierTo=function(t,e,n,i,r,o){var a,s,l,u,h,c=this._ctx,p=this._dashSum,d=this._dashOffset,f=this._lineDash,g=this._xi,y=this._yi,v=0,m=this._dashIdx,_=f.length,x=0;for(d<0&&(d=p+d),d%=p,a=0;a<1;a+=.1)s=Eo(g,t,n,r,a+.1)-Eo(g,t,n,r,a),l=Eo(y,e,i,o,a+.1)-Eo(y,e,i,o,a),v+=wa(s*s+l*l);for(;m<_&&!((x+=f[m])>d);m++);for(a=(x-d)/v;a<=1;)u=Eo(g,t,n,r,a),h=Eo(y,e,i,o,a),m%2?c.moveTo(u,h):c.lineTo(u,h),a+=f[m]/v,m=(m+1)%_;m%2!=0&&c.lineTo(r,o),s=r-u,l=o-h,this._dashOffset=-wa(s*s+l*l)},t.prototype._dashedQuadraticTo=function(t,e,n,i){var r=n,o=i;n=(n+2*t)/3,i=(i+2*e)/3,t=(this._xi+2*t)/3,e=(this._yi+2*e)/3,this._dashedBezierTo(t,e,n,i,r,o)},t.prototype.toStatic=function(){if(this._saveData){this._drawPendingPt();var t=this.data;t instanceof Array&&(t.length=this._len,Ta&&this._len>11&&(this.data=new Float32Array(t)))}},t.prototype.getBoundingRect=function(){fa[0]=fa[1]=ya[0]=ya[1]=Number.MAX_VALUE,ga[0]=ga[1]=va[0]=va[1]=-Number.MAX_VALUE;var t,e=this.data,n=0,i=0,r=0,o=0;for(t=0;t<this._len;){var a=e[t++],s=1===t;switch(s&&(r=n=e[t],o=i=e[t+1]),a){case ca.M:n=r=e[t++],i=o=e[t++],ya[0]=r,ya[1]=o,va[0]=r,va[1]=o;break;case ca.L:oa(n,i,e[t],e[t+1],ya,va),n=e[t++],i=e[t++];break;case ca.C:la(n,i,e[t++],e[t++],e[t++],e[t++],e[t],e[t+1],ya,va),n=e[t++],i=e[t++];break;case ca.Q:ua(n,i,e[t++],e[t++],e[t],e[t+1],ya,va),n=e[t++],i=e[t++];break;case ca.A:var l=e[t++],u=e[t++],h=e[t++],c=e[t++],p=e[t++],d=e[t++]+p;t+=1;var f=!e[t++];s&&(r=xa(p)*h+l,o=ba(p)*c+u),ha(l,u,h,c,p,d,f,ya,va),n=xa(d)*h+l,i=ba(d)*c+u;break;case ca.R:oa(r=n=e[t++],o=i=e[t++],r+e[t++],o+e[t++],ya,va);break;case ca.Z:n=r,i=o}Nt(fa,fa,ya),zt(ga,ga,va)}return 0===t&&(fa[0]=fa[1]=ga[0]=ga[1]=0),new gi(fa[0],fa[1],ga[0]-fa[0],ga[1]-fa[1])},t.prototype._calculateLength=function(){var t=this.data,e=this._len,n=this._ux,i=this._uy,r=0,o=0,a=0,s=0;this._pathSegLen||(this._pathSegLen=[]);for(var l=this._pathSegLen,u=0,h=0,c=0;c<e;){var p=t[c++],d=1===c;d&&(a=r=t[c],s=o=t[c+1]);var f=-1;switch(p){case ca.M:r=a=t[c++],o=s=t[c++];break;case ca.L:var g=t[c++],y=(_=t[c++])-o;(Sa(A=g-r)>n||Sa(y)>i||c===e-1)&&(f=Math.sqrt(A*A+y*y),r=g,o=_);break;case ca.C:var v=t[c++],m=t[c++],_=(g=t[c++],t[c++]),x=t[c++],b=t[c++];f=Wo(r,o,v,m,g,_,x,b,10),r=x,o=b;break;case ca.Q:f=qo(r,o,v=t[c++],m=t[c++],g=t[c++],_=t[c++],10),r=g,o=_;break;case ca.A:var w=t[c++],S=t[c++],M=t[c++],I=t[c++],T=t[c++],C=t[c++],D=C+T;c+=1;t[c++];d&&(a=xa(T)*M+w,s=ba(T)*I+S),f=_a(M,I)*ma(Ia,Math.abs(C)),r=xa(D)*M+w,o=ba(D)*I+S;break;case ca.R:a=r=t[c++],s=o=t[c++],f=2*t[c++]+2*t[c++];break;case ca.Z:var A=a-r;y=s-o;f=Math.sqrt(A*A+y*y),r=a,o=s}f>=0&&(l[h++]=f,u+=f)}return this._pathLen=u,u},t.prototype.rebuildPath=function(t,e){var n,i,r,o,a,s,l,u,h,c,p=this.data,d=this._ux,f=this._uy,g=this._len,y=e<1,v=0,m=0,_=0;if(!y||(this._pathSegLen||this._calculateLength(),l=this._pathSegLen,u=e*this._pathLen))t:for(var x=0;x<g;){var b=p[x++],w=1===x;switch(w&&(n=r=p[x],i=o=p[x+1]),b){case ca.M:_>0&&(t.lineTo(h,c),_=0),n=r=p[x++],i=o=p[x++],t.moveTo(r,o);break;case ca.L:a=p[x++],s=p[x++];var S=Sa(a-r),M=Sa(s-o);if(S>d||M>f){if(y){if(v+(j=l[m++])>u){var I=(u-v)/j;t.lineTo(r*(1-I)+a*I,o*(1-I)+s*I);break t}v+=j}t.lineTo(a,s),r=a,o=s,_=0}else{var T=S*S+M*M;T>_&&(h=a,c=s,_=T)}break;case ca.C:var C=p[x++],D=p[x++],A=p[x++],L=p[x++],k=p[x++],P=p[x++];if(y){if(v+(j=l[m++])>u){Go(r,C,A,k,I=(u-v)/j,pa),Go(o,D,L,P,I,da),t.bezierCurveTo(pa[1],da[1],pa[2],da[2],pa[3],da[3]);break t}v+=j}t.bezierCurveTo(C,D,A,L,k,P),r=k,o=P;break;case ca.Q:C=p[x++],D=p[x++],A=p[x++],L=p[x++];if(y){if(v+(j=l[m++])>u){Zo(r,C,A,I=(u-v)/j,pa),Zo(o,D,L,I,da),t.quadraticCurveTo(pa[1],da[1],pa[2],da[2]);break t}v+=j}t.quadraticCurveTo(C,D,A,L),r=A,o=L;break;case ca.A:var O=p[x++],R=p[x++],N=p[x++],z=p[x++],E=p[x++],V=p[x++],B=p[x++],F=!p[x++],G=N>z?N:z,H=Sa(N-z)>.001,W=E+V,U=!1;if(y)v+(j=l[m++])>u&&(W=E+V*(u-v)/j,U=!0),v+=j;if(H&&t.ellipse?t.ellipse(O,R,N,z,B,E,W,F):t.arc(O,R,G,E,W,F),U)break t;w&&(n=xa(E)*N+O,i=ba(E)*z+R),r=xa(W)*N+O,o=ba(W)*z+R;break;case ca.R:n=r=p[x],i=o=p[x+1],a=p[x++],s=p[x++];var X=p[x++],Y=p[x++];if(y){if(v+(j=l[m++])>u){var Z=u-v;t.moveTo(a,s),t.lineTo(a+ma(Z,X),s),(Z-=X)>0&&t.lineTo(a+X,s+ma(Z,Y)),(Z-=Y)>0&&t.lineTo(a+_a(X-Z,0),s+Y),(Z-=X)>0&&t.lineTo(a,s+_a(Y-Z,0));break t}v+=j}t.rect(a,s,X,Y);break;case ca.Z:if(_>0&&(t.lineTo(h,c),_=0),y){var j;if(v+(j=l[m++])>u){I=(u-v)/j;t.lineTo(r*(1-I)+n*I,o*(1-I)+i*I);break t}v+=j}t.closePath(),r=n,o=i}}},t.CMD=ca,t.initDefaultProps=function(){var e=t.prototype;e._saveData=!0,e._needsDash=!1,e._dashOffset=0,e._dashIdx=0,e._dashSum=0,e._ux=0,e._uy=0,e._pendingPtDist=0,e._version=0}(),t}();function ka(t,e,n,i,r,o,a){if(0===r)return!1;var s=r,l=0;if(a>e+s&&a>i+s||a<e-s&&a<i-s||o>t+s&&o>n+s||o<t-s&&o<n-s)return!1;if(t===n)return Math.abs(o-t)<=s/2;var u=(l=(e-i)/(t-n))*o-a+(t*i-n*e)/(t-n);return u*u/(l*l+1)<=s/2*s/2}function Pa(t,e,n,i,r,o,a,s,l,u,h){if(0===l)return!1;var c=l;return!(h>e+c&&h>i+c&&h>o+c&&h>s+c||h<e-c&&h<i-c&&h<o-c&&h<s-c||u>t+c&&u>n+c&&u>r+c&&u>a+c||u<t-c&&u<n-c&&u<r-c&&u<a-c)&&Ho(t,e,n,i,r,o,a,s,u,h,null)<=c/2}function Oa(t,e,n,i,r,o,a,s,l){if(0===a)return!1;var u=a;return!(l>e+u&&l>i+u&&l>o+u||l<e-u&&l<i-u&&l<o-u||s>t+u&&s>n+u&&s>r+u||s<t-u&&s<n-u&&s<r-u)&&jo(t,e,n,i,r,o,s,l,null)<=u/2}var Ra=2*Math.PI;function Na(t){return(t%=Ra)<0&&(t+=Ra),t}var za=2*Math.PI;function Ea(t,e,n,i,r,o,a,s,l){if(0===a)return!1;var u=a;s-=t,l-=e;var h=Math.sqrt(s*s+l*l);if(h-u>n||h+u<n)return!1;if(Math.abs(i-r)%za<1e-4)return!0;if(o){var c=i;i=Na(r),r=Na(c)}else i=Na(i),r=Na(r);i>r&&(r+=za);var p=Math.atan2(l,s);return p<0&&(p+=za),p>=i&&p<=r||p+za>=i&&p+za<=r}function Va(t,e,n,i,r,o){if(o>e&&o>i||o<e&&o<i)return 0;if(i===e)return 0;var a=(o-e)/(i-e),s=i<e?1:-1;1!==a&&0!==a||(s=i<e?.5:-.5);var l=a*(n-t)+t;return l===r?1/0:l>r?s:0}var Ba=La.CMD,Fa=2*Math.PI;var Ga=[-1,-1,-1],Ha=[-1,-1];function Wa(t,e,n,i,r,o,a,s,l,u){if(u>e&&u>i&&u>o&&u>s||u<e&&u<i&&u<o&&u<s)return 0;var h,c=Bo(e,i,o,s,u,Ga);if(0===c)return 0;for(var p=0,d=-1,f=void 0,g=void 0,y=0;y<c;y++){var v=Ga[y],m=0===v||1===v?.5:1;Eo(t,n,r,a,v)<l||(d<0&&(d=Fo(e,i,o,s,Ha),Ha[1]<Ha[0]&&d>1&&(h=void 0,h=Ha[0],Ha[0]=Ha[1],Ha[1]=h),f=Eo(e,i,o,s,Ha[0]),d>1&&(g=Eo(e,i,o,s,Ha[1]))),2===d?v<Ha[0]?p+=f<e?m:-m:v<Ha[1]?p+=g<f?m:-m:p+=s<g?m:-m:v<Ha[0]?p+=f<e?m:-m:p+=s<f?m:-m)}return p}function Ua(t,e,n,i,r,o,a,s){if(s>e&&s>i&&s>o||s<e&&s<i&&s<o)return 0;var l=function(t,e,n,i,r){var o=t-2*e+n,a=2*(e-t),s=t-i,l=0;if(No(o))zo(a)&&(h=-s/a)>=0&&h<=1&&(r[l++]=h);else{var u=a*a-4*o*s;if(No(u))(h=-a/(2*o))>=0&&h<=1&&(r[l++]=h);else if(u>0){var h,c=Co(u),p=(-a-c)/(2*o);(h=(-a+c)/(2*o))>=0&&h<=1&&(r[l++]=h),p>=0&&p<=1&&(r[l++]=p)}}return l}(e,i,o,s,Ga);if(0===l)return 0;var u=Yo(e,i,o);if(u>=0&&u<=1){for(var h=0,c=Uo(e,i,o,u),p=0;p<l;p++){var d=0===Ga[p]||1===Ga[p]?.5:1;Uo(t,n,r,Ga[p])<a||(Ga[p]<u?h+=c<e?d:-d:h+=o<c?d:-d)}return h}d=0===Ga[0]||1===Ga[0]?.5:1;return Uo(t,n,r,Ga[0])<a?0:o<e?d:-d}function Xa(t,e,n,i,r,o,a,s){if((s-=e)>n||s<-n)return 0;var l=Math.sqrt(n*n-s*s);Ga[0]=-l,Ga[1]=l;var u=Math.abs(i-r);if(u<1e-4)return 0;if(u>=Fa-1e-4){i=0,r=Fa;var h=o?1:-1;return a>=Ga[0]+t&&a<=Ga[1]+t?h:0}if(i>r){var c=i;i=r,r=c}i<0&&(i+=Fa,r+=Fa);for(var p=0,d=0;d<2;d++){var f=Ga[d];if(f+t>a){var g=Math.atan2(s,f);h=o?1:-1;g<0&&(g=Fa+g),(g>=i&&g<=r||g+Fa>=i&&g+Fa<=r)&&(g>Math.PI/2&&g<1.5*Math.PI&&(h=-h),p+=h)}}return p}function Ya(t,e,n,i,r){for(var o,a,s,l,u=t.data,h=t.len(),c=0,p=0,d=0,f=0,g=0,y=0;y<h;){var v=u[y++],m=1===y;switch(v===Ba.M&&y>1&&(n||(c+=Va(p,d,f,g,i,r))),m&&(f=p=u[y],g=d=u[y+1]),v){case Ba.M:p=f=u[y++],d=g=u[y++];break;case Ba.L:if(n){if(ka(p,d,u[y],u[y+1],e,i,r))return!0}else c+=Va(p,d,u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case Ba.C:if(n){if(Pa(p,d,u[y++],u[y++],u[y++],u[y++],u[y],u[y+1],e,i,r))return!0}else c+=Wa(p,d,u[y++],u[y++],u[y++],u[y++],u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case Ba.Q:if(n){if(Oa(p,d,u[y++],u[y++],u[y],u[y+1],e,i,r))return!0}else c+=Ua(p,d,u[y++],u[y++],u[y],u[y+1],i,r)||0;p=u[y++],d=u[y++];break;case Ba.A:var _=u[y++],x=u[y++],b=u[y++],w=u[y++],S=u[y++],M=u[y++];y+=1;var I=!!(1-u[y++]);o=Math.cos(S)*b+_,a=Math.sin(S)*w+x,m?(f=o,g=a):c+=Va(p,d,o,a,i,r);var T=(i-_)*w/b+_;if(n){if(Ea(_,x,w,S,S+M,I,e,T,r))return!0}else c+=Xa(_,x,w,S,S+M,I,T,r);p=Math.cos(S+M)*b+_,d=Math.sin(S+M)*w+x;break;case Ba.R:if(f=p=u[y++],g=d=u[y++],o=f+u[y++],a=g+u[y++],n){if(ka(f,g,o,g,e,i,r)||ka(o,g,o,a,e,i,r)||ka(o,a,f,a,e,i,r)||ka(f,a,f,g,e,i,r))return!0}else c+=Va(o,g,o,a,i,r),c+=Va(f,a,f,g,i,r);break;case Ba.Z:if(n){if(ka(p,d,f,g,e,i,r))return!0}else c+=Va(p,d,f,g,i,r);p=f,d=g}}return n||(s=d,l=g,Math.abs(s-l)<1e-4)||(c+=Va(p,d,f,g,i,r)||0),0!==c}var Za=T({fill:\"#000\",stroke:null,strokePercent:1,fillOpacity:1,strokeOpacity:1,lineDashOffset:0,lineWidth:1,lineCap:\"butt\",miterLimit:10,strokeNoScale:!1,strokeFirst:!1},_o),ja={style:T({fill:!0,stroke:!0,strokePercent:!0,fillOpacity:!0,strokeOpacity:!0,lineDashOffset:!0,lineWidth:!0,miterLimit:!0},xo.style)},qa=[\"x\",\"y\",\"rotation\",\"scaleX\",\"scaleY\",\"originX\",\"originY\",\"invisible\",\"culling\",\"z\",\"z2\",\"zlevel\",\"parent\"],Ka=function(t){function e(e){return t.call(this,e)||this}var i;return n(e,t),e.prototype.update=function(){var n=this;t.prototype.update.call(this);var i=this.style;if(i.decal){var r=this._decalEl=this._decalEl||new e;r.buildPath===e.prototype.buildPath&&(r.buildPath=function(t){n.buildPath(t,n.shape)}),r.silent=!0;var o=r.style;for(var a in i)o[a]!==i[a]&&(o[a]=i[a]);o.fill=i.fill?i.decal:null,o.decal=null,o.shadowColor=null,i.strokeFirst&&(o.stroke=null);for(var s=0;s<qa.length;++s)r[qa[s]]=this[qa[s]];r.__dirty|=1}else this._decalEl&&(this._decalEl=null)},e.prototype.getDecalElement=function(){return this._decalEl},e.prototype._init=function(e){var n=E(e);this.shape=this.getDefaultShape();var i=this.getDefaultStyle();i&&this.useStyle(i);for(var r=0;r<n.length;r++){var o=n[r],a=e[o];\"style\"===o?this.style?I(this.style,a):this.useStyle(a):\"shape\"===o?I(this.shape,a):t.prototype.attrKV.call(this,o,a)}this.style||this.useStyle({})},e.prototype.getDefaultStyle=function(){return null},e.prototype.getDefaultShape=function(){return{}},e.prototype.canBeInsideText=function(){return this.hasFill()},e.prototype.getInsideTextFill=function(){var t=this.style.fill;if(\"none\"!==t){if(H(t)){var e=Qe(t,0);return e>.5?Vn:e>.2?\"#eee\":Bn}if(t)return Bn}return Vn},e.prototype.getInsideTextStroke=function(t){var e=this.style.fill;if(H(e)){var n=this.__zr;if(!(!n||!n.isDarkMode())===Qe(t,0)<.4)return e}},e.prototype.buildPath=function(t,e,n){},e.prototype.pathUpdated=function(){this.__dirty&=-5},e.prototype.createPathProxy=function(){this.path=new La(!1)},e.prototype.hasStroke=function(){var t=this.style,e=t.stroke;return!(null==e||\"none\"===e||!(t.lineWidth>0))},e.prototype.hasFill=function(){var t=this.style.fill;return null!=t&&\"none\"!==t},e.prototype.getBoundingRect=function(){var t=this._rect,e=this.style,n=!t;if(n){var i=!1;this.path||(i=!0,this.createPathProxy());var r=this.path;(i||4&this.__dirty)&&(r.beginPath(),this.buildPath(r,this.shape,!1),this.pathUpdated()),t=r.getBoundingRect()}if(this._rect=t,this.hasStroke()&&this.path&&this.path.len()>0){var o=this._rectWithStroke||(this._rectWithStroke=t.clone());if(this.__dirty||n){o.copy(t);var a=e.strokeNoScale?this.getLineScale():1,s=e.lineWidth;if(!this.hasFill()){var l=this.strokeContainThreshold;s=Math.max(s,null==l?4:l)}a>1e-10&&(o.width+=s/a,o.height+=s/a,o.x-=s/a/2,o.y-=s/a/2)}return o}return t},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e),i=this.getBoundingRect(),r=this.style;if(t=n[0],e=n[1],i.contain(t,e)){var o=this.path;if(this.hasStroke()){var a=r.lineWidth,s=r.strokeNoScale?this.getLineScale():1;if(s>1e-10&&(this.hasFill()||(a=Math.max(a,this.strokeContainThreshold)),function(t,e,n,i){return Ya(t,e,!0,n,i)}(o,a/s,t,e)))return!0}if(this.hasFill())return function(t,e,n){return Ya(t,0,!1,e,n)}(o,t,e)}return!1},e.prototype.dirtyShape=function(){this.__dirty|=4,this._rect&&(this._rect=null),this._decalEl&&this._decalEl.dirtyShape(),this.markRedraw()},e.prototype.dirty=function(){this.dirtyStyle(),this.dirtyShape()},e.prototype.animateShape=function(t){return this.animate(\"shape\",t)},e.prototype.updateDuringAnimation=function(t){\"style\"===t?this.dirtyStyle():\"shape\"===t?this.dirtyShape():this.markRedraw()},e.prototype.attrKV=function(e,n){\"shape\"===e?this.setShape(n):t.prototype.attrKV.call(this,e,n)},e.prototype.setShape=function(t,e){var n=this.shape;return n||(n=this.shape={}),\"string\"==typeof t?n[t]=e:I(n,t),this.dirtyShape(),this},e.prototype.shapeChanged=function(){return!!(4&this.__dirty)},e.prototype.createStyle=function(t){return pt(Za,t)},e.prototype._innerSaveToNormal=function(e){t.prototype._innerSaveToNormal.call(this,e);var n=this._normalState;e.shape&&!n.shape&&(n.shape=I({},this.shape))},e.prototype._applyStateObj=function(e,n,i,r,o,a){t.prototype._applyStateObj.call(this,e,n,i,r,o,a);var s,l=!(n&&r);if(n&&n.shape?o?r?s=n.shape:(s=I({},i.shape),I(s,n.shape)):(s=I({},r?this.shape:i.shape),I(s,n.shape)):l&&(s=i.shape),s)if(o){this.shape=I({},this.shape);for(var u={},h=E(s),c=0;c<h.length;c++){var p=h[c];\"object\"==typeof s[p]?this.shape[p]=s[p]:u[p]=s[p]}this._transitionState(e,{shape:u},a)}else this.shape=s,this.dirtyShape()},e.prototype._mergeStates=function(e){for(var n,i=t.prototype._mergeStates.call(this,e),r=0;r<e.length;r++){var o=e[r];o.shape&&(n=n||{},this._mergeStyle(n,o.shape))}return n&&(i.shape=n),i},e.prototype.getAnimationStyleProps=function(){return ja},e.prototype.isZeroArea=function(){return!1},e.extend=function(t){var i=function(e){function i(n){var i=e.call(this,n)||this;return t.init&&t.init.call(i,n),i}return n(i,e),i.prototype.getDefaultStyle=function(){return w(t.style)},i.prototype.getDefaultShape=function(){return w(t.shape)},i}(e);for(var r in t)\"function\"==typeof t[r]&&(i.prototype[r]=t[r]);return i},e.initDefaultProps=((i=e.prototype).type=\"path\",i.strokeContainThreshold=5,i.segmentIgnoreThreshold=0,i.subPixelOptimize=!1,i.autoBatch=!1,void(i.__dirty=7)),e}(So),$a=T({strokeFirst:!0,font:vi,x:0,y:0,textAlign:\"left\",textBaseline:\"top\",miterLimit:2},Za),Ja=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.hasStroke=function(){var t=this.style,e=t.stroke;return null!=e&&\"none\"!==e&&t.lineWidth>0},e.prototype.hasFill=function(){var t=this.style.fill;return null!=t&&\"none\"!==t},e.prototype.createStyle=function(t){return pt($a,t)},e.prototype.setBoundingRect=function(t){this._rect=t},e.prototype.getBoundingRect=function(){var t=this.style;if(!this._rect){var e=t.text;null!=e?e+=\"\":e=\"\";var n=bi(e,t.font,t.textAlign,t.textBaseline);if(n.x+=t.x||0,n.y+=t.y||0,this.hasStroke()){var i=t.lineWidth;n.x-=i/2,n.y-=i/2,n.width+=i,n.height+=i}this._rect=n}return this._rect},e.initDefaultProps=void(e.prototype.dirtyRectTolerance=10),e}(So);Ja.prototype.type=\"tspan\";var Qa=T({x:0,y:0},_o),ts={style:T({x:!0,y:!0,width:!0,height:!0,sx:!0,sy:!0,sWidth:!0,sHeight:!0},xo.style)};var es=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.createStyle=function(t){return pt(Qa,t)},e.prototype._getSize=function(t){var e=this.style,n=e[t];if(null!=n)return n;var i,r=(i=e.image)&&\"string\"!=typeof i&&i.width&&i.height?e.image:this.__image;if(!r)return 0;var o=\"width\"===t?\"height\":\"width\",a=e[o];return null==a?r[t]:r[t]/r[o]*a},e.prototype.getWidth=function(){return this._getSize(\"width\")},e.prototype.getHeight=function(){return this._getSize(\"height\")},e.prototype.getAnimationStyleProps=function(){return ts},e.prototype.getBoundingRect=function(){var t=this.style;return this._rect||(this._rect=new gi(t.x||0,t.y||0,this.getWidth(),this.getHeight())),this._rect},e}(So);es.prototype.type=\"image\";var ns=Math.round;function is(t,e,n){if(e){var i=e.x1,r=e.x2,o=e.y1,a=e.y2;t.x1=i,t.x2=r,t.y1=o,t.y2=a;var s=n&&n.lineWidth;return s?(ns(2*i)===ns(2*r)&&(t.x1=t.x2=os(i,s,!0)),ns(2*o)===ns(2*a)&&(t.y1=t.y2=os(o,s,!0)),t):t}}function rs(t,e,n){if(e){var i=e.x,r=e.y,o=e.width,a=e.height;t.x=i,t.y=r,t.width=o,t.height=a;var s=n&&n.lineWidth;return s?(t.x=os(i,s,!0),t.y=os(r,s,!0),t.width=Math.max(os(i+o,s,!1)-t.x,0===o?0:1),t.height=Math.max(os(r+a,s,!1)-t.y,0===a?0:1),t):t}}function os(t,e,n){if(!e)return t;var i=ns(2*t);return(i+ns(e))%2==0?i/2:(i+(n?1:-1))/2}var as=function(){this.x=0,this.y=0,this.width=0,this.height=0},ss={},ls=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new as},e.prototype.buildPath=function(t,e){var n,i,r,o;if(this.subPixelOptimize){var a=rs(ss,e,this.style);n=a.x,i=a.y,r=a.width,o=a.height,a.r=e.r,e=a}else n=e.x,i=e.y,r=e.width,o=e.height;e.r?function(t,e){var n,i,r,o,a,s=e.x,l=e.y,u=e.width,h=e.height,c=e.r;u<0&&(s+=u,u=-u),h<0&&(l+=h,h=-h),\"number\"==typeof c?n=i=r=o=c:c instanceof Array?1===c.length?n=i=r=o=c[0]:2===c.length?(n=r=c[0],i=o=c[1]):3===c.length?(n=c[0],i=o=c[1],r=c[2]):(n=c[0],i=c[1],r=c[2],o=c[3]):n=i=r=o=0,n+i>u&&(n*=u/(a=n+i),i*=u/a),r+o>u&&(r*=u/(a=r+o),o*=u/a),i+r>h&&(i*=h/(a=i+r),r*=h/a),n+o>h&&(n*=h/(a=n+o),o*=h/a),t.moveTo(s+n,l),t.lineTo(s+u-i,l),0!==i&&t.arc(s+u-i,l+i,i,-Math.PI/2,0),t.lineTo(s+u,l+h-r),0!==r&&t.arc(s+u-r,l+h-r,r,0,Math.PI/2),t.lineTo(s+o,l+h),0!==o&&t.arc(s+o,l+h-o,o,Math.PI/2,Math.PI),t.lineTo(s,l+n),0!==n&&t.arc(s+n,l+n,n,Math.PI,1.5*Math.PI)}(t,e):t.rect(n,i,r,o)},e.prototype.isZeroArea=function(){return!this.shape.width||!this.shape.height},e}(Ka);ls.prototype.type=\"rect\";var us={fill:\"#000\"},hs={style:T({fill:!0,stroke:!0,fillOpacity:!0,strokeOpacity:!0,lineWidth:!0,fontSize:!0,lineHeight:!0,width:!0,height:!0,textShadowColor:!0,textShadowBlur:!0,textShadowOffsetX:!0,textShadowOffsetY:!0,backgroundColor:!0,padding:!0,borderColor:!0,borderWidth:!0,borderRadius:!0},xo.style)},cs=function(t){function e(e){var n=t.call(this)||this;return n.type=\"text\",n._children=[],n._defaultStyle=us,n.attr(e),n}return n(e,t),e.prototype.childrenRef=function(){return this._children},e.prototype.update=function(){this.styleChanged()&&this._updateSubTexts();for(var e=0;e<this._children.length;e++){var n=this._children[e];n.zlevel=this.zlevel,n.z=this.z,n.z2=this.z2,n.culling=this.culling,n.cursor=this.cursor,n.invisible=this.invisible}var i=this.attachedTransform;if(i){i.updateTransform();var r=i.transform;r?(this.transform=this.transform||[],Hn(this.transform,r)):this.transform=null}else t.prototype.update.call(this)},e.prototype.getComputedTransform=function(){return this.__hostTarget&&(this.__hostTarget.getComputedTransform(),this.__hostTarget.updateInnerText(!0)),this.attachedTransform?this.attachedTransform.getComputedTransform():t.prototype.getComputedTransform.call(this)},e.prototype._updateSubTexts=function(){var t;this._childCursor=0,fs(t=this.style),P(t.rich,fs),this.style.rich?this._updateRichTexts():this._updatePlainTexts(),this._children.length=this._childCursor,this.styleUpdated()},e.prototype.addSelfToZr=function(e){t.prototype.addSelfToZr.call(this,e);for(var n=0;n<this._children.length;n++)this._children[n].__zr=e},e.prototype.removeSelfFromZr=function(e){t.prototype.removeSelfFromZr.call(this,e);for(var n=0;n<this._children.length;n++)this._children[n].__zr=null},e.prototype.getBoundingRect=function(){if(this.styleChanged()&&this._updateSubTexts(),!this._rect){for(var t=new gi(0,0,0,0),e=this._children,n=[],i=null,r=0;r<e.length;r++){var o=e[r],a=o.getBoundingRect(),s=o.getLocalTransform(n);s?(t.copy(a),t.applyTransform(s),(i=i||t.clone()).union(t)):(i=i||a.clone()).union(a)}this._rect=i||t}return this._rect},e.prototype.setDefaultTextStyle=function(t){this._defaultStyle=t||us},e.prototype.setTextContent=function(t){throw new Error(\"Can't attach text on another text\")},e.prototype._mergeStyle=function(t,e){if(!e)return t;var n=e.rich,i=t.rich||n&&{};return I(t,e),n&&i?(this._mergeRich(i,n),t.rich=i):i&&(t.rich=i),t},e.prototype._mergeRich=function(t,e){for(var n=E(e),i=0;i<n.length;i++){var r=n[i];t[r]=t[r]||{},I(t[r],e[r])}},e.prototype.getAnimationStyleProps=function(){return hs},e.prototype._getOrCreateChild=function(t){var e=this._children[this._childCursor];return e&&e instanceof t||(e=new t),this._children[this._childCursor++]=e,e.__zr=this.__zr,e.parent=this,e},e.prototype._updatePlainTexts=function(){var t=this.style,e=t.font||vi,n=t.padding,i=function(t,e){null!=t&&(t+=\"\");var n,i=e.overflow,r=e.padding,o=e.font,a=\"truncate\"===i,s=Mi(o),l=tt(e.lineHeight,s),u=\"truncate\"===e.lineOverflow,h=e.width,c=(n=null!=h&&\"break\"===i||\"breakAll\"===i?t?vo(t,e.font,h,\"breakAll\"===i,0).lines:[]:t?t.split(\"\\n\"):[]).length*l,p=tt(e.height,c);if(c>p&&u){var d=Math.floor(p/l);n=n.slice(0,d)}var f=p,g=h;if(r&&(f+=r[0]+r[2],null!=g&&(g+=r[1]+r[3])),t&&a&&null!=g)for(var y=so(h,o,e.ellipsis,{minChar:e.truncateMinChar,placeholder:e.placeholder}),v=0;v<n.length;v++)n[v]=lo(n[v],y);if(null==h){var m=0;for(v=0;v<n.length;v++)m=Math.max(_i(n[v],o),m);h=m}return{lines:n,height:p,outerHeight:f,lineHeight:l,calculatedLineHeight:s,contentHeight:c,width:h}}(vs(t),t),r=ms(t),o=!!t.backgroundColor,a=i.outerHeight,s=i.lines,l=i.lineHeight,u=this._defaultStyle,h=t.x||0,c=t.y||0,p=t.align||u.align||\"left\",d=t.verticalAlign||u.verticalAlign||\"top\",f=h,g=Si(c,i.contentHeight,d);if(r||n){var y=i.width;n&&(y+=n[1]+n[3]);var v=wi(h,y,p),m=Si(c,a,d);r&&this._renderBackground(t,t,v,m,y,a)}g+=l/2,n&&(f=ys(h,p,n),\"top\"===d?g+=n[0]:\"bottom\"===d&&(g-=n[2]));for(var _,x=0,b=!1,w=(null==(_=\"fill\"in t?t.fill:(b=!0,u.fill))||\"none\"===_?null:_.image||_.colorStops?\"#000\":_),S=(gs(\"stroke\"in t?t.stroke:o||u.autoStroke&&!b?null:(x=2,u.stroke))),M=t.textShadowBlur>0,I=null!=t.width&&(\"truncate\"===t.overflow||\"break\"===t.overflow||\"breakAll\"===t.overflow),T=i.calculatedLineHeight,C=0;C<s.length;C++){var D=this._getOrCreateChild(Ja),A=D.createStyle();D.useStyle(A),A.text=s[C],A.x=f,A.y=g,p&&(A.textAlign=p),A.textBaseline=\"middle\",A.opacity=t.opacity,A.strokeFirst=!0,M&&(A.shadowBlur=t.textShadowBlur||0,A.shadowColor=t.textShadowColor||\"transparent\",A.shadowOffsetX=t.textShadowOffsetX||0,A.shadowOffsetY=t.textShadowOffsetY||0),S&&(A.stroke=S,A.lineWidth=t.lineWidth||x,A.lineDash=t.lineDash,A.lineDashOffset=t.lineDashOffset||0),w&&(A.fill=w),A.font=e,g+=l,I&&D.setBoundingRect(new gi(wi(A.x,t.width,A.textAlign),Si(A.y,T,A.textBaseline),t.width,T))}},e.prototype._updateRichTexts=function(){var t=this.style,e=function(t,e){var n=new po;if(null!=t&&(t+=\"\"),!t)return n;for(var i,r=e.width,o=e.height,a=e.overflow,s=\"break\"!==a&&\"breakAll\"!==a||null==r?null:{width:r,accumWidth:0,breakAll:\"breakAll\"===a},l=oo.lastIndex=0;null!=(i=oo.exec(t));){var u=i.index;u>l&&fo(n,t.substring(l,u),e,s),fo(n,i[2],e,s,i[1]),l=oo.lastIndex}l<t.length&&fo(n,t.substring(l,t.length),e,s);var h=[],c=0,p=0,d=e.padding,f=\"truncate\"===a,g=\"truncate\"===e.lineOverflow;function y(t,e,n){t.width=e,t.lineHeight=n,c+=n,p=Math.max(p,e)}t:for(var v=0;v<n.lines.length;v++){for(var m=n.lines[v],_=0,x=0,b=0;b<m.tokens.length;b++){var w=(P=m.tokens[b]).styleName&&e.rich[P.styleName]||{},S=P.textPadding=w.padding,M=S?S[1]+S[3]:0,I=P.font=w.font||e.font;P.contentHeight=Mi(I);var T=tt(w.height,P.contentHeight);if(P.innerHeight=T,S&&(T+=S[0]+S[2]),P.height=T,P.lineHeight=et(w.lineHeight,e.lineHeight,T),P.align=w&&w.align||e.align,P.verticalAlign=w&&w.verticalAlign||\"middle\",g&&null!=o&&c+P.lineHeight>o){b>0?(m.tokens=m.tokens.slice(0,b),y(m,x,_),n.lines=n.lines.slice(0,v+1)):n.lines=n.lines.slice(0,v);break t}var C=w.width,D=null==C||\"auto\"===C;if(\"string\"==typeof C&&\"%\"===C.charAt(C.length-1))P.percentWidth=C,h.push(P),P.contentWidth=_i(P.text,I);else{if(D){var A=w.backgroundColor,L=A&&A.image;L&&ro(L=eo(L))&&(P.width=Math.max(P.width,L.width*T/L.height))}var k=f&&null!=r?r-x:null;null!=k&&k<P.width?!D||k<M?(P.text=\"\",P.width=P.contentWidth=0):(P.text=ao(P.text,k-M,I,e.ellipsis,{minChar:e.truncateMinChar}),P.width=P.contentWidth=_i(P.text,I)):P.contentWidth=_i(P.text,I)}P.width+=M,x+=P.width,w&&(_=Math.max(_,P.lineHeight))}y(m,x,_)}for(n.outerWidth=n.width=tt(r,p),n.outerHeight=n.height=tt(o,c),n.contentHeight=c,n.contentWidth=p,d&&(n.outerWidth+=d[1]+d[3],n.outerHeight+=d[0]+d[2]),v=0;v<h.length;v++){var P,O=(P=h[v]).percentWidth;P.width=parseInt(O,10)/100*n.width}return n}(vs(t),t),n=e.width,i=e.outerWidth,r=e.outerHeight,o=t.padding,a=t.x||0,s=t.y||0,l=this._defaultStyle,u=t.align||l.align,h=t.verticalAlign||l.verticalAlign,c=wi(a,i,u),p=Si(s,r,h),d=c,f=p;o&&(d+=o[3],f+=o[0]);var g=d+n;ms(t)&&this._renderBackground(t,t,c,p,i,r);for(var y=!!t.backgroundColor,v=0;v<e.lines.length;v++){for(var m=e.lines[v],_=m.tokens,x=_.length,b=m.lineHeight,w=m.width,S=0,M=d,I=g,T=x-1,C=void 0;S<x&&(!(C=_[S]).align||\"left\"===C.align);)this._placeToken(C,t,b,f,M,\"left\",y),w-=C.width,M+=C.width,S++;for(;T>=0&&\"right\"===(C=_[T]).align;)this._placeToken(C,t,b,f,I,\"right\",y),w-=C.width,I-=C.width,T--;for(M+=(n-(M-d)-(g-I)-w)/2;S<=T;)C=_[S],this._placeToken(C,t,b,f,M+C.width/2,\"center\",y),M+=C.width,S++;f+=b}},e.prototype._placeToken=function(t,e,n,i,r,o,a){var s=e.rich[t.styleName]||{};s.text=t.text;var l=t.verticalAlign,u=i+n/2;\"top\"===l?u=i+t.height/2:\"bottom\"===l&&(u=i+n-t.height/2),!t.isLineHolder&&ms(s)&&this._renderBackground(s,e,\"right\"===o?r-t.width:\"center\"===o?r-t.width/2:r,u-t.height/2,t.width,t.height);var h=!!s.backgroundColor,c=t.textPadding;c&&(r=ys(r,o,c),u-=t.height/2-c[0]-t.innerHeight/2);var p=this._getOrCreateChild(Ja),d=p.createStyle();p.useStyle(d);var f=this._defaultStyle,g=!1,y=0,v=gs(\"fill\"in s?s.fill:\"fill\"in e?e.fill:(g=!0,f.fill)),m=gs(\"stroke\"in s?s.stroke:\"stroke\"in e?e.stroke:h||a||f.autoStroke&&!g?null:(y=2,f.stroke)),_=s.textShadowBlur>0||e.textShadowBlur>0;d.text=t.text,d.x=r,d.y=u,_&&(d.shadowBlur=s.textShadowBlur||e.textShadowBlur||0,d.shadowColor=s.textShadowColor||e.textShadowColor||\"transparent\",d.shadowOffsetX=s.textShadowOffsetX||e.textShadowOffsetX||0,d.shadowOffsetY=s.textShadowOffsetY||e.textShadowOffsetY||0),d.textAlign=o,d.textBaseline=\"middle\",d.font=t.font||vi,d.opacity=et(s.opacity,e.opacity,1),m&&(d.lineWidth=et(s.lineWidth,e.lineWidth,y),d.lineDash=tt(s.lineDash,e.lineDash),d.lineDashOffset=e.lineDashOffset||0,d.stroke=m),v&&(d.fill=v);var x=t.contentWidth,b=t.contentHeight;p.setBoundingRect(new gi(wi(d.x,x,d.textAlign),Si(d.y,b,d.textBaseline),x,b))},e.prototype._renderBackground=function(t,e,n,i,r,o){var a,s,l,u=t.backgroundColor,h=t.borderWidth,c=t.borderColor,p=u&&u.image,d=u&&!p,f=t.borderRadius,g=this;if(d||h&&c){(a=this._getOrCreateChild(ls)).useStyle(a.createStyle()),a.style.fill=null;var y=a.shape;y.x=n,y.y=i,y.width=r,y.height=o,y.r=f,a.dirtyShape()}if(d)(l=a.style).fill=u||null,l.fillOpacity=tt(t.fillOpacity,1);else if(p){(s=this._getOrCreateChild(es)).onload=function(){g.dirtyStyle()};var v=s.style;v.image=u.image,v.x=n,v.y=i,v.width=r,v.height=o}h&&c&&((l=a.style).lineWidth=h,l.stroke=c,l.strokeOpacity=tt(t.strokeOpacity,1),l.lineDash=t.borderDash,l.lineDashOffset=t.borderDashOffset||0,a.strokeContainThreshold=0,a.hasFill()&&a.hasStroke()&&(l.strokeFirst=!0,l.lineWidth*=2));var m=(a||s).style;m.shadowBlur=t.shadowBlur||0,m.shadowColor=t.shadowColor||\"transparent\",m.shadowOffsetX=t.shadowOffsetX||0,m.shadowOffsetY=t.shadowOffsetY||0,m.opacity=et(t.opacity,e.opacity,1)},e.makeFont=function(t){var e=\"\";if(t.fontSize||t.fontFamily||t.fontWeight){var n=\"\";n=\"string\"!=typeof t.fontSize||-1===t.fontSize.indexOf(\"px\")&&-1===t.fontSize.indexOf(\"rem\")&&-1===t.fontSize.indexOf(\"em\")?isNaN(+t.fontSize)?\"12px\":t.fontSize+\"px\":t.fontSize,e=[t.fontStyle,t.fontWeight,n,t.fontFamily||\"sans-serif\"].join(\" \")}return e&&ot(e)||t.textFont||t.font},e}(So),ps={left:!0,right:1,center:1},ds={top:1,bottom:1,middle:1};function fs(t){if(t){t.font=cs.makeFont(t);var e=t.align;\"middle\"===e&&(e=\"center\"),t.align=null==e||ps[e]?e:\"left\";var n=t.verticalAlign;\"center\"===n&&(n=\"middle\"),t.verticalAlign=null==n||ds[n]?n:\"top\",t.padding&&(t.padding=it(t.padding))}}function gs(t,e){return null==t||e<=0||\"transparent\"===t||\"none\"===t?null:t.image||t.colorStops?\"#000\":t}function ys(t,e,n){return\"right\"===e?t-n[1]:\"center\"===e?t+n[3]/2-n[1]/2:t+n[3]}function vs(t){var e=t.text;return null!=e&&(e+=\"\"),e}function ms(t){return!!(t.backgroundColor||t.borderWidth&&t.borderColor)}var _s=kr(),xs=1,bs={},ws=kr(),Ss=[\"emphasis\",\"blur\",\"select\"],Ms=[\"normal\",\"emphasis\",\"blur\",\"select\"],Is=10,Ts=\"highlight\",Cs=\"downplay\",Ds=\"select\",As=\"unselect\",Ls=\"toggleSelect\";function ks(t){return null!=t&&\"none\"!==t}var Ps=new Ae(100);function Os(t){if(\"string\"!=typeof t)return t;var e=Ps.get(t);return e||(e=Ue(t,-.1),Ps.put(t,e)),e}function Rs(t,e,n){t.onHoverStateChange&&(t.hoverState||0)!==n&&t.onHoverStateChange(e),t.hoverState=n}function Ns(t){Rs(t,\"emphasis\",2)}function zs(t){2===t.hoverState&&Rs(t,\"normal\",0)}function Es(t){Rs(t,\"blur\",1)}function Vs(t){1===t.hoverState&&Rs(t,\"normal\",0)}function Bs(t){t.selected=!0}function Fs(t){t.selected=!1}function Gs(t,e,n){e(t,n)}function Hs(t,e,n){Gs(t,e,n),t.isGroup&&t.traverse((function(t){Gs(t,e,n)}))}function Ws(t,e){switch(e){case\"emphasis\":t.hoverState=2;break;case\"normal\":t.hoverState=0;break;case\"blur\":t.hoverState=1;break;case\"select\":t.selected=!0}}function Us(t,e){var n=this.states[t];if(this.style){if(\"emphasis\"===t)return function(t,e,n,i){var r=n&&D(n,\"select\")>=0,o=!1;if(t instanceof Ka){var a=ws(t),s=r&&a.selectFill||a.normalFill,l=r&&a.selectStroke||a.normalStroke;if(ks(s)||ks(l)){var u=(i=i||{}).style||{};!ks(u.fill)&&ks(s)?(o=!0,i=I({},i),(u=I({},u)).fill=Os(s)):!ks(u.stroke)&&ks(l)&&(o||(i=I({},i),u=I({},u)),u.stroke=Os(l)),i.style=u}}if(i&&null==i.z2){o||(i=I({},i));var h=t.z2EmphasisLift;i.z2=t.z2+(null!=h?h:Is)}return i}(this,0,e,n);if(\"blur\"===t)return function(t,e,n){var i=D(t.currentStates,e)>=0,r=t.style.opacity,o=i?null:function(t,e,n,i){for(var r=t.style,o={},a=0;a<e.length;a++){var s=e[a],l=r[s];o[s]=null==l?i&&i[s]:l}for(a=0;a<t.animators.length;a++){var u=t.animators[a];u.__fromStateTransition&&u.__fromStateTransition.indexOf(n)<0&&\"style\"===u.targetName&&u.saveFinalToTarget(o,e)}return o}(t,[\"opacity\"],e,{opacity:1}),a=(n=n||{}).style||{};return null==a.opacity&&(n=I({},n),a=I({opacity:i?r:.1*o.opacity},a),n.style=a),n}(this,t,n);if(\"select\"===t)return function(t,e,n){if(n&&null==n.z2){n=I({},n);var i=t.z2SelectLift;n.z2=t.z2+(null!=i?i:9)}return n}(this,0,n)}return n}function Xs(t){t.stateProxy=Us;var e=t.getTextContent(),n=t.getTextGuideLine();e&&(e.stateProxy=Us),n&&(n.stateProxy=Us)}function Ys(t,e){!tl(t,e)&&!t.__highByOuter&&Hs(t,Ns)}function Zs(t,e){!tl(t,e)&&!t.__highByOuter&&Hs(t,zs)}function js(t,e){t.__highByOuter|=1<<(e||0),Hs(t,Ns)}function qs(t,e){!(t.__highByOuter&=~(1<<(e||0)))&&Hs(t,zs)}function Ks(t){Hs(t,Es)}function $s(t){Hs(t,Vs)}function Js(t){Hs(t,Bs)}function Qs(t){Hs(t,Fs)}function tl(t,e){return t.__highDownSilentOnTouch&&e.zrByTouch}function el(t){t.getModel().eachComponent((function(e,n){(\"series\"===e?t.getViewOfSeriesModel(n):t.getViewOfComponentModel(n)).group.traverse((function(t){Vs(t)}))}))}function nl(t,e,n,i){var r=i.getModel();function o(t,e){for(var n=0;n<e.length;n++){var i=t.getItemGraphicEl(e[n]);i&&$s(i)}}if(n=n||\"coordinateSystem\",null!=t&&e&&\"none\"!==e){var a=r.getSeriesByIndex(t),s=a.coordinateSystem;s&&s.master&&(s=s.master);var l=[];r.eachSeries((function(t){var r=a===t,u=t.coordinateSystem;if(u&&u.master&&(u=u.master),!(\"series\"===n&&!r||\"coordinateSystem\"===n&&!(u&&s?u===s:r)||\"series\"===e&&r)){if(i.getViewOfSeriesModel(t).group.traverse((function(t){Es(t)})),k(e))o(t.getData(),e);else if(X(e))for(var h=E(e),c=0;c<h.length;c++)o(t.getData(h[c]),e[h[c]]);l.push(t)}})),r.eachComponent((function(t,e){if(\"series\"!==t){var n=i.getViewOfComponentModel(e);n&&n.blurSeries&&n.blurSeries(l,r)}}))}}function il(t,e,n){if(null!=t&&null!=e){var i=n.getModel().getComponent(t,e);if(i){var r=n.getViewOfComponentModel(i);r&&r.focusBlurEnabled&&r.group.traverse((function(t){Es(t)}))}}}function rl(t,e,n,i){var r={focusSelf:!1,dispatchers:null};if(null==t||\"series\"===t||null==e||null==n)return r;var o=i.getModel().getComponent(t,e);if(!o)return r;var a=i.getViewOfComponentModel(o);if(!a||!a.findHighDownDispatchers)return r;for(var s,l=a.findHighDownDispatchers(n),u=0;u<l.length;u++)if(\"self\"===_s(l[u]).focus){s=!0;break}return{focusSelf:s,dispatchers:l}}function ol(t){P(t.getAllData(),(function(e){var n=e.data,i=e.type;n.eachItemGraphicEl((function(e,n){t.isSelected(n,i)?Js(e):Qs(e)}))}))}function al(t){var e=[];return t.eachSeries((function(t){P(t.getAllData(),(function(n){n.data;var i=n.type,r=t.getSelectedDataIndices();if(r.length>0){var o={dataIndex:r,seriesIndex:t.seriesIndex};null!=i&&(o.dataType=i),e.push(o)}}))})),e}function sl(t,e,n){pl(t,!0),Hs(t,Xs),ll(t,e,n)}function ll(t,e,n){var i=_s(t);null!=e?(i.focus=e,i.blurScope=n):i.focus&&(i.focus=null)}var ul=[\"emphasis\",\"blur\",\"select\"],hl={itemStyle:\"getItemStyle\",lineStyle:\"getLineStyle\",areaStyle:\"getAreaStyle\"};function cl(t,e,n,i){n=n||\"itemStyle\";for(var r=0;r<ul.length;r++){var o=ul[r],a=e.getModel([o,n]);t.ensureState(o).style=i?i(a):a[hl[n]]()}}function pl(t,e){var n=!1===e,i=t;t.highDownSilentOnTouch&&(i.__highDownSilentOnTouch=t.highDownSilentOnTouch),n&&!i.__highDownDispatcher||(i.__highByOuter=i.__highByOuter||0,i.__highDownDispatcher=!n)}function dl(t){return!(!t||!t.__highDownDispatcher)}function fl(t){var e=t.type;return e===Ds||e===As||e===Ls}function gl(t){var e=t.type;return e===Ts||e===Cs}var yl=La.CMD,vl=[[],[],[]],ml=Math.sqrt,_l=Math.atan2;var xl=Math.sqrt,bl=Math.sin,wl=Math.cos,Sl=Math.PI;function Ml(t){return Math.sqrt(t[0]*t[0]+t[1]*t[1])}function Il(t,e){return(t[0]*e[0]+t[1]*e[1])/(Ml(t)*Ml(e))}function Tl(t,e){return(t[0]*e[1]<t[1]*e[0]?-1:1)*Math.acos(Il(t,e))}function Cl(t,e,n,i,r,o,a,s,l,u,h){var c=l*(Sl/180),p=wl(c)*(t-n)/2+bl(c)*(e-i)/2,d=-1*bl(c)*(t-n)/2+wl(c)*(e-i)/2,f=p*p/(a*a)+d*d/(s*s);f>1&&(a*=xl(f),s*=xl(f));var g=(r===o?-1:1)*xl((a*a*(s*s)-a*a*(d*d)-s*s*(p*p))/(a*a*(d*d)+s*s*(p*p)))||0,y=g*a*d/s,v=g*-s*p/a,m=(t+n)/2+wl(c)*y-bl(c)*v,_=(e+i)/2+bl(c)*y+wl(c)*v,x=Tl([1,0],[(p-y)/a,(d-v)/s]),b=[(p-y)/a,(d-v)/s],w=[(-1*p-y)/a,(-1*d-v)/s],S=Tl(b,w);if(Il(b,w)<=-1&&(S=Sl),Il(b,w)>=1&&(S=0),S<0){var M=Math.round(S/Sl*1e6)/1e6;S=2*Sl+M%2*Sl}h.addData(u,m,_,a,s,x,S,c,o)}var Dl=/([mlvhzcqtsa])([^mlvhzcqtsa]*)/gi,Al=/-?([0-9]*\\.)?[0-9]+([eE]-?[0-9]+)?/g;var Ll=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.applyTransform=function(t){},e}(Ka);function kl(t){return null!=t.setData}function Pl(t,e){var n=function(t){var e=new La;if(!t)return e;var n,i=0,r=0,o=i,a=r,s=La.CMD,l=t.match(Dl);if(!l)return e;for(var u=0;u<l.length;u++){for(var h=l[u],c=h.charAt(0),p=void 0,d=h.match(Al)||[],f=d.length,g=0;g<f;g++)d[g]=parseFloat(d[g]);for(var y=0;y<f;){var v=void 0,m=void 0,_=void 0,x=void 0,b=void 0,w=void 0,S=void 0,M=i,I=r,T=void 0,C=void 0;switch(c){case\"l\":i+=d[y++],r+=d[y++],p=s.L,e.addData(p,i,r);break;case\"L\":i=d[y++],r=d[y++],p=s.L,e.addData(p,i,r);break;case\"m\":i+=d[y++],r+=d[y++],p=s.M,e.addData(p,i,r),o=i,a=r,c=\"l\";break;case\"M\":i=d[y++],r=d[y++],p=s.M,e.addData(p,i,r),o=i,a=r,c=\"L\";break;case\"h\":i+=d[y++],p=s.L,e.addData(p,i,r);break;case\"H\":i=d[y++],p=s.L,e.addData(p,i,r);break;case\"v\":r+=d[y++],p=s.L,e.addData(p,i,r);break;case\"V\":r=d[y++],p=s.L,e.addData(p,i,r);break;case\"C\":p=s.C,e.addData(p,d[y++],d[y++],d[y++],d[y++],d[y++],d[y++]),i=d[y-2],r=d[y-1];break;case\"c\":p=s.C,e.addData(p,d[y++]+i,d[y++]+r,d[y++]+i,d[y++]+r,d[y++]+i,d[y++]+r),i+=d[y-2],r+=d[y-1];break;case\"S\":v=i,m=r,T=e.len(),C=e.data,n===s.C&&(v+=i-C[T-4],m+=r-C[T-3]),p=s.C,M=d[y++],I=d[y++],i=d[y++],r=d[y++],e.addData(p,v,m,M,I,i,r);break;case\"s\":v=i,m=r,T=e.len(),C=e.data,n===s.C&&(v+=i-C[T-4],m+=r-C[T-3]),p=s.C,M=i+d[y++],I=r+d[y++],i+=d[y++],r+=d[y++],e.addData(p,v,m,M,I,i,r);break;case\"Q\":M=d[y++],I=d[y++],i=d[y++],r=d[y++],p=s.Q,e.addData(p,M,I,i,r);break;case\"q\":M=d[y++]+i,I=d[y++]+r,i+=d[y++],r+=d[y++],p=s.Q,e.addData(p,M,I,i,r);break;case\"T\":v=i,m=r,T=e.len(),C=e.data,n===s.Q&&(v+=i-C[T-4],m+=r-C[T-3]),i=d[y++],r=d[y++],p=s.Q,e.addData(p,v,m,i,r);break;case\"t\":v=i,m=r,T=e.len(),C=e.data,n===s.Q&&(v+=i-C[T-4],m+=r-C[T-3]),i+=d[y++],r+=d[y++],p=s.Q,e.addData(p,v,m,i,r);break;case\"A\":_=d[y++],x=d[y++],b=d[y++],w=d[y++],S=d[y++],Cl(M=i,I=r,i=d[y++],r=d[y++],w,S,_,x,b,p=s.A,e);break;case\"a\":_=d[y++],x=d[y++],b=d[y++],w=d[y++],S=d[y++],Cl(M=i,I=r,i+=d[y++],r+=d[y++],w,S,_,x,b,p=s.A,e)}}\"z\"!==c&&\"Z\"!==c||(p=s.Z,e.addData(p),i=o,r=a),n=p}return e.toStatic(),e}(t),i=I({},e);return i.buildPath=function(t){if(kl(t)){t.setData(n.data),(e=t.getContext())&&t.rebuildPath(e,1)}else{var e=t;n.rebuildPath(e,1)}},i.applyTransform=function(t){!function(t,e){var n,i,r,o,a,s,l=t.data,u=t.len(),h=yl.M,c=yl.C,p=yl.L,d=yl.R,f=yl.A,g=yl.Q;for(r=0,o=0;r<u;){switch(n=l[r++],o=r,i=0,n){case h:case p:i=1;break;case c:i=3;break;case g:i=2;break;case f:var y=e[4],v=e[5],m=ml(e[0]*e[0]+e[1]*e[1]),_=ml(e[2]*e[2]+e[3]*e[3]),x=_l(-e[1]/_,e[0]/m);l[r]*=m,l[r++]+=y,l[r]*=_,l[r++]+=v,l[r++]*=m,l[r++]*=_,l[r++]+=x,l[r++]+=x,o=r+=2;break;case d:s[0]=l[r++],s[1]=l[r++],Rt(s,s,e),l[o++]=s[0],l[o++]=s[1],s[0]+=l[r++],s[1]+=l[r++],Rt(s,s,e),l[o++]=s[0],l[o++]=s[1]}for(a=0;a<i;a++){var b=vl[a];b[0]=l[r++],b[1]=l[r++],Rt(b,b,e),l[o++]=b[0],l[o++]=b[1]}}t.increaseVersion()}(n,t),this.dirtyShape()},i}function Ol(t,e){return new Ll(Pl(t,e))}var Rl=function(){this.cx=0,this.cy=0,this.r=0},Nl=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Rl},e.prototype.buildPath=function(t,e,n){n&&t.moveTo(e.cx+e.r,e.cy),t.arc(e.cx,e.cy,e.r,0,2*Math.PI)},e}(Ka);Nl.prototype.type=\"circle\";var zl=function(){this.cx=0,this.cy=0,this.rx=0,this.ry=0},El=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new zl},e.prototype.buildPath=function(t,e){var n=.5522848,i=e.cx,r=e.cy,o=e.rx,a=e.ry,s=o*n,l=a*n;t.moveTo(i-o,r),t.bezierCurveTo(i-o,r-l,i-s,r-a,i,r-a),t.bezierCurveTo(i+s,r-a,i+o,r-l,i+o,r),t.bezierCurveTo(i+o,r+l,i+s,r+a,i,r+a),t.bezierCurveTo(i-s,r+a,i-o,r+l,i-o,r),t.closePath()},e}(Ka);El.prototype.type=\"ellipse\";var Vl=Math.PI,Bl=2*Vl,Fl=Math.sin,Gl=Math.cos,Hl=Math.acos,Wl=Math.atan2,Ul=Math.abs,Xl=Math.sqrt,Yl=Math.max,Zl=Math.min,jl=1e-4;function ql(t,e,n,i,r,o,a){var s=t-n,l=e-i,u=(a?o:-o)/Xl(s*s+l*l),h=u*l,c=-u*s,p=t+h,d=e+c,f=n+h,g=i+c,y=(p+f)/2,v=(d+g)/2,m=f-p,_=g-d,x=m*m+_*_,b=r-o,w=p*g-f*d,S=(_<0?-1:1)*Xl(Yl(0,b*b*x-w*w)),M=(w*_-m*S)/x,I=(-w*m-_*S)/x,T=(w*_+m*S)/x,C=(-w*m+_*S)/x,D=M-y,A=I-v,L=T-y,k=C-v;return D*D+A*A>L*L+k*k&&(M=T,I=C),{cx:M,cy:I,x01:-h,y01:-c,x11:M*(r/b-1),y11:I*(r/b-1)}}function Kl(t,e){var n=Yl(e.r,0),i=Yl(e.r0||0,0),r=n>0;if(r||i>0){if(r||(n=i,i=0),i>n){var o=n;n=i,i=o}var a,s=!!e.clockwise,l=e.startAngle,u=e.endAngle;if(l===u)a=0;else{var h=[l,u];Aa(h,!s),a=Ul(h[0]-h[1])}var c=e.cx,p=e.cy,d=e.cornerRadius||0,f=e.innerCornerRadius||0;if(n>jl)if(a>Bl-jl)t.moveTo(c+n*Gl(l),p+n*Fl(l)),t.arc(c,p,n,l,u,!s),i>jl&&(t.moveTo(c+i*Gl(u),p+i*Fl(u)),t.arc(c,p,i,u,l,s));else{var g=Ul(n-i)/2,y=Zl(g,d),v=Zl(g,f),m=v,_=y,x=n*Gl(l),b=n*Fl(l),w=i*Gl(u),S=i*Fl(u),M=void 0,I=void 0,T=void 0,C=void 0;if((y>jl||v>jl)&&(M=n*Gl(u),I=n*Fl(u),T=i*Gl(l),C=i*Fl(l),a<Vl)){var D=function(t,e,n,i,r,o,a,s){var l=n-t,u=i-e,h=a-r,c=s-o,p=c*l-h*u;if(!(p*p<jl))return[t+(p=(h*(e-o)-c*(t-r))/p)*l,e+p*u]}(x,b,T,C,M,I,w,S);if(D){var A=x-D[0],L=b-D[1],k=M-D[0],P=I-D[1],O=1/Fl(Hl((A*k+L*P)/(Xl(A*A+L*L)*Xl(k*k+P*P)))/2),R=Xl(D[0]*D[0]+D[1]*D[1]);m=Zl(v,(i-R)/(O-1)),_=Zl(y,(n-R)/(O+1))}}if(a>jl)if(_>jl){var N=ql(T,C,x,b,n,_,s),z=ql(M,I,w,S,n,_,s);t.moveTo(c+N.cx+N.x01,p+N.cy+N.y01),_<y?t.arc(c+N.cx,p+N.cy,_,Wl(N.y01,N.x01),Wl(z.y01,z.x01),!s):(t.arc(c+N.cx,p+N.cy,_,Wl(N.y01,N.x01),Wl(N.y11,N.x11),!s),t.arc(c,p,n,Wl(N.cy+N.y11,N.cx+N.x11),Wl(z.cy+z.y11,z.cx+z.x11),!s),t.arc(c+z.cx,p+z.cy,_,Wl(z.y11,z.x11),Wl(z.y01,z.x01),!s))}else t.moveTo(c+x,p+b),t.arc(c,p,n,l,u,!s);else t.moveTo(c+x,p+b);if(i>jl&&a>jl)if(m>jl){N=ql(w,S,M,I,i,-m,s),z=ql(x,b,T,C,i,-m,s);t.lineTo(c+N.cx+N.x01,p+N.cy+N.y01),m<v?t.arc(c+N.cx,p+N.cy,m,Wl(N.y01,N.x01),Wl(z.y01,z.x01),!s):(t.arc(c+N.cx,p+N.cy,m,Wl(N.y01,N.x01),Wl(N.y11,N.x11),!s),t.arc(c,p,i,Wl(N.cy+N.y11,N.cx+N.x11),Wl(z.cy+z.y11,z.cx+z.x11),s),t.arc(c+z.cx,p+z.cy,m,Wl(z.y11,z.x11),Wl(z.y01,z.x01),!s))}else t.lineTo(c+w,p+S),t.arc(c,p,i,u,l,s);else t.lineTo(c+w,p+S)}else t.moveTo(c,p);t.closePath()}}var $l=function(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0,this.cornerRadius=0,this.innerCornerRadius=0},Jl=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new $l},e.prototype.buildPath=function(t,e){Kl(t,e)},e.prototype.isZeroArea=function(){return this.shape.startAngle===this.shape.endAngle||this.shape.r===this.shape.r0},e}(Ka);Jl.prototype.type=\"sector\";var Ql=function(){this.cx=0,this.cy=0,this.r=0,this.r0=0},tu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new Ql},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=2*Math.PI;t.moveTo(n+e.r,i),t.arc(n,i,e.r,0,r,!1),t.moveTo(n+e.r0,i),t.arc(n,i,e.r0,0,r,!0)},e}(Ka);function eu(t,e,n,i,r,o,a){var s=.5*(n-t),l=.5*(i-e);return(2*(e-n)+s+l)*a+(-3*(e-n)-2*s-l)*o+s*r+e}function nu(t,e,n){var i=e.smooth,r=e.points;if(r&&r.length>=2){if(i&&\"spline\"!==i){var o=function(t,e,n,i){var r,o,a,s,l=[],u=[],h=[],c=[];if(i){a=[1/0,1/0],s=[-1/0,-1/0];for(var p=0,d=t.length;p<d;p++)Nt(a,a,t[p]),zt(s,s,t[p]);Nt(a,a,i[0]),zt(s,s,i[1])}for(p=0,d=t.length;p<d;p++){var f=t[p];if(n)r=t[p?p-1:d-1],o=t[(p+1)%d];else{if(0===p||p===d-1){l.push(mt(t[p]));continue}r=t[p-1],o=t[p+1]}wt(u,o,r),Ct(u,u,e);var g=At(f,r),y=At(f,o),v=g+y;0!==v&&(g/=v,y/=v),Ct(h,u,-g),Ct(c,u,y);var m=xt([],f,h),_=xt([],f,c);i&&(zt(m,m,a),Nt(m,m,s),zt(_,_,a),Nt(_,_,s)),l.push(m),l.push(_)}return n&&l.push(l.shift()),l}(r,i,n,e.smoothConstraint);t.moveTo(r[0][0],r[0][1]);for(var a=r.length,s=0;s<(n?a:a-1);s++){var l=o[2*s],u=o[2*s+1],h=r[(s+1)%a];t.bezierCurveTo(l[0],l[1],u[0],u[1],h[0],h[1])}}else{\"spline\"===i&&(r=function(t,e){for(var n=t.length,i=[],r=0,o=1;o<n;o++)r+=At(t[o-1],t[o]);var a=r/2;for(a=a<n?n:a,o=0;o<a;o++){var s=o/(a-1)*(e?n:n-1),l=Math.floor(s),u=s-l,h=void 0,c=t[l%n],p=void 0,d=void 0;e?(h=t[(l-1+n)%n],p=t[(l+1)%n],d=t[(l+2)%n]):(h=t[0===l?l:l-1],p=t[l>n-2?n-1:l+1],d=t[l>n-3?n-1:l+2]);var f=u*u,g=u*f;i.push([eu(h[0],c[0],p[0],d[0],u,f,g),eu(h[1],c[1],p[1],d[1],u,f,g)])}return i}(r,n)),t.moveTo(r[0][0],r[0][1]);s=1;for(var c=r.length;s<c;s++)t.lineTo(r[s][0],r[s][1])}n&&t.closePath()}}tu.prototype.type=\"ring\";var iu=function(){this.points=null,this.smooth=0,this.smoothConstraint=null},ru=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new iu},e.prototype.buildPath=function(t,e){nu(t,e,!0)},e}(Ka);ru.prototype.type=\"polygon\";var ou=function(){this.points=null,this.percent=1,this.smooth=0,this.smoothConstraint=null},au=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new ou},e.prototype.buildPath=function(t,e){nu(t,e,!1)},e}(Ka);au.prototype.type=\"polyline\";var su={},lu=function(){this.x1=0,this.y1=0,this.x2=0,this.y2=0,this.percent=1},uu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new lu},e.prototype.buildPath=function(t,e){var n,i,r,o;if(this.subPixelOptimize){var a=is(su,e,this.style);n=a.x1,i=a.y1,r=a.x2,o=a.y2}else n=e.x1,i=e.y1,r=e.x2,o=e.y2;var s=e.percent;0!==s&&(t.moveTo(n,i),s<1&&(r=n*(1-s)+r*s,o=i*(1-s)+o*s),t.lineTo(r,o))},e.prototype.pointAt=function(t){var e=this.shape;return[e.x1*(1-t)+e.x2*t,e.y1*(1-t)+e.y2*t]},e}(Ka);uu.prototype.type=\"line\";var hu=[],cu=function(){this.x1=0,this.y1=0,this.x2=0,this.y2=0,this.cpx1=0,this.cpy1=0,this.percent=1};function pu(t,e,n){var i=t.cpx2,r=t.cpy2;return null===i||null===r?[(n?Vo:Eo)(t.x1,t.cpx1,t.cpx2,t.x2,e),(n?Vo:Eo)(t.y1,t.cpy1,t.cpy2,t.y2,e)]:[(n?Xo:Uo)(t.x1,t.cpx1,t.x2,e),(n?Xo:Uo)(t.y1,t.cpy1,t.y2,e)]}var du=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new cu},e.prototype.buildPath=function(t,e){var n=e.x1,i=e.y1,r=e.x2,o=e.y2,a=e.cpx1,s=e.cpy1,l=e.cpx2,u=e.cpy2,h=e.percent;0!==h&&(t.moveTo(n,i),null==l||null==u?(h<1&&(Zo(n,a,r,h,hu),a=hu[1],r=hu[2],Zo(i,s,o,h,hu),s=hu[1],o=hu[2]),t.quadraticCurveTo(a,s,r,o)):(h<1&&(Go(n,a,l,r,h,hu),a=hu[1],l=hu[2],r=hu[3],Go(i,s,u,o,h,hu),s=hu[1],u=hu[2],o=hu[3]),t.bezierCurveTo(a,s,l,u,r,o)))},e.prototype.pointAt=function(t){return pu(this.shape,t,!1)},e.prototype.tangentAt=function(t){var e=pu(this.shape,t,!0);return Dt(e,e)},e}(Ka);du.prototype.type=\"bezier-curve\";var fu=function(){this.cx=0,this.cy=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0},gu=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new fu},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=Math.max(e.r,0),o=e.startAngle,a=e.endAngle,s=e.clockwise,l=Math.cos(o),u=Math.sin(o);t.moveTo(l*r+n,u*r+i),t.arc(n,i,r,o,a,!s)},e}(Ka);gu.prototype.type=\"arc\";var yu=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"compound\",e}return n(e,t),e.prototype._updatePathDirty=function(){for(var t=this.shape.paths,e=this.shapeChanged(),n=0;n<t.length;n++)e=e||t[n].shapeChanged();e&&this.dirtyShape()},e.prototype.beforeBrush=function(){this._updatePathDirty();for(var t=this.shape.paths||[],e=this.getGlobalScale(),n=0;n<t.length;n++)t[n].path||t[n].createPathProxy(),t[n].path.setScale(e[0],e[1],t[n].segmentIgnoreThreshold)},e.prototype.buildPath=function(t,e){for(var n=e.paths||[],i=0;i<n.length;i++)n[i].buildPath(t,n[i].shape,!0)},e.prototype.afterBrush=function(){for(var t=this.shape.paths||[],e=0;e<t.length;e++)t[e].pathUpdated()},e.prototype.getBoundingRect=function(){return this._updatePathDirty.call(this),Ka.prototype.getBoundingRect.call(this)},e}(Ka),vu=function(){function t(t){this.colorStops=t||[]}return t.prototype.addColorStop=function(t,e){this.colorStops.push({offset:t,color:e})},t}(),mu=function(t){function e(e,n,i,r,o,a){var s=t.call(this,o)||this;return s.x=null==e?0:e,s.y=null==n?0:n,s.x2=null==i?1:i,s.y2=null==r?0:r,s.type=\"linear\",s.global=a||!1,s}return n(e,t),e}(vu),_u=function(t){function e(e,n,i,r,o){var a=t.call(this,r)||this;return a.x=null==e?.5:e,a.y=null==n?.5:n,a.r=null==i?.5:i,a.type=\"radial\",a.global=o||!1,a}return n(e,t),e}(vu),xu=[0,0],bu=[0,0],wu=new ai,Su=new ai,Mu=function(){function t(t,e){this._corners=[],this._axes=[],this._origin=[0,0];for(var n=0;n<4;n++)this._corners[n]=new ai;for(n=0;n<2;n++)this._axes[n]=new ai;t&&this.fromBoundingRect(t,e)}return t.prototype.fromBoundingRect=function(t,e){var n=this._corners,i=this._axes,r=t.x,o=t.y,a=r+t.width,s=o+t.height;if(n[0].set(r,o),n[1].set(a,o),n[2].set(a,s),n[3].set(r,s),e)for(var l=0;l<4;l++)n[l].transform(e);ai.sub(i[0],n[1],n[0]),ai.sub(i[1],n[3],n[0]),i[0].normalize(),i[1].normalize();for(l=0;l<2;l++)this._origin[l]=i[l].dot(n[0])},t.prototype.intersect=function(t,e){var n=!0,i=!e;return wu.set(1/0,1/0),Su.set(0,0),!this._intersectCheckOneSide(this,t,wu,Su,i,1)&&(n=!1,i)||!this._intersectCheckOneSide(t,this,wu,Su,i,-1)&&(n=!1,i)||i||ai.copy(e,n?wu:Su),n},t.prototype._intersectCheckOneSide=function(t,e,n,i,r,o){for(var a=!0,s=0;s<2;s++){var l=this._axes[s];if(this._getProjMinMaxOnAxis(s,t._corners,xu),this._getProjMinMaxOnAxis(s,e._corners,bu),xu[1]<bu[0]||xu[0]>bu[1]){if(a=!1,r)return a;var u=Math.abs(bu[0]-xu[1]),h=Math.abs(xu[0]-bu[1]);Math.min(u,h)>i.len()&&(u<h?ai.scale(i,l,-u*o):ai.scale(i,l,h*o))}else if(n){u=Math.abs(bu[0]-xu[1]),h=Math.abs(xu[0]-bu[1]);Math.min(u,h)<n.len()&&(u<h?ai.scale(n,l,u*o):ai.scale(n,l,-h*o))}}return a},t.prototype._getProjMinMaxOnAxis=function(t,e,n){for(var i=this._axes[t],r=this._origin,o=e[0].dot(i)+r[t],a=o,s=o,l=1;l<e.length;l++){var u=e[l].dot(i)+r[t];a=Math.min(u,a),s=Math.max(u,s)}n[0]=a,n[1]=s},t}(),Iu=[],Tu=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.notClear=!0,e.incremental=!0,e._displayables=[],e._temporaryDisplayables=[],e._cursor=0,e}return n(e,t),e.prototype.traverse=function(t,e){t.call(e,this)},e.prototype.useStyle=function(){this.style={}},e.prototype.getCursor=function(){return this._cursor},e.prototype.innerAfterBrush=function(){this._cursor=this._displayables.length},e.prototype.clearDisplaybles=function(){this._displayables=[],this._temporaryDisplayables=[],this._cursor=0,this.markRedraw(),this.notClear=!1},e.prototype.clearTemporalDisplayables=function(){this._temporaryDisplayables=[]},e.prototype.addDisplayable=function(t,e){e?this._temporaryDisplayables.push(t):this._displayables.push(t),this.markRedraw()},e.prototype.addDisplayables=function(t,e){e=e||!1;for(var n=0;n<t.length;n++)this.addDisplayable(t[n],e)},e.prototype.getDisplayables=function(){return this._displayables},e.prototype.getTemporalDisplayables=function(){return this._temporaryDisplayables},e.prototype.eachPendingDisplayable=function(t){for(var e=this._cursor;e<this._displayables.length;e++)t&&t(this._displayables[e]);for(e=0;e<this._temporaryDisplayables.length;e++)t&&t(this._temporaryDisplayables[e])},e.prototype.update=function(){this.updateTransform();for(var t=this._cursor;t<this._displayables.length;t++){(e=this._displayables[t]).parent=this,e.update(),e.parent=null}for(t=0;t<this._temporaryDisplayables.length;t++){var e;(e=this._temporaryDisplayables[t]).parent=this,e.update(),e.parent=null}},e.prototype.getBoundingRect=function(){if(!this._rect){for(var t=new gi(1/0,1/0,-1/0,-1/0),e=0;e<this._displayables.length;e++){var n=this._displayables[e],i=n.getBoundingRect().clone();n.needLocalTransform()&&i.applyTransform(n.getLocalTransform(Iu)),t.union(i)}this._rect=t}return this._rect},e.prototype.contain=function(t,e){var n=this.transformCoordToLocal(t,e);if(this.getBoundingRect().contain(n[0],n[1]))for(var i=0;i<this._displayables.length;i++){if(this._displayables[i].contain(t,e))return!0}return!1},e}(So),Cu=Math.max,Du=Math.min,Au={};function Lu(t){return Ka.extend(t)}var ku=function(t,e){var i=Pl(t,e);return function(t){function e(e){var n=t.call(this,e)||this;return n.applyTransform=i.applyTransform,n.buildPath=i.buildPath,n}return n(e,t),e}(Ll)};function Pu(t,e){return ku(t,e)}function Ou(t,e){Au[t]=e}function Ru(t){if(Au.hasOwnProperty(t))return Au[t]}function Nu(t,e,n,i){var r=Ol(t,e);return n&&(\"center\"===i&&(n=Eu(n,r.getBoundingRect())),Bu(r,n)),r}function zu(t,e,n){var i=new es({style:{image:t,x:e.x,y:e.y,width:e.width,height:e.height},onload:function(t){if(\"center\"===n){var r={width:t.width,height:t.height};i.setStyle(Eu(e,r))}}});return i}function Eu(t,e){var n,i=e.width/e.height,r=t.height*i;return n=r<=t.width?t.height:(r=t.width)/i,{x:t.x+t.width/2-r/2,y:t.y+t.height/2-n/2,width:r,height:n}}var Vu=function(t,e){for(var n=[],i=t.length,r=0;r<i;r++){var o=t[r];o.path||o.createPathProxy(),o.shapeChanged()&&o.buildPath(o.path,o.shape,!0),n.push(o.path)}var a=new Ka(e);return a.createPathProxy(),a.buildPath=function(t){if(kl(t)){t.appendPath(n);var e=t.getContext();e&&t.rebuildPath(e,1)}},a};function Bu(t,e){if(t.applyTransform){var n=t.getBoundingRect().calculateTransform(e);t.applyTransform(n)}}var Fu=os;function Gu(t,e,n,i,r,o,a){var s,l=!1;\"function\"==typeof r?(a=o,o=r,r=null):X(r)&&(o=r.cb,a=r.during,l=r.isFrom,s=r.removeOpt,r=r.dataIndex);var u,h=\"update\"===t,c=\"remove\"===t;if(i&&i.ecModel){var p=i.ecModel.getUpdatePayload();u=p&&p.animation}var d=i&&i.isAnimationEnabled();if(c||e.stopAnimation(\"remove\"),d){var f=void 0,g=void 0,y=void 0;u?(f=u.duration||0,g=u.easing||\"cubicOut\",y=u.delay||0):c?(f=tt((s=s||{}).duration,200),g=tt(s.easing,\"cubicOut\"),y=0):(f=i.getShallow(h?\"animationDurationUpdate\":\"animationDuration\"),g=i.getShallow(h?\"animationEasingUpdate\":\"animationEasing\"),y=i.getShallow(h?\"animationDelayUpdate\":\"animationDelay\")),\"function\"==typeof y&&(y=y(r,i.getAnimationDelayParams?i.getAnimationDelayParams(e,r):null)),\"function\"==typeof f&&(f=f(r)),f>0?l?e.animateFrom(n,{duration:f,delay:y||0,easing:g,done:o,force:!!o||!!a,scope:t,during:a}):e.animateTo(n,{duration:f,delay:y||0,easing:g,done:o,force:!!o||!!a,setToFinal:!0,scope:t,during:a}):(e.stopAnimation(),!l&&e.attr(n),o&&o())}else e.stopAnimation(),!l&&e.attr(n),a&&a(1),o&&o()}function Hu(t,e,n,i,r,o){Gu(\"update\",t,e,n,i,r,o)}function Wu(t,e,n,i,r,o){Gu(\"init\",t,e,n,i,r,o)}function Uu(t,e,n,i,r,o){Zu(t)||Gu(\"remove\",t,e,n,i,r,o)}function Xu(t,e,n,i){t.removeTextContent(),t.removeTextGuideLine(),Uu(t,{style:{opacity:0}},e,n,i)}function Yu(t,e,n){function i(){t.parent&&t.parent.remove(t)}t.isGroup?t.traverse((function(t){t.isGroup||Xu(t,e,n,i)})):Xu(t,e,n,i)}function Zu(t){if(!t.__zr)return!0;for(var e=0;e<t.animators.length;e++){if(\"remove\"===t.animators[e].scope)return!0}return!1}function ju(t,e){for(var n=Gn([]);t&&t!==e;)Wn(n,t.getLocalTransform(),n),t=t.parent;return n}function qu(t,e,n){return e&&!k(e)&&(e=oi.getLocalTransform(e)),n&&(e=Zn([],e)),Rt([],t,e)}function Ku(t,e,n){var i=0===e[4]||0===e[5]||0===e[0]?1:Math.abs(2*e[4]/e[0]),r=0===e[4]||0===e[5]||0===e[2]?1:Math.abs(2*e[4]/e[2]),o=[\"left\"===t?-i:\"right\"===t?i:0,\"top\"===t?-r:\"bottom\"===t?r:0];return o=qu(o,e,n),Math.abs(o[0])>Math.abs(o[1])?o[0]>0?\"right\":\"left\":o[1]>0?\"bottom\":\"top\"}function $u(t){return!t.isGroup}function Ju(t,e,n){if(t&&e){var i,r=(i={},t.traverse((function(t){$u(t)&&t.anid&&(i[t.anid]=t)})),i);e.traverse((function(t){if($u(t)&&t.anid){var e=r[t.anid];if(e){var i=o(t);t.attr(o(e)),Hu(t,i,n,_s(t).dataIndex)}}}))}function o(t){var e={x:t.x,y:t.y,rotation:t.rotation};return function(t){return null!=t.shape}(t)&&(e.shape=I({},t.shape)),e}}function Qu(t,e){return O(t,(function(t){var n=t[0];n=Cu(n,e.x),n=Du(n,e.x+e.width);var i=t[1];return i=Cu(i,e.y),[n,i=Du(i,e.y+e.height)]}))}function th(t,e){var n=Cu(t.x,e.x),i=Du(t.x+t.width,e.x+e.width),r=Cu(t.y,e.y),o=Du(t.y+t.height,e.y+e.height);if(i>=n&&o>=r)return{x:n,y:r,width:i-n,height:o-r}}function eh(t,e,n){var i=I({rectHover:!0},e),r=i.style={strokeNoScale:!0};if(n=n||{x:-1,y:-1,width:2,height:2},t)return 0===t.indexOf(\"image://\")?(r.image=t.slice(8),T(r,n),new es(i)):Nu(t.replace(\"path://\",\"\"),i,n,\"center\")}function nh(t,e,n,i,r){for(var o=0,a=r[r.length-1];o<r.length;o++){var s=r[o];if(ih(t,e,n,i,s[0],s[1],a[0],a[1]))return!0;a=s}}function ih(t,e,n,i,r,o,a,s){var l,u=n-t,h=i-e,c=a-r,p=s-o,d=rh(c,p,u,h);if((l=d)<=1e-6&&l>=-1e-6)return!1;var f=t-r,g=e-o,y=rh(f,g,u,h)/d;if(y<0||y>1)return!1;var v=rh(f,g,c,p)/d;return!(v<0||v>1)}function rh(t,e,n,i){return t*i-n*e}function oh(t){var e=t.itemTooltipOption,n=t.componentModel,i=t.itemName,r=H(e)?{formatter:e}:e,o=n.mainType,a=n.componentIndex,s={componentType:o,name:i,$vars:[\"name\"]};s[o+\"Index\"]=a;var l=t.formatterParamsExtra;l&&P(E(l),(function(t){dt(s,t)||(s[t]=l[t],s.$vars.push(t))}));var u=_s(t.el);u.componentMainType=o,u.componentIndex=a,u.tooltipConfig={name:i,option:T({content:i,formatterParams:s},r)}}Ou(\"circle\",Nl),Ou(\"ellipse\",El),Ou(\"sector\",Jl),Ou(\"ring\",tu),Ou(\"polygon\",ru),Ou(\"polyline\",au),Ou(\"rect\",ls),Ou(\"line\",uu),Ou(\"bezierCurve\",du),Ou(\"arc\",gu);var ah=Object.freeze({__proto__:null,extendShape:Lu,extendPath:Pu,registerShape:Ou,getShapeClass:Ru,makePath:Nu,makeImage:zu,mergePath:Vu,resizePath:Bu,subPixelOptimizeLine:function(t){return is(t.shape,t.shape,t.style),t},subPixelOptimizeRect:function(t){return rs(t.shape,t.shape,t.style),t},subPixelOptimize:Fu,updateProps:Hu,initProps:Wu,removeElement:Uu,removeElementWithFadeOut:Yu,isElementRemoved:Zu,getTransform:ju,applyTransform:qu,transformDirection:Ku,groupTransition:Ju,clipPointsByRect:Qu,clipRectByRect:th,createIcon:eh,linePolygonIntersect:nh,lineLineIntersect:ih,setTooltipConfig:oh,Group:Ei,Image:es,Text:cs,Circle:Nl,Ellipse:El,Sector:Jl,Ring:tu,Polygon:ru,Polyline:au,Rect:ls,Line:uu,BezierCurve:du,Arc:gu,IncrementalDisplayable:Tu,CompoundPath:yu,LinearGradient:mu,RadialGradient:_u,BoundingRect:gi,OrientedBoundingRect:Mu,Point:ai,Path:Ka}),sh={};function lh(t,e){for(var n=0;n<Ss.length;n++){var i=Ss[n],r=e[i],o=t.ensureState(i);o.style=o.style||{},o.style.text=r}var a=t.currentStates.slice();t.clearStates(!0),t.setStyle({text:e.normal}),t.useStates(a,!0)}function uh(t,e,n){var i,r=t.labelFetcher,o=t.labelDataIndex,a=t.labelDimIndex,s=e.normal;r&&(i=r.getFormattedLabel(o,\"normal\",null,a,s&&s.get(\"formatter\"),null!=n?{interpolatedValue:n}:null)),null==i&&(i=G(t.defaultText)?t.defaultText(o,t,n):t.defaultText);for(var l={normal:i},u=0;u<Ss.length;u++){var h=Ss[u],c=e[h];l[h]=tt(r?r.getFormattedLabel(o,h,null,a,c&&c.get(\"formatter\")):null,i)}return l}function hh(t,e,n,i){n=n||sh;for(var r=t instanceof cs,o=!1,a=0;a<Ms.length;a++){if((p=e[Ms[a]])&&p.getShallow(\"show\")){o=!0;break}}var s=r?t:t.getTextContent();if(o){r||(s||(s=new cs,t.setTextContent(s)),t.stateProxy&&(s.stateProxy=t.stateProxy));var l=uh(n,e),u=e.normal,h=!!u.getShallow(\"show\"),c=ph(u,i&&i.normal,n,!1,!r);c.text=l.normal,r||t.setTextConfig(dh(u,n,!1));for(a=0;a<Ss.length;a++){var p,d=Ss[a];if(p=e[d]){var f=s.ensureState(d),g=!!tt(p.getShallow(\"show\"),h);if(g!==h&&(f.ignore=!g),f.style=ph(p,i&&i[d],n,!0,!r),f.style.text=l[d],!r)t.ensureState(d).textConfig=dh(p,n,!0)}}s.silent=!!u.getShallow(\"silent\"),null!=s.style.x&&(c.x=s.style.x),null!=s.style.y&&(c.y=s.style.y),s.ignore=!h,s.useStyle(c),s.dirty(),n.enableTextSetter&&(_h(s).setLabelText=function(t){var i=uh(n,e,t);lh(s,i)})}else s&&(s.ignore=!0);t.dirty()}function ch(t,e){e=e||\"label\";for(var n={normal:t.getModel(e)},i=0;i<Ss.length;i++){var r=Ss[i];n[r]=t.getModel([r,e])}return n}function ph(t,e,n,i,r){var o={};return function(t,e,n,i,r){n=n||sh;var o,a=e.ecModel,s=a&&a.option.textStyle,l=function(t){var e;for(;t&&t!==t.ecModel;){var n=(t.option||sh).rich;if(n){e=e||{};for(var i=E(n),r=0;r<i.length;r++){e[i[r]]=1}}t=t.parentModel}return e}(e);if(l)for(var u in o={},l)if(l.hasOwnProperty(u)){var h=e.getModel([\"rich\",u]);vh(o[u]={},h,s,n,i,r,!1,!0)}o&&(t.rich=o);var c=e.get(\"overflow\");c&&(t.overflow=c);var p=e.get(\"minMargin\");null!=p&&(t.margin=p);vh(t,e,s,n,i,r,!0,!1)}(o,t,n,i,r),e&&I(o,e),o}function dh(t,e,n){e=e||{};var i,r={},o=t.getShallow(\"rotate\"),a=tt(t.getShallow(\"distance\"),n?null:5),s=t.getShallow(\"offset\");return\"outside\"===(i=t.getShallow(\"position\")||(n?null:\"inside\"))&&(i=e.defaultOutsidePosition||\"top\"),null!=i&&(r.position=i),null!=s&&(r.offset=s),null!=o&&(o*=Math.PI/180,r.rotation=o),null!=a&&(r.distance=a),r.outsideFill=\"inherit\"===t.get(\"color\")?e.inheritColor||null:\"auto\",r}var fh=[\"fontStyle\",\"fontWeight\",\"fontSize\",\"fontFamily\",\"textShadowColor\",\"textShadowBlur\",\"textShadowOffsetX\",\"textShadowOffsetY\"],gh=[\"align\",\"lineHeight\",\"width\",\"height\",\"tag\",\"verticalAlign\"],yh=[\"padding\",\"borderWidth\",\"borderRadius\",\"borderDashOffset\",\"backgroundColor\",\"borderColor\",\"shadowColor\",\"shadowBlur\",\"shadowOffsetX\",\"shadowOffsetY\"];function vh(t,e,n,i,r,o,a,s){n=!r&&n||sh;var l=i&&i.inheritColor,u=e.getShallow(\"color\"),h=e.getShallow(\"textBorderColor\"),c=tt(e.getShallow(\"opacity\"),n.opacity);\"inherit\"!==u&&\"auto\"!==u||(u=l||null),\"inherit\"!==h&&\"auto\"!==h||(h=l||null),o||(u=u||n.color,h=h||n.textBorderColor),null!=u&&(t.fill=u),null!=h&&(t.stroke=h);var p=tt(e.getShallow(\"textBorderWidth\"),n.textBorderWidth);null!=p&&(t.lineWidth=p);var d=tt(e.getShallow(\"textBorderType\"),n.textBorderType);null!=d&&(t.lineDash=d);var f=tt(e.getShallow(\"textBorderDashOffset\"),n.textBorderDashOffset);null!=f&&(t.lineDashOffset=f),r||null!=c||s||(c=i&&i.defaultOpacity),null!=c&&(t.opacity=c),r||o||null==t.fill&&i.inheritColor&&(t.fill=i.inheritColor);for(var g=0;g<fh.length;g++){var y=fh[g];null!=(m=tt(e.getShallow(y),n[y]))&&(t[y]=m)}for(g=0;g<gh.length;g++){y=gh[g];null!=(m=e.getShallow(y))&&(t[y]=m)}if(null==t.verticalAlign){var v=e.getShallow(\"baseline\");null!=v&&(t.verticalAlign=v)}if(!a||!i.disableBox){for(g=0;g<yh.length;g++){var m;y=yh[g];null!=(m=e.getShallow(y))&&(t[y]=m)}var _=e.getShallow(\"borderType\");null!=_&&(t.borderDash=_),\"auto\"!==t.backgroundColor&&\"inherit\"!==t.backgroundColor||!l||(t.backgroundColor=l),\"auto\"!==t.borderColor&&\"inherit\"!==t.borderColor||!l||(t.borderColor=l)}}function mh(t,e){var n=e&&e.getModel(\"textStyle\");return ot([t.fontStyle||n&&n.getShallow(\"fontStyle\")||\"\",t.fontWeight||n&&n.getShallow(\"fontWeight\")||\"\",(t.fontSize||n&&n.getShallow(\"fontSize\")||12)+\"px\",t.fontFamily||n&&n.getShallow(\"fontFamily\")||\"sans-serif\"].join(\" \"))}var _h=kr();function xh(t,e,n,i){if(t){var r=_h(t);r.prevValue=r.value,r.value=n;var o=e.normal;r.valueAnimation=o.get(\"valueAnimation\"),r.valueAnimation&&(r.precision=o.get(\"precision\"),r.defaultInterpolatedText=i,r.statesModels=e)}}function bh(t,e,n,i,r){var o=_h(t);if(o.valueAnimation){var a=o.defaultInterpolatedText,s=tt(o.interpolatedValue,o.prevValue),l=o.value;(null==s?Wu:Hu)(t,{},i,e,null,(function(i){var u=Fr(n,o.precision,s,l,i);o.interpolatedValue=1===i?null:u;var h=uh({labelDataIndex:e,labelFetcher:r,defaultText:a?a(u):u+\"\"},o.statesModels,u);lh(t,h)}))}}var wh,Sh,Mh=[\"textStyle\",\"color\"],Ih=new cs,Th=function(){function t(){}return t.prototype.getTextColor=function(t){var e=this.ecModel;return this.getShallow(\"color\")||(!t&&e?e.get(Mh):null)},t.prototype.getFont=function(){return mh({fontStyle:this.getShallow(\"fontStyle\"),fontWeight:this.getShallow(\"fontWeight\"),fontSize:this.getShallow(\"fontSize\"),fontFamily:this.getShallow(\"fontFamily\")},this.ecModel)},t.prototype.getTextRect=function(t){return Ih.useStyle({text:t,fontStyle:this.getShallow(\"fontStyle\"),fontWeight:this.getShallow(\"fontWeight\"),fontSize:this.getShallow(\"fontSize\"),fontFamily:this.getShallow(\"fontFamily\"),verticalAlign:this.getShallow(\"verticalAlign\")||this.getShallow(\"baseline\"),padding:this.getShallow(\"padding\"),lineHeight:this.getShallow(\"lineHeight\"),rich:this.getShallow(\"rich\")}),Ih.update(),Ih.getBoundingRect()},t}(),Ch=[[\"lineWidth\",\"width\"],[\"stroke\",\"color\"],[\"opacity\"],[\"shadowBlur\"],[\"shadowOffsetX\"],[\"shadowOffsetY\"],[\"shadowColor\"],[\"lineDash\",\"type\"],[\"lineDashOffset\",\"dashOffset\"],[\"lineCap\",\"cap\"],[\"lineJoin\",\"join\"],[\"miterLimit\"]],Dh=$r(Ch),Ah=function(){function t(){}return t.prototype.getLineStyle=function(t){return Dh(this,t)},t}(),Lh=[[\"fill\",\"color\"],[\"stroke\",\"borderColor\"],[\"lineWidth\",\"borderWidth\"],[\"opacity\"],[\"shadowBlur\"],[\"shadowOffsetX\"],[\"shadowOffsetY\"],[\"shadowColor\"],[\"lineDash\",\"borderType\"],[\"lineDashOffset\",\"borderDashOffset\"],[\"lineCap\",\"borderCap\"],[\"lineJoin\",\"borderJoin\"],[\"miterLimit\",\"borderMiterLimit\"]],kh=$r(Lh),Ph=function(){function t(){}return t.prototype.getItemStyle=function(t,e){return kh(this,t,e)},t}(),Oh=function(){function t(t,e,n){this.parentModel=e,this.ecModel=n,this.option=t}return t.prototype.init=function(t,e,n){for(var i=[],r=3;r<arguments.length;r++)i[r-3]=arguments[r]},t.prototype.mergeOption=function(t,e){S(this.option,t,!0)},t.prototype.get=function(t,e){return null==t?this.option:this._doGet(this.parsePath(t),!e&&this.parentModel)},t.prototype.getShallow=function(t,e){var n=this.option,i=null==n?n:n[t];if(null==i&&!e){var r=this.parentModel;r&&(i=r.getShallow(t))}return i},t.prototype.getModel=function(e,n){var i=null!=e,r=i?this.parsePath(e):null;return new t(i?this._doGet(r):this.option,n=n||this.parentModel&&this.parentModel.getModel(this.resolveParentPath(r)),this.ecModel)},t.prototype.isEmpty=function(){return null==this.option},t.prototype.restoreData=function(){},t.prototype.clone=function(){return new(0,this.constructor)(w(this.option))},t.prototype.parsePath=function(t){return\"string\"==typeof t?t.split(\".\"):t},t.prototype.resolveParentPath=function(t){return t},t.prototype.isAnimationEnabled=function(){if(!a.node&&this.option){if(null!=this.option.animation)return!!this.option.animation;if(this.parentModel)return this.parentModel.isAnimationEnabled()}},t.prototype._doGet=function(t,e){var n=this.option;if(!t)return n;for(var i=0;i<t.length&&(!t[i]||null!=(n=n&&\"object\"==typeof n?n[t[i]]:null));i++);return null==n&&e&&(n=e._doGet(this.resolveParentPath(t),e.parentModel)),n},t}();Ur(Oh),wh=Oh,Sh=[\"__\\0is_clz\",Zr++].join(\"_\"),wh.prototype[Sh]=!0,wh.isInstance=function(t){return!(!t||!t[Sh])},L(Oh,Ah),L(Oh,Ph),L(Oh,Qr),L(Oh,Th);var Rh=Math.round(10*Math.random());function Nh(t){return[t||\"\",Rh++].join(\"_\")}function zh(t,e){return S(S({},t,!0),e,!0)}var Eh=\"ZH\",Vh=\"EN\",Bh=Vh,Fh={},Gh={},Hh=a.domSupported&&(document.documentElement.lang||navigator.language||navigator.browserLanguage).toUpperCase().indexOf(Eh)>-1?Eh:Bh;function Wh(t,e){t=t.toUpperCase(),Gh[t]=new Oh(e),Fh[t]=e}Wh(Vh,{time:{month:[\"January\",\"February\",\"March\",\"April\",\"May\",\"June\",\"July\",\"August\",\"September\",\"October\",\"November\",\"December\"],monthAbbr:[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"],dayOfWeek:[\"Sunday\",\"Monday\",\"Tuesday\",\"Wednesday\",\"Thursday\",\"Friday\",\"Saturday\"],dayOfWeekAbbr:[\"Sun\",\"Mon\",\"Tue\",\"Wed\",\"Thu\",\"Fri\",\"Sat\"]},legend:{selector:{all:\"All\",inverse:\"Inv\"}},toolbox:{brush:{title:{rect:\"Box Select\",polygon:\"Lasso Select\",lineX:\"Horizontally Select\",lineY:\"Vertically Select\",keep:\"Keep Selections\",clear:\"Clear Selections\"}},dataView:{title:\"Data View\",lang:[\"Data View\",\"Close\",\"Refresh\"]},dataZoom:{title:{zoom:\"Zoom\",back:\"Zoom Reset\"}},magicType:{title:{line:\"Switch to Line Chart\",bar:\"Switch to Bar Chart\",stack:\"Stack\",tiled:\"Tile\"}},restore:{title:\"Restore\"},saveAsImage:{title:\"Save as Image\",lang:[\"Right Click to Save Image\"]}},series:{typeNames:{pie:\"Pie chart\",bar:\"Bar chart\",line:\"Line chart\",scatter:\"Scatter plot\",effectScatter:\"Ripple scatter plot\",radar:\"Radar chart\",tree:\"Tree\",treemap:\"Treemap\",boxplot:\"Boxplot\",candlestick:\"Candlestick\",k:\"K line chart\",heatmap:\"Heat map\",map:\"Map\",parallel:\"Parallel coordinate map\",lines:\"Line graph\",graph:\"Relationship graph\",sankey:\"Sankey diagram\",funnel:\"Funnel chart\",gauge:\"Gauge\",pictorialBar:\"Pictorial bar\",themeRiver:\"Theme River Map\",sunburst:\"Sunburst\"}},aria:{general:{withTitle:'This is a chart about \"{title}\"',withoutTitle:\"This is a chart\"},series:{single:{prefix:\"\",withName:\" with type {seriesType} named {seriesName}.\",withoutName:\" with type {seriesType}.\"},multiple:{prefix:\". It consists of {seriesCount} series count.\",withName:\" The {seriesId} series is a {seriesType} representing {seriesName}.\",withoutName:\" The {seriesId} series is a {seriesType}.\",separator:{middle:\"\",end:\"\"}}},data:{allData:\"The data is as follows: \",partialData:\"The first {displayCnt} items are: \",withName:\"the data for {name} is {value}\",withoutName:\"{value}\",separator:{middle:\", \",end:\". \"}}}}),Wh(Eh,{time:{month:[\"一月\",\"二月\",\"三月\",\"四月\",\"五月\",\"六月\",\"七月\",\"八月\",\"九月\",\"十月\",\"十一月\",\"十二月\"],monthAbbr:[\"1月\",\"2月\",\"3月\",\"4月\",\"5月\",\"6月\",\"7月\",\"8月\",\"9月\",\"10月\",\"11月\",\"12月\"],dayOfWeek:[\"星期日\",\"星期一\",\"星期二\",\"星期三\",\"星期四\",\"星期五\",\"星期六\"],dayOfWeekAbbr:[\"日\",\"一\",\"二\",\"三\",\"四\",\"五\",\"六\"]},legend:{selector:{all:\"全选\",inverse:\"反选\"}},toolbox:{brush:{title:{rect:\"矩形选择\",polygon:\"圈选\",lineX:\"横向选择\",lineY:\"纵向选择\",keep:\"保持选择\",clear:\"清除选择\"}},dataView:{title:\"数据视图\",lang:[\"数据视图\",\"关闭\",\"刷新\"]},dataZoom:{title:{zoom:\"区域缩放\",back:\"区域缩放还原\"}},magicType:{title:{line:\"切换为折线图\",bar:\"切换为柱状图\",stack:\"切换为堆叠\",tiled:\"切换为平铺\"}},restore:{title:\"还原\"},saveAsImage:{title:\"保存为图片\",lang:[\"右键另存为图片\"]}},series:{typeNames:{pie:\"饼图\",bar:\"柱状图\",line:\"折线图\",scatter:\"散点图\",effectScatter:\"涟漪散点图\",radar:\"雷达图\",tree:\"树图\",treemap:\"矩形树图\",boxplot:\"箱型图\",candlestick:\"K线图\",k:\"K线图\",heatmap:\"热力图\",map:\"地图\",parallel:\"平行坐标图\",lines:\"线图\",graph:\"关系图\",sankey:\"桑基图\",funnel:\"漏斗图\",gauge:\"仪表盘图\",pictorialBar:\"象形柱图\",themeRiver:\"主题河流图\",sunburst:\"旭日图\"}},aria:{general:{withTitle:\"这是一个关于“{title}”的图表。\",withoutTitle:\"这是一个图表，\"},series:{single:{prefix:\"\",withName:\"图表类型是{seriesType}，表示{seriesName}。\",withoutName:\"图表类型是{seriesType}。\"},multiple:{prefix:\"它由{seriesCount}个图表系列组成。\",withName:\"第{seriesId}个系列是一个表示{seriesName}的{seriesType}，\",withoutName:\"第{seriesId}个系列是一个{seriesType}，\",separator:{middle:\"；\",end:\"。\"}}},data:{allData:\"其数据是——\",partialData:\"其中，前{displayCnt}项是——\",withName:\"{name}的数据是{value}\",withoutName:\"{value}\",separator:{middle:\"，\",end:\"\"}}}});var Uh=1e3,Xh=6e4,Yh=36e5,Zh=864e5,jh=31536e6,qh={year:\"{yyyy}\",month:\"{MMM}\",day:\"{d}\",hour:\"{HH}:{mm}\",minute:\"{HH}:{mm}\",second:\"{HH}:{mm}:{ss}\",millisecond:\"{hh}:{mm}:{ss} {SSS}\",none:\"{yyyy}-{MM}-{dd} {hh}:{mm}:{ss} {SSS}\"},Kh=\"{yyyy}-{MM}-{dd}\",$h={year:\"{yyyy}\",month:\"{yyyy}-{MM}\",day:Kh,hour:\"{yyyy}-{MM}-{dd} \"+qh.hour,minute:\"{yyyy}-{MM}-{dd} \"+qh.minute,second:\"{yyyy}-{MM}-{dd} \"+qh.second,millisecond:qh.none},Jh=[\"year\",\"month\",\"day\",\"hour\",\"minute\",\"second\",\"millisecond\"],Qh=[\"year\",\"half-year\",\"quarter\",\"month\",\"week\",\"half-week\",\"day\",\"half-day\",\"quarter-day\",\"hour\",\"minute\",\"second\",\"millisecond\"];function tc(t,e){return\"0000\".substr(0,e-(t+=\"\").length)+t}function ec(t){switch(t){case\"half-year\":case\"quarter\":return\"month\";case\"week\":case\"half-week\":return\"day\";case\"half-day\":case\"quarter-day\":return\"hour\";default:return t}}function nc(t){return t===ec(t)}function ic(t,e,n,i){var r=or(t),o=r[ac(n)](),a=r[sc(n)]()+1,s=Math.floor((a-1)/4)+1,l=r[lc(n)](),u=r[\"get\"+(n?\"UTC\":\"\")+\"Day\"](),h=r[uc(n)](),c=(h-1)%12+1,p=r[hc(n)](),d=r[cc(n)](),f=r[pc(n)](),g=(i instanceof Oh?i:function(t){return Gh[t]}(i||Hh)||Gh.EN).getModel(\"time\"),y=g.get(\"month\"),v=g.get(\"monthAbbr\"),m=g.get(\"dayOfWeek\"),_=g.get(\"dayOfWeekAbbr\");return(e||\"\").replace(/{yyyy}/g,o+\"\").replace(/{yy}/g,o%100+\"\").replace(/{Q}/g,s+\"\").replace(/{MMMM}/g,y[a-1]).replace(/{MMM}/g,v[a-1]).replace(/{MM}/g,tc(a,2)).replace(/{M}/g,a+\"\").replace(/{dd}/g,tc(l,2)).replace(/{d}/g,l+\"\").replace(/{eeee}/g,m[u]).replace(/{ee}/g,_[u]).replace(/{e}/g,u+\"\").replace(/{HH}/g,tc(h,2)).replace(/{H}/g,h+\"\").replace(/{hh}/g,tc(c+\"\",2)).replace(/{h}/g,c+\"\").replace(/{mm}/g,tc(p,2)).replace(/{m}/g,p+\"\").replace(/{ss}/g,tc(d,2)).replace(/{s}/g,d+\"\").replace(/{SSS}/g,tc(f,3)).replace(/{S}/g,f+\"\")}function rc(t,e){var n=or(t),i=n[sc(e)]()+1,r=n[lc(e)](),o=n[uc(e)](),a=n[hc(e)](),s=n[cc(e)](),l=0===n[pc(e)](),u=l&&0===s,h=u&&0===a,c=h&&0===o,p=c&&1===r;return p&&1===i?\"year\":p?\"month\":c?\"day\":h?\"hour\":u?\"minute\":l?\"second\":\"millisecond\"}function oc(t,e,n){var i=\"number\"==typeof t?or(t):t;switch(e=e||rc(t,n)){case\"year\":return i[ac(n)]();case\"half-year\":return i[sc(n)]()>=6?1:0;case\"quarter\":return Math.floor((i[sc(n)]()+1)/4);case\"month\":return i[sc(n)]();case\"day\":return i[lc(n)]();case\"half-day\":return i[uc(n)]()/24;case\"hour\":return i[uc(n)]();case\"minute\":return i[hc(n)]();case\"second\":return i[cc(n)]();case\"millisecond\":return i[pc(n)]()}}function ac(t){return t?\"getUTCFullYear\":\"getFullYear\"}function sc(t){return t?\"getUTCMonth\":\"getMonth\"}function lc(t){return t?\"getUTCDate\":\"getDate\"}function uc(t){return t?\"getUTCHours\":\"getHours\"}function hc(t){return t?\"getUTCMinutes\":\"getMinutes\"}function cc(t){return t?\"getUTCSeconds\":\"getSeconds\"}function pc(t){return t?\"getUTCSeconds\":\"getSeconds\"}function dc(t){return t?\"setUTCFullYear\":\"setFullYear\"}function fc(t){return t?\"setUTCMonth\":\"setMonth\"}function gc(t){return t?\"setUTCDate\":\"setDate\"}function yc(t){return t?\"setUTCHours\":\"setHours\"}function vc(t){return t?\"setUTCMinutes\":\"setMinutes\"}function mc(t){return t?\"setUTCSeconds\":\"setSeconds\"}function _c(t){return t?\"setUTCSeconds\":\"setSeconds\"}function xc(t){if(!pr(t))return H(t)?t:\"-\";var e=(t+\"\").split(\".\");return e[0].replace(/(\\d{1,3})(?=(?:\\d{3})+(?!\\d))/g,\"$1,\")+(e.length>1?\".\"+e[1]:\"\")}function bc(t,e){return t=(t||\"\").toLowerCase().replace(/-(.)/g,(function(t,e){return e.toUpperCase()})),e&&t&&(t=t.charAt(0).toUpperCase()+t.slice(1)),t}var wc=it,Sc=/([&<>\"'])/g,Mc={\"&\":\"&amp;\",\"<\":\"&lt;\",\">\":\"&gt;\",'\"':\"&quot;\",\"'\":\"&#39;\"};function Ic(t){return null==t?\"\":(t+\"\").replace(Sc,(function(t,e){return Mc[e]}))}function Tc(t,e,n){function i(t){return t&&ot(t)?t:\"-\"}function r(t){return!(null==t||isNaN(t)||!isFinite(t))}var o=\"time\"===e,a=t instanceof Date;if(o||a){var s=o?or(t):t;if(!isNaN(+s))return ic(s,\"{yyyy}-{MM}-{dd} {hh}:{mm}:{ss}\",n);if(a)return\"-\"}if(\"ordinal\"===e)return W(t)?i(t):U(t)&&r(t)?t+\"\":\"-\";var l=cr(t);return r(l)?xc(l):W(t)?i(t):\"-\"}var Cc=[\"a\",\"b\",\"c\",\"d\",\"e\",\"f\",\"g\"],Dc=function(t,e){return\"{\"+t+(null==e?\"\":e)+\"}\"};function Ac(t,e,n){F(e)||(e=[e]);var i=e.length;if(!i)return\"\";for(var r=e[0].$vars||[],o=0;o<r.length;o++){var a=Cc[o];t=t.replace(Dc(a),Dc(a,0))}for(var s=0;s<i;s++)for(var l=0;l<r.length;l++){var u=e[s][r[l]];t=t.replace(Dc(Cc[l],s),n?Ic(u):u)}return t}function Lc(t,e){var n=H(t)?{color:t,extraCssText:e}:t||{},i=n.color,r=n.type;e=n.extraCssText;var o=n.renderMode||\"html\";return i?\"html\"===o?\"subItem\"===r?'<span style=\"display:inline-block;vertical-align:middle;margin-right:8px;margin-left:3px;border-radius:4px;width:4px;height:4px;background-color:'+Ic(i)+\";\"+(e||\"\")+'\"></span>':'<span style=\"display:inline-block;margin-right:4px;border-radius:10px;width:10px;height:10px;background-color:'+Ic(i)+\";\"+(e||\"\")+'\"></span>':{renderMode:o,content:\"{\"+(n.markerId||\"markerX\")+\"|}  \",style:\"subItem\"===r?{width:4,height:4,borderRadius:2,backgroundColor:i}:{width:10,height:10,borderRadius:5,backgroundColor:i}}:\"\"}function kc(t,e){return e=e||\"transparent\",H(t)?t:X(t)&&t.colorStops&&(t.colorStops[0]||{}).color||e}function Pc(t,e){if(\"_blank\"===e||\"blank\"===e){var n=window.open();n.opener=null,n.location.href=t}else window.open(t,e)}var Oc=P,Rc=[\"left\",\"right\",\"top\",\"bottom\",\"width\",\"height\"],Nc=[[\"width\",\"left\",\"right\"],[\"height\",\"top\",\"bottom\"]];function zc(t,e,n,i,r){var o=0,a=0;null==i&&(i=1/0),null==r&&(r=1/0);var s=0;e.eachChild((function(l,u){var h,c,p=l.getBoundingRect(),d=e.childAt(u+1),f=d&&d.getBoundingRect();if(\"horizontal\"===t){var g=p.width+(f?-f.x+p.x:0);(h=o+g)>i||l.newline?(o=0,h=g,a+=s+n,s=p.height):s=Math.max(s,p.height)}else{var y=p.height+(f?-f.y+p.y:0);(c=a+y)>r||l.newline?(o+=s+n,a=0,c=y,s=p.width):s=Math.max(s,p.width)}l.newline||(l.x=o,l.y=a,l.markRedraw(),\"horizontal\"===t?o=h+n:a=c+n)}))}var Ec=zc;B(zc,\"vertical\"),B(zc,\"horizontal\");function Vc(t,e,n){n=wc(n||0);var i=e.width,r=e.height,o=Zi(t.left,i),a=Zi(t.top,r),s=Zi(t.right,i),l=Zi(t.bottom,r),u=Zi(t.width,i),h=Zi(t.height,r),c=n[2]+n[0],p=n[1]+n[3],d=t.aspect;switch(isNaN(u)&&(u=i-s-p-o),isNaN(h)&&(h=r-l-c-a),null!=d&&(isNaN(u)&&isNaN(h)&&(d>i/r?u=.8*i:h=.8*r),isNaN(u)&&(u=d*h),isNaN(h)&&(h=u/d)),isNaN(o)&&(o=i-s-u-p),isNaN(a)&&(a=r-l-h-c),t.left||t.right){case\"center\":o=i/2-u/2-n[3];break;case\"right\":o=i-u-p}switch(t.top||t.bottom){case\"middle\":case\"center\":a=r/2-h/2-n[0];break;case\"bottom\":a=r-h-c}o=o||0,a=a||0,isNaN(u)&&(u=i-p-o-(s||0)),isNaN(h)&&(h=r-c-a-(l||0));var f=new gi(o+n[3],a+n[0],u,h);return f.margin=n,f}function Bc(t,e,n,i,r){var o=!r||!r.hv||r.hv[0],a=!r||!r.hv||r.hv[1],s=r&&r.boundingMode||\"all\";if(o||a){var l;if(\"raw\"===s)l=\"group\"===t.type?new gi(0,0,+e.width||0,+e.height||0):t.getBoundingRect();else if(l=t.getBoundingRect(),t.needLocalTransform()){var u=t.getLocalTransform();(l=l.clone()).applyTransform(u)}var h=Vc(T({width:l.width,height:l.height},e),n,i),c=o?h.x-l.x:0,p=a?h.y-l.y:0;\"raw\"===s?(t.x=c,t.y=p):(t.x+=c,t.y+=p),t.markRedraw()}}function Fc(t){var e=t.layoutMode||t.constructor.layoutMode;return X(e)?e:e?{type:e}:null}function Gc(t,e,n){var i=n&&n.ignoreSize;!F(i)&&(i=[i,i]);var r=a(Nc[0],0),o=a(Nc[1],1);function a(n,r){var o={},a=0,u={},h=0;if(Oc(n,(function(e){u[e]=t[e]})),Oc(n,(function(t){s(e,t)&&(o[t]=u[t]=e[t]),l(o,t)&&a++,l(u,t)&&h++})),i[r])return l(e,n[1])?u[n[2]]=null:l(e,n[2])&&(u[n[1]]=null),u;if(2!==h&&a){if(a>=2)return o;for(var c=0;c<n.length;c++){var p=n[c];if(!s(o,p)&&s(t,p)){o[p]=t[p];break}}return o}return u}function s(t,e){return t.hasOwnProperty(e)}function l(t,e){return null!=t[e]&&\"auto\"!==t[e]}function u(t,e,n){Oc(t,(function(t){e[t]=n[t]}))}u(Nc[0],t,r),u(Nc[1],t,o)}function Hc(t){return Wc({},t)}function Wc(t,e){return e&&t&&Oc(Rc,(function(n){e.hasOwnProperty(n)&&(t[n]=e[n])})),t}var Uc=kr(),Xc=function(t){function e(e,n,i){var r=t.call(this,e,n,i)||this;return r.uid=Nh(\"ec_cpt_model\"),r}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n)},e.prototype.mergeDefaultAndTheme=function(t,e){var n=Fc(this),i=n?Hc(t):{};S(t,e.getTheme().get(this.mainType)),S(t,this.getDefaultOption()),n&&Gc(t,i,n)},e.prototype.mergeOption=function(t,e){S(this.option,t,!0);var n=Fc(this);n&&Gc(this.option,t,n)},e.prototype.optionUpdated=function(t,e){},e.prototype.getDefaultOption=function(){var t=this.constructor;if(!function(t){return!(!t||!t[Hr])}(t))return t.defaultOption;var e=Uc(this);if(!e.defaultOption){for(var n=[],i=t;i;){var r=i.prototype.defaultOption;r&&n.push(r),i=i.superClass}for(var o={},a=n.length-1;a>=0;a--)o=S(o,n[a],!0);e.defaultOption=o}return e.defaultOption},e.prototype.getReferringComponents=function(t,e){var n=t+\"Index\",i=t+\"Id\";return Er(this.ecModel,t,{index:this.get(n,!0),id:this.get(i,!0)},e)},e.prototype.getBoxLayoutParams=function(){var t=this;return{left:t.get(\"left\"),top:t.get(\"top\"),right:t.get(\"right\"),bottom:t.get(\"bottom\"),width:t.get(\"width\"),height:t.get(\"height\")}},e.protoInitialize=function(){var t=e.prototype;t.type=\"component\",t.id=\"\",t.name=\"\",t.mainType=\"\",t.subType=\"\",t.componentIndex=0}(),e}(Oh);Yr(Xc,Oh),Kr(Xc),function(t){var e={};t.registerSubTypeDefaulter=function(t,n){var i=Wr(t);e[i.main]=n},t.determineSubType=function(n,i){var r=i.type;if(!r){var o=Wr(n).main;t.hasSubTypes(n)&&e[o]&&(r=e[o](i))}return r}}(Xc),function(t,e){function n(t,e){return t[e]||(t[e]={predecessor:[],successor:[]}),t[e]}t.topologicalTravel=function(t,i,r,o){if(t.length){var a=function(t){var i={},r=[];return P(t,(function(o){var a=n(i,o),s=function(t,e){var n=[];return P(t,(function(t){D(e,t)>=0&&n.push(t)})),n}(a.originalDeps=e(o),t);a.entryCount=s.length,0===a.entryCount&&r.push(o),P(s,(function(t){D(a.predecessor,t)<0&&a.predecessor.push(t);var e=n(i,t);D(e.successor,t)<0&&e.successor.push(o)}))})),{graph:i,noEntryList:r}}(i),s=a.graph,l=a.noEntryList,u={};for(P(t,(function(t){u[t]=!0}));l.length;){var h=l.pop(),c=s[h],p=!!u[h];p&&(r.call(o,h,c.originalDeps.slice()),delete u[h]),P(c.successor,p?f:d)}P(u,(function(){var t=\"\";throw new Error(t)}))}function d(t){s[t].entryCount--,0===s[t].entryCount&&l.push(t)}function f(t){u[t]=!0,d(t)}}}(Xc,(function(t){var e=[];P(Xc.getClassesByMainType(t),(function(t){e=e.concat(t.dependencies||t.prototype.dependencies||[])})),e=O(e,(function(t){return Wr(t).main})),\"dataset\"!==t&&D(e,\"dataset\")<=0&&e.unshift(\"dataset\");return e}));var Yc=\"\";\"undefined\"!=typeof navigator&&(Yc=navigator.platform||\"\");var Zc=\"rgba(0, 0, 0, 0.2)\",jc={darkMode:\"auto\",color:[\"#5470c6\",\"#91cc75\",\"#fac858\",\"#ee6666\",\"#73c0de\",\"#3ba272\",\"#fc8452\",\"#9a60b4\",\"#ea7ccc\"],gradientColor:[\"#f6efa6\",\"#d88273\",\"#bf444c\"],aria:{decal:{decals:[{color:Zc,dashArrayX:[1,0],dashArrayY:[2,5],symbolSize:1,rotation:Math.PI/6},{color:Zc,symbol:\"circle\",dashArrayX:[[8,8],[0,8,8,0]],dashArrayY:[6,0],symbolSize:.8},{color:Zc,dashArrayX:[1,0],dashArrayY:[4,3],rotation:-Math.PI/4},{color:Zc,dashArrayX:[[6,6],[0,6,6,0]],dashArrayY:[6,0]},{color:Zc,dashArrayX:[[1,0],[1,6]],dashArrayY:[1,0,6,0],rotation:Math.PI/4},{color:Zc,symbol:\"triangle\",dashArrayX:[[9,9],[0,9,9,0]],dashArrayY:[7,2],symbolSize:.75}]}},textStyle:{fontFamily:Yc.match(/^Win/)?\"Microsoft YaHei\":\"sans-serif\",fontSize:12,fontStyle:\"normal\",fontWeight:\"normal\"},blendMode:null,stateAnimation:{duration:300,easing:\"cubicOut\"},animation:\"auto\",animationDuration:1e3,animationDurationUpdate:500,animationEasing:\"cubicInOut\",animationEasingUpdate:\"cubicInOut\",animationThreshold:2e3,progressiveThreshold:3e3,progressive:400,hoverLayerThreshold:3e3,useUTC:!1},qc=ht([\"tooltip\",\"label\",\"itemName\",\"itemId\",\"seriesName\"]),Kc=\"original\",$c=\"arrayRows\",Jc=\"objectRows\",Qc=\"keyedColumns\",tp=\"typedArray\",ep=\"unknown\",np=\"column\",ip=\"row\",rp=1,op=2,ap=3,sp=kr();function lp(t,e,n){var i={},r=hp(e);if(!r||!t)return i;var o,a,s=[],l=[],u=e.ecModel,h=sp(u).datasetMap,c=r.uid+\"_\"+n.seriesLayoutBy;P(t=t.slice(),(function(e,n){var r=X(e)?e:t[n]={name:e};\"ordinal\"===r.type&&null==o&&(o=n,a=f(r)),i[r.name]=[]}));var p=h.get(c)||h.set(c,{categoryWayDim:a,valueWayDim:0});function d(t,e,n){for(var i=0;i<n;i++)t.push(e+i)}function f(t){var e=t.dimsDef;return e?e.length:1}return P(t,(function(t,e){var n=t.name,r=f(t);if(null==o){var a=p.valueWayDim;d(i[n],a,r),d(l,a,r),p.valueWayDim+=r}else if(o===e)d(i[n],0,r),d(s,0,r);else{a=p.categoryWayDim;d(i[n],a,r),d(l,a,r),p.categoryWayDim+=r}})),s.length&&(i.itemName=s),l.length&&(i.seriesName=l),i}function up(t,e,n){var i={};if(!hp(t))return i;var r,o=e.sourceFormat,a=e.dimensionsDefine;o!==Jc&&o!==Qc||P(a,(function(t,e){\"name\"===(X(t)?t.name:t)&&(r=e)}));var s=function(){for(var t={},i={},s=[],l=0,u=Math.min(5,n);l<u;l++){var h=pp(e.data,o,e.seriesLayoutBy,a,e.startIndex,l);s.push(h);var c=h===ap;if(c&&null==t.v&&l!==r&&(t.v=l),(null==t.n||t.n===t.v||!c&&s[t.n]===ap)&&(t.n=l),p(t)&&s[t.n]!==ap)return t;c||(h===op&&null==i.v&&l!==r&&(i.v=l),null!=i.n&&i.n!==i.v||(i.n=l))}function p(t){return null!=t.v&&null!=t.n}return p(t)?t:p(i)?i:null}();if(s){i.value=[s.v];var l=null!=r?r:s.n;i.itemName=[l],i.seriesName=[l]}return i}function hp(t){if(!t.get(\"data\",!0))return Er(t.ecModel,\"dataset\",{index:t.get(\"datasetIndex\",!0),id:t.get(\"datasetId\",!0)},Nr).models[0]}function cp(t,e){return pp(t.data,t.sourceFormat,t.seriesLayoutBy,t.dimensionsDefine,t.startIndex,e)}function pp(t,e,n,i,r,o){var a,s,l;if(Z(t))return ap;if(i){var u=i[o];X(u)?(s=u.name,l=u.type):H(u)&&(s=u)}if(null!=l)return\"ordinal\"===l?rp:ap;if(e===$c){var h=t;if(n===ip){for(var c=h[o],p=0;p<(c||[]).length&&p<5;p++)if(null!=(a=m(c[r+p])))return a}else for(p=0;p<h.length&&p<5;p++){var d=h[r+p];if(d&&null!=(a=m(d[o])))return a}}else if(e===Jc){var f=t;if(!s)return ap;for(p=0;p<f.length&&p<5;p++){if((y=f[p])&&null!=(a=m(y[s])))return a}}else if(e===Qc){if(!s)return ap;if(!(c=t[s])||Z(c))return ap;for(p=0;p<c.length&&p<5;p++)if(null!=(a=m(c[p])))return a}else if(e===Kc){var g=t;for(p=0;p<g.length&&p<5;p++){var y,v=Sr(y=g[p]);if(!F(v))return ap;if(null!=(a=m(v[o])))return a}}function m(t){var e=H(t);return null!=t&&isFinite(t)&&\"\"!==t?e?op:ap:e&&\"-\"!==t?rp:void 0}return ap}var dp=ht();var fp,gp,yp,vp=kr(),mp=kr(),_p=function(){function t(){}return t.prototype.getColorFromPalette=function(t,e,n){var i=xr(this.get(\"color\",!0)),r=this.get(\"colorLayer\",!0);return bp(this,vp,i,r,t,e,n)},t.prototype.clearColorPalette=function(){!function(t,e){e(t).paletteIdx=0,e(t).paletteNameMap={}}(this,vp)},t}();function xp(t,e,n,i){var r=xr(t.get([\"aria\",\"decal\",\"decals\"]));return bp(t,mp,r,null,e,n,i)}function bp(t,e,n,i,r,o,a){var s=e(o=o||t),l=s.paletteIdx||0,u=s.paletteNameMap=s.paletteNameMap||{};if(u.hasOwnProperty(r))return u[r];var h=null!=a&&i?function(t,e){for(var n=t.length,i=0;i<n;i++)if(t[i].length>e)return t[i];return t[n-1]}(i,a):n;if((h=h||n)&&h.length){var c=h[l];return r&&(u[r]=c),s.paletteIdx=(l+1)%h.length,c}}var wp=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.init=function(t,e,n,i,r,o){i=i||{},this.option=null,this._theme=new Oh(i),this._locale=new Oh(r),this._optionManager=o},e.prototype.setOption=function(t,e,n){var i=Ip(e);this._optionManager.setOption(t,n,i),this._resetOption(null,i)},e.prototype.resetOption=function(t,e){return this._resetOption(t,Ip(e))},e.prototype._resetOption=function(t,e){var n=!1,i=this._optionManager;if(!t||\"recreate\"===t){var r=i.mountOption(\"recreate\"===t);0,this.option&&\"recreate\"!==t?(this.restoreData(),this._mergeOption(r,e)):yp(this,r),n=!0}if(\"timeline\"!==t&&\"media\"!==t||this.restoreData(),!t||\"recreate\"===t||\"timeline\"===t){var o=i.getTimelineOption(this);o&&(n=!0,this._mergeOption(o,e))}if(!t||\"recreate\"===t||\"media\"===t){var a=i.getMediaOption(this);a.length&&P(a,(function(t){n=!0,this._mergeOption(t,e)}),this)}return n},e.prototype.mergeOption=function(t){this._mergeOption(t,null)},e.prototype._mergeOption=function(t,e){var n=this.option,i=this._componentsMap,r=this._componentsCount,o=[],a=ht(),s=e&&e.replaceMergeMainTypeMap;sp(this).datasetMap=ht(),P(t,(function(t,e){null!=t&&(Xc.hasClass(e)?e&&(o.push(e),a.set(e,!0)):n[e]=null==n[e]?w(t):S(n[e],t,!0))})),s&&s.each((function(t,e){Xc.hasClass(e)&&!a.get(e)&&(o.push(e),a.set(e,!0))})),Xc.topologicalTravel(o,Xc.getAllClassMainTypes(),(function(e){var o=function(t,e,n){var i=dp.get(e);if(!i)return n;var r=i(t);return r?n.concat(r):n}(this,e,xr(t[e])),a=i.get(e),l=a?s&&s.get(e)?\"replaceMerge\":\"normalMerge\":\"replaceAll\",u=Mr(a,o,l);(function(t,e,n){P(t,(function(t){var i=t.newOption;X(i)&&(t.keyInfo.mainType=e,t.keyInfo.subType=function(t,e,n,i){return e.type?e.type:n?n.subType:i.determineSubType(t,e)}(e,i,t.existing,n))}))})(u,e,Xc),n[e]=null,i.set(e,null),r.set(e,0);var h=[],c=[],p=0;P(u,(function(t,n){var i=t.existing,r=t.newOption;if(r){var o=\"series\"===e,a=Xc.getClass(e,t.keyInfo.subType,!o);if(!a)return;if(i&&i.constructor===a)i.name=t.keyInfo.name,i.mergeOption(r,this),i.optionUpdated(r,!1);else{var s=I({componentIndex:n},t.keyInfo);I(i=new a(r,this,this,s),s),t.brandNew&&(i.__requireNewView=!0),i.init(r,this,this),i.optionUpdated(null,!0)}}else i&&(i.mergeOption({},this),i.optionUpdated({},!1));i?(h.push(i.option),c.push(i),p++):(h.push(void 0),c.push(void 0))}),this),n[e]=h,i.set(e,c),r.set(e,p),\"series\"===e&&fp(this)}),this),this._seriesIndices||fp(this)},e.prototype.getOption=function(){var t=w(this.option);return P(t,(function(e,n){if(Xc.hasClass(n)){for(var i=xr(e),r=i.length,o=!1,a=r-1;a>=0;a--)i[a]&&!Ar(i[a])?o=!0:(i[a]=null,!o&&r--);i.length=r,t[n]=i}})),delete t[\"\\0_ec_inner\"],t},e.prototype.getTheme=function(){return this._theme},e.prototype.getLocaleModel=function(){return this._locale},e.prototype.getLocale=function(t){return this.getLocaleModel().get(t)},e.prototype.setUpdatePayload=function(t){this._payload=t},e.prototype.getUpdatePayload=function(){return this._payload},e.prototype.getComponent=function(t,e){var n=this._componentsMap.get(t);if(n){var i=n[e||0];if(i)return i;if(null==e)for(var r=0;r<n.length;r++)if(n[r])return n[r]}},e.prototype.queryComponents=function(t){var e=t.mainType;if(!e)return[];var n,i=t.index,r=t.id,o=t.name,a=this._componentsMap.get(e);return a&&a.length?(null!=i?(n=[],P(xr(i),(function(t){a[t]&&n.push(a[t])}))):n=null!=r?Sp(\"id\",r,a):null!=o?Sp(\"name\",o,a):N(a,(function(t){return!!t})),Mp(n,t)):[]},e.prototype.findComponents=function(t){var e,n,i,r,o,a=t.query,s=t.mainType,l=(n=s+\"Index\",i=s+\"Id\",r=s+\"Name\",!(e=a)||null==e[n]&&null==e[i]&&null==e[r]?null:{mainType:s,index:e[n],id:e[i],name:e[r]}),u=l?this.queryComponents(l):N(this._componentsMap.get(s),(function(t){return!!t}));return o=Mp(u,t),t.filter?N(o,t.filter):o},e.prototype.eachComponent=function(t,e,n){var i=this._componentsMap;if(G(t)){var r=e,o=t;i.each((function(t,e){for(var n=0;t&&n<t.length;n++){var i=t[n];i&&o.call(r,e,i,i.componentIndex)}}))}else for(var a=H(t)?i.get(t):X(t)?this.findComponents(t):null,s=0;a&&s<a.length;s++){var l=a[s];l&&e.call(n,l,l.componentIndex)}},e.prototype.getSeriesByName=function(t){var e=Cr(t,null);return N(this._componentsMap.get(\"series\"),(function(t){return!!t&&null!=e&&t.name===e}))},e.prototype.getSeriesByIndex=function(t){return this._componentsMap.get(\"series\")[t]},e.prototype.getSeriesByType=function(t){return N(this._componentsMap.get(\"series\"),(function(e){return!!e&&e.subType===t}))},e.prototype.getSeries=function(){return N(this._componentsMap.get(\"series\").slice(),(function(t){return!!t}))},e.prototype.getSeriesCount=function(){return this._componentsCount.get(\"series\")},e.prototype.eachSeries=function(t,e){gp(this),P(this._seriesIndices,(function(n){var i=this._componentsMap.get(\"series\")[n];t.call(e,i,n)}),this)},e.prototype.eachRawSeries=function(t,e){P(this._componentsMap.get(\"series\"),(function(n){n&&t.call(e,n,n.componentIndex)}))},e.prototype.eachSeriesByType=function(t,e,n){gp(this),P(this._seriesIndices,(function(i){var r=this._componentsMap.get(\"series\")[i];r.subType===t&&e.call(n,r,i)}),this)},e.prototype.eachRawSeriesByType=function(t,e,n){return P(this.getSeriesByType(t),e,n)},e.prototype.isSeriesFiltered=function(t){return gp(this),null==this._seriesIndicesMap.get(t.componentIndex)},e.prototype.getCurrentSeriesIndices=function(){return(this._seriesIndices||[]).slice()},e.prototype.filterSeries=function(t,e){gp(this);var n=[];P(this._seriesIndices,(function(i){var r=this._componentsMap.get(\"series\")[i];t.call(e,r,i)&&n.push(i)}),this),this._seriesIndices=n,this._seriesIndicesMap=ht(n)},e.prototype.restoreData=function(t){fp(this);var e=this._componentsMap,n=[];e.each((function(t,e){Xc.hasClass(e)&&n.push(e)})),Xc.topologicalTravel(n,Xc.getAllClassMainTypes(),(function(n){P(e.get(n),(function(e){!e||\"series\"===n&&function(t,e){if(e){var n=e.seriesIndex,i=e.seriesId,r=e.seriesName;return null!=n&&t.componentIndex!==n||null!=i&&t.id!==i||null!=r&&t.name!==r}}(e,t)||e.restoreData()}))}))},e.internalField=(fp=function(t){var e=t._seriesIndices=[];P(t._componentsMap.get(\"series\"),(function(t){t&&e.push(t.componentIndex)})),t._seriesIndicesMap=ht(e)},gp=function(t){},void(yp=function(t,e){t.option={},t.option[\"\\0_ec_inner\"]=1,t._componentsMap=ht({series:[]}),t._componentsCount=ht();var n=e.aria;X(n)&&null==n.enabled&&(n.enabled=!0),function(t,e){var n=t.color&&!t.colorLayer;P(e,(function(e,i){\"colorLayer\"===i&&n||Xc.hasClass(i)||(\"object\"==typeof e?t[i]=t[i]?S(t[i],e,!1):w(e):null==t[i]&&(t[i]=e))}))}(e,t._theme.option),S(e,jc,!1),t._mergeOption(e,null)})),e}(Oh);function Sp(t,e,n){if(F(e)){var i=ht();return P(e,(function(t){null!=t&&(null!=Cr(t,null)&&i.set(t,!0))})),N(n,(function(e){return e&&i.get(e[t])}))}var r=Cr(e,null);return N(n,(function(e){return e&&null!=r&&e[t]===r}))}function Mp(t,e){return e.hasOwnProperty(\"subType\")?N(t,(function(t){return t&&t.subType===e.subType})):t}function Ip(t){var e=ht();return t&&P(xr(t.replaceMerge),(function(t){e.set(t,!0)})),{replaceMergeMainTypeMap:e}}L(wp,_p);var Tp=[\"getDom\",\"getZr\",\"getWidth\",\"getHeight\",\"getDevicePixelRatio\",\"dispatchAction\",\"isDisposed\",\"on\",\"off\",\"getDataURL\",\"getConnectedDataURL\",\"getOption\",\"getId\",\"updateLabelLayout\"],Cp=function(t){P(Tp,(function(e){this[e]=V(t[e],t)}),this)},Dp={},Ap=function(){function t(){this._coordinateSystems=[]}return t.prototype.create=function(t,e){var n=[];P(Dp,(function(i,r){var o=i.create(t,e);n=n.concat(o||[])})),this._coordinateSystems=n},t.prototype.update=function(t,e){P(this._coordinateSystems,(function(n){n.update&&n.update(t,e)}))},t.prototype.getCoordinateSystems=function(){return this._coordinateSystems.slice()},t.register=function(t,e){Dp[t]=e},t.get=function(t){return Dp[t]},t}(),Lp=/^(min|max)?(.+)$/,kp=function(){function t(t){this._timelineOptions=[],this._mediaList=[],this._currentMediaIndices=[],this._api=t}return t.prototype.setOption=function(t,e,n){t&&(P(xr(t.series),(function(t){t&&t.data&&Z(t.data)&&st(t.data)})),P(xr(t.dataset),(function(t){t&&t.source&&Z(t.source)&&st(t.source)}))),t=w(t);var i=this._optionBackup,r=function(t,e,n){var i,r,o=[],a=t.baseOption,s=t.timeline,l=t.options,u=t.media,h=!!t.media,c=!!(l||s||a&&a.timeline);a?(r=a).timeline||(r.timeline=s):((c||h)&&(t.options=t.media=null),r=t);h&&F(u)&&P(u,(function(t){t&&t.option&&(t.query?o.push(t):i||(i=t))}));function p(t){P(e,(function(e){e(t,n)}))}return p(r),P(l,(function(t){return p(t)})),P(o,(function(t){return p(t.option)})),{baseOption:r,timelineOptions:l||[],mediaDefault:i,mediaList:o}}(t,e,!i);this._newBaseOption=r.baseOption,i?(r.timelineOptions.length&&(i.timelineOptions=r.timelineOptions),r.mediaList.length&&(i.mediaList=r.mediaList),r.mediaDefault&&(i.mediaDefault=r.mediaDefault)):this._optionBackup=r},t.prototype.mountOption=function(t){var e=this._optionBackup;return this._timelineOptions=e.timelineOptions,this._mediaList=e.mediaList,this._mediaDefault=e.mediaDefault,this._currentMediaIndices=[],w(t?e.baseOption:this._newBaseOption)},t.prototype.getTimelineOption=function(t){var e,n=this._timelineOptions;if(n.length){var i=t.getComponent(\"timeline\");i&&(e=w(n[i.getCurrentIndex()]))}return e},t.prototype.getMediaOption=function(t){var e,n,i=this._api.getWidth(),r=this._api.getHeight(),o=this._mediaList,a=this._mediaDefault,s=[],l=[];if(!o.length&&!a)return l;for(var u=0,h=o.length;u<h;u++)Pp(o[u].query,i,r)&&s.push(u);return!s.length&&a&&(s=[-1]),s.length&&(e=s,n=this._currentMediaIndices,e.join(\",\")!==n.join(\",\"))&&(l=O(s,(function(t){return w(-1===t?a.option:o[t].option)}))),this._currentMediaIndices=s,l},t}();function Pp(t,e,n){var i={width:e,height:n,aspectratio:e/n},r=!0;return P(t,(function(t,e){var n=e.match(Lp);if(n&&n[1]&&n[2]){var o=n[1],a=n[2].toLowerCase();(function(t,e,n){return\"min\"===n?t>=e:\"max\"===n?t<=e:t===e})(i[a],t,o)||(r=!1)}})),r}var Op=P,Rp=X,Np=[\"areaStyle\",\"lineStyle\",\"nodeStyle\",\"linkStyle\",\"chordStyle\",\"label\",\"labelLine\"];function zp(t){var e=t&&t.itemStyle;if(e)for(var n=0,i=Np.length;n<i;n++){var r=Np[n],o=e.normal,a=e.emphasis;o&&o[r]&&(t[r]=t[r]||{},t[r].normal?S(t[r].normal,o[r]):t[r].normal=o[r],o[r]=null),a&&a[r]&&(t[r]=t[r]||{},t[r].emphasis?S(t[r].emphasis,a[r]):t[r].emphasis=a[r],a[r]=null)}}function Ep(t,e,n){if(t&&t[e]&&(t[e].normal||t[e].emphasis)){var i=t[e].normal,r=t[e].emphasis;i&&(n?(t[e].normal=t[e].emphasis=null,T(t[e],i)):t[e]=i),r&&(t.emphasis=t.emphasis||{},t.emphasis[e]=r,r.focus&&(t.emphasis.focus=r.focus),r.blurScope&&(t.emphasis.blurScope=r.blurScope))}}function Vp(t){Ep(t,\"itemStyle\"),Ep(t,\"lineStyle\"),Ep(t,\"areaStyle\"),Ep(t,\"label\"),Ep(t,\"labelLine\"),Ep(t,\"upperLabel\"),Ep(t,\"edgeLabel\")}function Bp(t,e){var n=Rp(t)&&t[e],i=Rp(n)&&n.textStyle;if(i){0;for(var r=0,o=wr.length;r<o;r++){var a=wr[r];i.hasOwnProperty(a)&&(n[a]=i[a])}}}function Fp(t){t&&(Vp(t),Bp(t,\"label\"),t.emphasis&&Bp(t.emphasis,\"label\"))}function Gp(t){return F(t)?t:t?[t]:[]}function Hp(t){return(F(t)?t[0]:t)||{}}function Wp(t,e){Op(Gp(t.series),(function(t){Rp(t)&&function(t){if(Rp(t)){zp(t),Vp(t),Bp(t,\"label\"),Bp(t,\"upperLabel\"),Bp(t,\"edgeLabel\"),t.emphasis&&(Bp(t.emphasis,\"label\"),Bp(t.emphasis,\"upperLabel\"),Bp(t.emphasis,\"edgeLabel\"));var e=t.markPoint;e&&(zp(e),Fp(e));var n=t.markLine;n&&(zp(n),Fp(n));var i=t.markArea;i&&Fp(i);var r=t.data;if(\"graph\"===t.type){r=r||t.nodes;var o=t.links||t.edges;if(o&&!Z(o))for(var a=0;a<o.length;a++)Fp(o[a]);P(t.categories,(function(t){Vp(t)}))}if(r&&!Z(r))for(a=0;a<r.length;a++)Fp(r[a]);if((e=t.markPoint)&&e.data){var s=e.data;for(a=0;a<s.length;a++)Fp(s[a])}if((n=t.markLine)&&n.data){var l=n.data;for(a=0;a<l.length;a++)F(l[a])?(Fp(l[a][0]),Fp(l[a][1])):Fp(l[a])}\"gauge\"===t.type?(Bp(t,\"axisLabel\"),Bp(t,\"title\"),Bp(t,\"detail\")):\"treemap\"===t.type?(Ep(t.breadcrumb,\"itemStyle\"),P(t.levels,(function(t){Vp(t)}))):\"tree\"===t.type&&Vp(t.leaves)}}(t)}));var n=[\"xAxis\",\"yAxis\",\"radiusAxis\",\"angleAxis\",\"singleAxis\",\"parallelAxis\",\"radar\"];e&&n.push(\"valueAxis\",\"categoryAxis\",\"logAxis\",\"timeAxis\"),Op(n,(function(e){Op(Gp(t[e]),(function(t){t&&(Bp(t,\"axisLabel\"),Bp(t.axisPointer,\"label\"))}))})),Op(Gp(t.parallel),(function(t){var e=t&&t.parallelAxisDefault;Bp(e,\"axisLabel\"),Bp(e&&e.axisPointer,\"label\")})),Op(Gp(t.calendar),(function(t){Ep(t,\"itemStyle\"),Bp(t,\"dayLabel\"),Bp(t,\"monthLabel\"),Bp(t,\"yearLabel\")})),Op(Gp(t.radar),(function(t){Bp(t,\"name\"),t.name&&null==t.axisName&&(t.axisName=t.name,delete t.name),null!=t.nameGap&&null==t.axisNameGap&&(t.axisNameGap=t.nameGap,delete t.nameGap)})),Op(Gp(t.geo),(function(t){Rp(t)&&(Fp(t),Op(Gp(t.regions),(function(t){Fp(t)})))})),Op(Gp(t.timeline),(function(t){Fp(t),Ep(t,\"label\"),Ep(t,\"itemStyle\"),Ep(t,\"controlStyle\",!0);var e=t.data;F(e)&&P(e,(function(t){X(t)&&(Ep(t,\"label\"),Ep(t,\"itemStyle\"))}))})),Op(Gp(t.toolbox),(function(t){Ep(t,\"iconStyle\"),Op(t.feature,(function(t){Ep(t,\"iconStyle\")}))})),Bp(Hp(t.axisPointer),\"label\"),Bp(Hp(t.tooltip).axisPointer,\"label\")}function Up(t){t&&P(Xp,(function(e){e[0]in t&&!(e[1]in t)&&(t[e[1]]=t[e[0]])}))}var Xp=[[\"x\",\"left\"],[\"y\",\"top\"],[\"x2\",\"right\"],[\"y2\",\"bottom\"]],Yp=[\"grid\",\"geo\",\"parallel\",\"legend\",\"toolbox\",\"title\",\"visualMap\",\"dataZoom\",\"timeline\"],Zp=[[\"borderRadius\",\"barBorderRadius\"],[\"borderColor\",\"barBorderColor\"],[\"borderWidth\",\"barBorderWidth\"]];function jp(t){var e=t&&t.itemStyle;if(e)for(var n=0;n<Zp.length;n++){var i=Zp[n][1],r=Zp[n][0];null!=e[i]&&(e[r]=e[i])}}function qp(t){t&&\"edge\"===t.alignTo&&null!=t.margin&&null==t.edgeDistance&&(t.edgeDistance=t.margin)}function Kp(t){t&&t.downplay&&!t.blur&&(t.blur=t.downplay)}function $p(t,e){if(t)for(var n=0;n<t.length;n++)e(t[n]),t[n]&&$p(t[n].children,e)}function Jp(t,e){Wp(t,e),t.series=xr(t.series),P(t.series,(function(t){if(X(t)){var e=t.type;if(\"line\"===e)null!=t.clipOverflow&&(t.clip=t.clipOverflow);else if(\"pie\"===e||\"gauge\"===e){if(null!=t.clockWise&&(t.clockwise=t.clockWise),qp(t.label),(r=t.data)&&!Z(r))for(var n=0;n<r.length;n++)qp(r[n]);null!=t.hoverOffset&&(t.emphasis=t.emphasis||{},(t.emphasis.scaleSize=null)&&(t.emphasis.scaleSize=t.hoverOffset))}else if(\"gauge\"===e){var i=function(t,e){for(var n=e.split(\",\"),i=t,r=0;r<n.length&&null!=(i=i&&i[n[r]]);r++);return i}(t,\"pointer.color\");null!=i&&function(t,e,n,i){for(var r,o=e.split(\",\"),a=t,s=0;s<o.length-1;s++)null==a[r=o[s]]&&(a[r]={}),a=a[r];(i||null==a[o[s]])&&(a[o[s]]=n)}(t,\"itemStyle.color\",i)}else if(\"bar\"===e){var r;if(jp(t),jp(t.backgroundStyle),jp(t.emphasis),(r=t.data)&&!Z(r))for(n=0;n<r.length;n++)\"object\"==typeof r[n]&&(jp(r[n]),jp(r[n]&&r[n].emphasis))}else if(\"sunburst\"===e){var o=t.highlightPolicy;o&&(t.emphasis=t.emphasis||{},t.emphasis.focus||(t.emphasis.focus=o)),Kp(t),$p(t.data,Kp)}else\"graph\"===e||\"sankey\"===e?function(t){t&&null!=t.focusNodeAdjacency&&(t.emphasis=t.emphasis||{},null==t.emphasis.focus&&(t.emphasis.focus=\"adjacency\"))}(t):\"map\"===e&&(t.mapType&&!t.map&&(t.map=t.mapType),t.mapLocation&&T(t,t.mapLocation));null!=t.hoverAnimation&&(t.emphasis=t.emphasis||{},t.emphasis&&null==t.emphasis.scale&&(t.emphasis.scale=t.hoverAnimation)),Up(t)}})),t.dataRange&&(t.visualMap=t.dataRange),P(Yp,(function(e){var n=t[e];n&&(F(n)||(n=[n]),P(n,(function(t){Up(t)})))}))}function Qp(t){P(t,(function(e,n){var i=[],r=[NaN,NaN],o=[e.stackResultDimension,e.stackedOverDimension],a=e.data,s=e.isStackedByIndex,l=a.map(o,(function(o,l,u){var h,c,p=a.get(e.stackedDimension,u);if(isNaN(p))return r;s?c=a.getRawIndex(u):h=a.get(e.stackedByDimension,u);for(var d=NaN,f=n-1;f>=0;f--){var g=t[f];if(s||(c=g.data.rawIndexOf(g.stackedByDimension,h)),c>=0){var y=g.data.getByRawIndex(g.stackResultDimension,c);if(p>=0&&y>0||p<=0&&y<0){p=tr(p,y),d=y;break}}}return i[0]=p,i[1]=d,i}));a.hostModel.setData(l),e.data=l}))}var td,ed,nd,id,rd,od=function(t){this.data=t.data||(t.sourceFormat===Qc?{}:[]),this.sourceFormat=t.sourceFormat||ep,this.seriesLayoutBy=t.seriesLayoutBy||np,this.startIndex=t.startIndex||0,this.dimensionsDefine=t.dimensionsDefine,this.dimensionsDetectedCount=t.dimensionsDetectedCount,this.encodeDefine=t.encodeDefine,this.metaRawOption=t.metaRawOption};function ad(t){return t instanceof od}function sd(t,e,n,i){n=n||hd(t);var r=e.seriesLayoutBy,o=function(t,e,n,i,r){var o,a;if(!t)return{dimensionsDefine:cd(r),startIndex:a,dimensionsDetectedCount:o};if(e===$c){var s=t;\"auto\"===i||null==i?pd((function(t){null!=t&&\"-\"!==t&&(H(t)?null==a&&(a=1):a=0)}),n,s,10):a=U(i)?i:i?1:0,r||1!==a||(r=[],pd((function(t,e){r[e]=null!=t?t+\"\":\"\"}),n,s,1/0)),o=r?r.length:n===ip?s.length:s[0]?s[0].length:null}else if(e===Jc)r||(r=function(t){var e,n=0;for(;n<t.length&&!(e=t[n++]););if(e){var i=[];return P(e,(function(t,e){i.push(e)})),i}}(t));else if(e===Qc)r||(r=[],P(t,(function(t,e){r.push(e)})));else if(e===Kc){var l=Sr(t[0]);o=F(l)&&l.length||1}return{startIndex:a,dimensionsDefine:cd(r),dimensionsDetectedCount:o}}(t,n,r,e.sourceHeader,e.dimensions);return new od({data:t,sourceFormat:n,seriesLayoutBy:r,dimensionsDefine:o.dimensionsDefine,startIndex:o.startIndex,dimensionsDetectedCount:o.dimensionsDetectedCount,encodeDefine:ud(i),metaRawOption:w(e)})}function ld(t){return new od({data:t,sourceFormat:Z(t)?tp:Kc})}function ud(t){return t?ht(t):null}function hd(t){var e=ep;if(Z(t))e=tp;else if(F(t)){0===t.length&&(e=$c);for(var n=0,i=t.length;n<i;n++){var r=t[n];if(null!=r){if(F(r)){e=$c;break}if(X(r)){e=Jc;break}}}}else if(X(t))for(var o in t)if(dt(t,o)&&k(t[o])){e=Qc;break}return e}function cd(t){if(t){var e=ht();return O(t,(function(t,n){var i={name:(t=X(t)?t:{name:t}).name,displayName:t.displayName,type:t.type};if(null==i.name)return i;i.name+=\"\",null==i.displayName&&(i.displayName=i.name);var r=e.get(i.name);return r?i.name+=\"-\"+r.count++:e.set(i.name,{count:1}),i}))}}function pd(t,e,n,i){if(e===ip)for(var r=0;r<n.length&&r<i;r++)t(n[r]?n[r][0]:null,r);else{var o=n[0]||[];for(r=0;r<o.length&&r<i;r++)t(o[r],r)}}var dd=function(){function t(t,e){var n=ad(t)?t:ld(t);this._source=n;var i=this._data=n.data;n.sourceFormat===tp&&(this._offset=0,this._dimSize=e,this._data=i),rd(this,i,n)}return t.prototype.getSource=function(){return this._source},t.prototype.count=function(){return 0},t.prototype.getItem=function(t,e){},t.prototype.appendData=function(t){},t.prototype.clean=function(){},t.protoInitialize=function(){var e=t.prototype;e.pure=!1,e.persistent=!0}(),t.internalField=function(){var t;rd=function(t,r,o){var a=o.sourceFormat,s=o.seriesLayoutBy,l=o.startIndex,u=o.dimensionsDefine,h=id[Sd(a,s)];if(I(t,h),a===tp)t.getItem=e,t.count=i,t.fillStorage=n;else{var c=yd(a,s);t.getItem=V(c,null,r,l,u);var p=_d(a,s);t.count=V(p,null,r,l,u)}};var e=function(t,e){t-=this._offset,e=e||[];for(var n=this._data,i=this._dimSize,r=i*t,o=0;o<i;o++)e[o]=n[r+o];return e},n=function(t,e,n,i){for(var r=this._data,o=this._dimSize,a=0;a<o;a++){for(var s=i[a],l=null==s[0]?1/0:s[0],u=null==s[1]?-1/0:s[1],h=e-t,c=n[a],p=0;p<h;p++){var d=r[p*o+a];c[t+p]=d,d<l&&(l=d),d>u&&(u=d)}s[0]=l,s[1]=u}},i=function(){return this._data?this._data.length/this._dimSize:0};function r(t){for(var e=0;e<t.length;e++)this._data.push(t[e])}(t={}).arrayRows_column={pure:!0,appendData:r},t.arrayRows_row={pure:!0,appendData:function(){throw new Error('Do not support appendData when set seriesLayoutBy: \"row\".')}},t.objectRows={pure:!0,appendData:r},t.keyedColumns={pure:!0,appendData:function(t){var e=this._data;P(t,(function(t,n){for(var i=e[n]||(e[n]=[]),r=0;r<(t||[]).length;r++)i.push(t[r])}))}},t.original={appendData:r},t.typedArray={persistent:!1,pure:!0,appendData:function(t){this._data=t},clean:function(){this._offset+=this.count(),this._data=null}},id=t}(),t}(),fd=function(t,e,n,i){return t[i]},gd=((td={}).arrayRows_column=function(t,e,n,i){return t[i+e]},td.arrayRows_row=function(t,e,n,i){i+=e;for(var r=[],o=t,a=0;a<o.length;a++){var s=o[a];r.push(s?s[i]:null)}return r},td.objectRows=fd,td.keyedColumns=function(t,e,n,i){for(var r=[],o=0;o<n.length;o++){var a=n[o].name;0;var s=t[a];r.push(s?s[i]:null)}return r},td.original=fd,td);function yd(t,e){var n=gd[Sd(t,e)];return n}var vd=function(t,e,n){return t.length},md=((ed={}).arrayRows_column=function(t,e,n){return Math.max(0,t.length-e)},ed.arrayRows_row=function(t,e,n){var i=t[0];return i?Math.max(0,i.length-e):0},ed.objectRows=vd,ed.keyedColumns=function(t,e,n){var i=n[0].name;var r=t[i];return r?r.length:0},ed.original=vd,ed);function _d(t,e){var n=md[Sd(t,e)];return n}var xd=function(t,e,n){return null!=e?t[e]:t},bd=((nd={}).arrayRows=xd,nd.objectRows=function(t,e,n){return null!=e?t[n]:t},nd.keyedColumns=xd,nd.original=function(t,e,n){var i=Sr(t);return null!=e&&i instanceof Array?i[e]:i},nd.typedArray=xd,nd);function wd(t){var e=bd[t];return e}function Sd(t,e){return t===$c?t+\"_\"+e:t}function Md(t,e,n){if(t){var i=t.getRawDataItem(e);if(null!=i){var r,o,a=t.getProvider().getSource().sourceFormat,s=t.getDimensionInfo(n);return s&&(r=s.name,o=s.index),wd(a)(i,o,r)}}}var Id=/\\{@(.+?)\\}/g,Td=function(){function t(){}return t.prototype.getDataParams=function(t,e){var n=this.getData(e),i=this.getRawValue(t,e),r=n.getRawIndex(t),o=n.getName(t),a=n.getRawDataItem(t),s=n.getItemVisual(t,\"style\"),l=s&&s[n.getItemVisual(t,\"drawType\")||\"fill\"],u=s&&s.stroke,h=this.mainType,c=\"series\"===h,p=n.userOutput;return{componentType:h,componentSubType:this.subType,componentIndex:this.componentIndex,seriesType:c?this.subType:null,seriesIndex:this.seriesIndex,seriesId:c?this.id:null,seriesName:c?this.name:null,name:o,dataIndex:r,data:a,dataType:e,value:i,color:l,borderColor:u,dimensionNames:p?p.dimensionNames:null,encode:p?p.encode:null,$vars:[\"seriesName\",\"name\",\"value\"]}},t.prototype.getFormattedLabel=function(t,e,n,i,r,o){e=e||\"normal\";var a=this.getData(n),s=this.getDataParams(t,n);(o&&(s.value=o.interpolatedValue),null!=i&&F(s.value)&&(s.value=s.value[i]),r)||(r=a.getItemModel(t).get(\"normal\"===e?[\"label\",\"formatter\"]:[e,\"label\",\"formatter\"]));return\"function\"==typeof r?(s.status=e,s.dimensionIndex=i,r(s)):\"string\"==typeof r?Ac(r,s).replace(Id,(function(e,n){var i=n.length,r=\"[\"===n.charAt(0)&&\"]\"===n.charAt(i-1)?+n.slice(1,i-1):n,s=Md(a,t,r);if(o&&F(o.interpolatedValue)){var l=a.getDimensionInfo(r);l&&(s=o.interpolatedValue[l.index])}return null!=s?s+\"\":\"\"})):void 0},t.prototype.getRawValue=function(t,e){return Md(this.getData(e),t)},t.prototype.formatTooltip=function(t,e,n){},t}();function Cd(t){var e,n;return X(t)?t.type&&(n=t):e=t,{markupText:e,markupFragment:n}}function Dd(t){return new Ad(t)}var Ad=function(){function t(t){t=t||{},this._reset=t.reset,this._plan=t.plan,this._count=t.count,this._onDirty=t.onDirty,this._dirty=!0}return t.prototype.perform=function(t){var e,n=this._upstream,i=t&&t.skip;if(this._dirty&&n){var r=this.context;r.data=r.outputData=n.context.outputData}this.__pipeline&&(this.__pipeline.currentTask=this),this._plan&&!i&&(e=this._plan(this.context));var o,a=h(this._modBy),s=this._modDataCount||0,l=h(t&&t.modBy),u=t&&t.modDataCount||0;function h(t){return!(t>=1)&&(t=1),t}a===l&&s===u||(e=\"reset\"),(this._dirty||\"reset\"===e)&&(this._dirty=!1,o=this._doReset(i)),this._modBy=l,this._modDataCount=u;var c=t&&t.step;if(this._dueEnd=n?n._outputDueEnd:this._count?this._count(this.context):1/0,this._progress){var p=this._dueIndex,d=Math.min(null!=c?this._dueIndex+c:1/0,this._dueEnd);if(!i&&(o||p<d)){var f=this._progress;if(F(f))for(var g=0;g<f.length;g++)this._doProgress(f[g],p,d,l,u);else this._doProgress(f,p,d,l,u)}this._dueIndex=d;var y=null!=this._settedOutputEnd?this._settedOutputEnd:d;0,this._outputDueEnd=y}else this._dueIndex=this._outputDueEnd=null!=this._settedOutputEnd?this._settedOutputEnd:this._dueEnd;return this.unfinished()},t.prototype.dirty=function(){this._dirty=!0,this._onDirty&&this._onDirty(this.context)},t.prototype._doProgress=function(t,e,n,i,r){Ld.reset(e,n,i,r),this._callingProgress=t,this._callingProgress({start:e,end:n,count:n-e,next:Ld.next},this.context)},t.prototype._doReset=function(t){var e,n;this._dueIndex=this._outputDueEnd=this._dueEnd=0,this._settedOutputEnd=null,!t&&this._reset&&((e=this._reset(this.context))&&e.progress&&(n=e.forceFirstProgress,e=e.progress),F(e)&&!e.length&&(e=null)),this._progress=e,this._modBy=this._modDataCount=null;var i=this._downstream;return i&&i.dirty(),n},t.prototype.unfinished=function(){return this._progress&&this._dueIndex<this._dueEnd},t.prototype.pipe=function(t){(this._downstream!==t||this._dirty)&&(this._downstream=t,t._upstream=this,t.dirty())},t.prototype.dispose=function(){this._disposed||(this._upstream&&(this._upstream._downstream=null),this._downstream&&(this._downstream._upstream=null),this._dirty=!1,this._disposed=!0)},t.prototype.getUpstream=function(){return this._upstream},t.prototype.getDownstream=function(){return this._downstream},t.prototype.setOutputEnd=function(t){this._outputDueEnd=this._settedOutputEnd=t},t}(),Ld=function(){var t,e,n,i,r,o={reset:function(l,u,h,c){e=l,t=u,n=h,i=c,r=Math.ceil(i/n),o.next=n>1&&i>0?s:a}};return o;function a(){return e<t?e++:null}function s(){var o=e%r*n+Math.ceil(e/r),a=e>=t?null:o<i?o:e;return e++,a}}();function kd(t,e){var n=e&&e.type;if(\"ordinal\"===n){var i=e&&e.ordinalMeta;return i?i.parseAndCollect(t):t}return\"time\"===n&&\"number\"!=typeof t&&null!=t&&\"-\"!==t&&(t=+or(t)),null==t||\"\"===t?NaN:+t}var Pd=ht({number:function(t){return parseFloat(t)},time:function(t){return+or(t)},trim:function(t){return\"string\"==typeof t?ot(t):t}});function Od(t){return Pd.get(t)}var Rd={lt:function(t,e){return t<e},lte:function(t,e){return t<=e},gt:function(t,e){return t>e},gte:function(t,e){return t>=e}},Nd=function(){function t(t,e){if(\"number\"!=typeof e){var n=\"\";0,vr(n)}this._opFn=Rd[t],this._rvalFloat=cr(e)}return t.prototype.evaluate=function(t){return\"number\"==typeof t?this._opFn(t,this._rvalFloat):this._opFn(cr(t),this._rvalFloat)},t}(),zd=function(){function t(t,e){var n=\"desc\"===t;this._resultLT=n?1:-1,null==e&&(e=n?\"min\":\"max\"),this._incomparable=\"min\"===e?-1/0:1/0}return t.prototype.evaluate=function(t,e){var n=typeof t,i=typeof e,r=\"number\"===n?t:cr(t),o=\"number\"===i?e:cr(e),a=isNaN(r),s=isNaN(o);if(a&&(r=this._incomparable),s&&(o=this._incomparable),a&&s){var l=\"string\"===n,u=\"string\"===i;l&&(r=u?t:0),u&&(o=l?e:0)}return r<o?this._resultLT:r>o?-this._resultLT:0},t}(),Ed=function(){function t(t,e){this._rval=e,this._isEQ=t,this._rvalTypeof=typeof e,this._rvalFloat=cr(e)}return t.prototype.evaluate=function(t){var e=t===this._rval;if(!e){var n=typeof t;n===this._rvalTypeof||\"number\"!==n&&\"number\"!==this._rvalTypeof||(e=cr(t)===this._rvalFloat)}return this._isEQ?e:!e},t}();function Vd(t,e){return\"eq\"===t||\"ne\"===t?new Ed(\"eq\"===t,e):dt(Rd,t)?new Nd(t,e):null}var Bd=function(){function t(){}return t.prototype.getRawData=function(){throw new Error(\"not supported\")},t.prototype.getRawDataItem=function(t){throw new Error(\"not supported\")},t.prototype.cloneRawData=function(){},t.prototype.getDimensionInfo=function(t){},t.prototype.cloneAllDimensionInfo=function(){},t.prototype.count=function(){},t.prototype.retrieveValue=function(t,e){},t.prototype.retrieveValueFromItem=function(t,e){},t.prototype.convertValue=function(t,e){return kd(t,e)},t}();function Fd(t){var e=t.sourceFormat;if(!Yd(e)){var n=\"\";0,vr(n)}return t.data}function Gd(t){var e=t.sourceFormat,n=t.data;if(!Yd(e)){var i=\"\";0,vr(i)}if(e===$c){for(var r=[],o=0,a=n.length;o<a;o++)r.push(n[o].slice());return r}if(e===Jc){for(r=[],o=0,a=n.length;o<a;o++)r.push(I({},n[o]));return r}}function Hd(t,e,n){if(null!=n)return\"number\"==typeof n||!isNaN(n)&&!dt(e,n)?t[n]:dt(e,n)?e[n]:void 0}function Wd(t){return w(t)}var Ud=ht();function Xd(t,e,n,i){var r=\"\";e.length||vr(r),X(t)||vr(r);var o=t.type,a=Ud.get(o);a||vr(r);var s=O(e,(function(t){return function(t,e){var n=new Bd,i=t.data,r=n.sourceFormat=t.sourceFormat,o=t.startIndex,a=\"\";t.seriesLayoutBy!==np&&vr(a);var s=[],l={},u=t.dimensionsDefine;if(u)P(u,(function(t,e){var n=t.name,i={index:e,name:n,displayName:t.displayName};if(s.push(i),null!=n){var r=\"\";dt(l,n)&&vr(r),l[n]=i}}));else for(var h=0;h<t.dimensionsDetectedCount;h++)s.push({index:h});var c=yd(r,np);e.__isBuiltIn&&(n.getRawDataItem=function(t){return c(i,o,s,t)},n.getRawData=V(Fd,null,t)),n.cloneRawData=V(Gd,null,t);var p=_d(r,np);n.count=V(p,null,i,o,s);var d=wd(r);n.retrieveValue=function(t,e){var n=c(i,o,s,t);return f(n,e)};var f=n.retrieveValueFromItem=function(t,e){if(null!=t){var n=s[e];return n?d(t,e,n.name):void 0}};return n.getDimensionInfo=V(Hd,null,s,l),n.cloneAllDimensionInfo=V(Wd,null,s),n}(t,a)})),l=xr(a.transform({upstream:s[0],upstreamList:s,config:w(t.config)}));return O(l,(function(t,n){var i,r=\"\";X(t)||vr(r),t.data||vr(r),Yd(hd(t.data))||vr(r);var o=e[0];if(o&&0===n&&!t.dimensions){var a=o.startIndex;a&&(t.data=o.data.slice(0,a).concat(t.data)),i={seriesLayoutBy:np,sourceHeader:a,dimensions:o.metaRawOption.dimensions}}else i={seriesLayoutBy:np,sourceHeader:0,dimensions:t.dimensions};return sd(t.data,i,null,null)}))}function Yd(t){return t===$c||t===Jc}var Zd=function(){function t(t){this._sourceList=[],this._upstreamSignList=[],this._versionSignBase=0,this._sourceHost=t}return t.prototype.dirty=function(){this._setLocalSource([],[])},t.prototype._setLocalSource=function(t,e){this._sourceList=t,this._upstreamSignList=e,this._versionSignBase++,this._versionSignBase>9e10&&(this._versionSignBase=0)},t.prototype._getVersionSign=function(){return this._sourceHost.uid+\"_\"+this._versionSignBase},t.prototype.prepareSource=function(){this._isDirty()&&this._createSource()},t.prototype._createSource=function(){this._setLocalSource([],[]);var t,e,n=this._sourceHost,i=this._getUpstreamSourceManagers(),r=!!i.length;if(qd(n)){var o=n,a=void 0,s=void 0,l=void 0;if(r){var u=i[0];u.prepareSource(),a=(l=u.getSource()).data,s=l.sourceFormat,e=[u._getVersionSign()]}else s=Z(a=o.get(\"data\",!0))?tp:Kc,e=[];var h=this._getSourceMetaRawOption(),c=l?l.metaRawOption:null;t=[sd(a,{seriesLayoutBy:tt(h.seriesLayoutBy,c?c.seriesLayoutBy:null),sourceHeader:tt(h.sourceHeader,c?c.sourceHeader:null),dimensions:tt(h.dimensions,c?c.dimensions:null)},s,o.get(\"encode\",!0))]}else{var p=n;if(r){var d=this._applyTransform(i);t=d.sourceList,e=d.upstreamSignList}else{t=[sd(p.get(\"source\",!0),this._getSourceMetaRawOption(),null,null)],e=[]}}this._setLocalSource(t,e)},t.prototype._applyTransform=function(t){var e,n=this._sourceHost,i=n.get(\"transform\",!0),r=n.get(\"fromTransformResult\",!0);if(null!=r){var o=\"\";1!==t.length&&Kd(o)}var a,s=[],l=[];return P(t,(function(t){t.prepareSource();var e=t.getSource(r||0),n=\"\";null==r||e||Kd(n),s.push(e),l.push(t._getVersionSign())})),i?e=function(t,e,n){var i=xr(t),r=i.length,o=\"\";r||vr(o);for(var a=0,s=r;a<s;a++)e=Xd(i[a],e),a!==s-1&&(e.length=Math.max(e.length,1));return e}(i,s,n.componentIndex):null!=r&&(e=[(a=s[0],new od({data:a.data,sourceFormat:a.sourceFormat,seriesLayoutBy:a.seriesLayoutBy,dimensionsDefine:w(a.dimensionsDefine),startIndex:a.startIndex,dimensionsDetectedCount:a.dimensionsDetectedCount,encodeDefine:ud(a.encodeDefine)}))]),{sourceList:e,upstreamSignList:l}},t.prototype._isDirty=function(){if(!this._sourceList.length)return!0;for(var t=this._getUpstreamSourceManagers(),e=0;e<t.length;e++){var n=t[e];if(n._isDirty()||this._upstreamSignList[e]!==n._getVersionSign())return!0}},t.prototype.getSource=function(t){return this._sourceList[t||0]},t.prototype._getUpstreamSourceManagers=function(){var t=this._sourceHost;if(qd(t)){var e=hp(t);return e?[e.getSourceManager()]:[]}return O(function(t){return t.get(\"transform\",!0)||t.get(\"fromTransformResult\",!0)?Er(t.ecModel,\"dataset\",{index:t.get(\"fromDatasetIndex\",!0),id:t.get(\"fromDatasetId\",!0)},Nr).models:[]}(t),(function(t){return t.getSourceManager()}))},t.prototype._getSourceMetaRawOption=function(){var t,e,n,i=this._sourceHost;if(qd(i))t=i.get(\"seriesLayoutBy\",!0),e=i.get(\"sourceHeader\",!0),n=i.get(\"dimensions\",!0);else if(!this._getUpstreamSourceManagers().length){var r=i;t=r.get(\"seriesLayoutBy\",!0),e=r.get(\"sourceHeader\",!0),n=r.get(\"dimensions\",!0)}return{seriesLayoutBy:t,sourceHeader:e,dimensions:n}},t}();function jd(t){t.option.transform&&st(t.option.transform)}function qd(t){return\"series\"===t.mainType}function Kd(t){throw new Error(t)}function $d(t,e){var n=t.color||\"#6e7079\",i=t.fontSize||12,r=t.fontWeight||\"400\",o=t.color||\"#464646\",a=t.fontSize||14,s=t.fontWeight||\"900\";return\"html\"===e?{nameStyle:\"font-size:\"+Ic(i+\"\")+\"px;color:\"+Ic(n)+\";font-weight:\"+Ic(r+\"\"),valueStyle:\"font-size:\"+Ic(a+\"\")+\"px;color:\"+Ic(o)+\";font-weight:\"+Ic(s+\"\")}:{nameStyle:{fontSize:i,fill:n,fontWeight:r},valueStyle:{fontSize:a,fill:o,fontWeight:s}}}var Jd=[0,10,20,30],Qd=[\"\",\"\\n\",\"\\n\\n\",\"\\n\\n\\n\"];function tf(t,e){return e.type=t,e}function ef(t){return dt(nf,t.type)&&nf[t.type]}var nf={section:{planLayout:function(t){var e=t.blocks.length,n=e>1||e>0&&!t.noHeader,i=0;P(t.blocks,(function(t){ef(t).planLayout(t);var e=t.__gapLevelBetweenSubBlocks;e>=i&&(i=e+(!n||e&&(\"section\"!==t.type||t.noHeader)?0:1))})),t.__gapLevelBetweenSubBlocks=i},build:function(t,e,n,i){var r=e.noHeader,o=of(e),a=function(t,e,n,i){var r=[],o=e.blocks||[];rt(!o||F(o)),o=o||[];var a=t.orderMode;if(e.sortBlocks&&a){o=o.slice();var s={valueAsc:\"asc\",valueDesc:\"desc\"};if(dt(s,a)){var l=new zd(s[a],null);o.sort((function(t,e){return l.evaluate(t.sortParam,e.sortParam)}))}else\"seriesDesc\"===a&&o.reverse()}var u=of(e);if(P(o,(function(e,n){var o=ef(e).build(t,e,n>0?u.html:0,i);null!=o&&r.push(o)})),!r.length)return;return\"richText\"===t.renderMode?r.join(u.richText):af(r.join(\"\"),n)}(t,e,r?n:o.html,i);if(r)return a;var s=Tc(e.header,\"ordinal\",t.useUTC),l=$d(i,t.renderMode).nameStyle;return\"richText\"===t.renderMode?sf(t,s,l)+o.richText+a:af('<div style=\"'+l+\";\"+'line-height:1;\">'+Ic(s)+\"</div>\"+a,n)}},nameValue:{planLayout:function(t){t.__gapLevelBetweenSubBlocks=0},build:function(t,e,n,i){var r=t.renderMode,o=e.noName,a=e.noValue,s=!e.markerType,l=e.name,u=e.value,h=t.useUTC;if(!o||!a){var c=s?\"\":t.markupStyleCreator.makeTooltipMarker(e.markerType,e.markerColor||\"#333\",r),p=o?\"\":Tc(l,\"ordinal\",h),d=e.valueType,f=a?[]:F(u)?O(u,(function(t,e){return Tc(t,F(d)?d[e]:d,h)})):[Tc(u,F(d)?d[0]:d,h)],g=!s||!o,y=!s&&o,v=$d(i,r),m=v.nameStyle,_=v.valueStyle;return\"richText\"===r?(s?\"\":c)+(o?\"\":sf(t,p,m))+(a?\"\":function(t,e,n,i,r){var o=[r],a=i?10:20;return n&&o.push({padding:[0,0,0,a],align:\"right\"}),t.markupStyleCreator.wrapRichTextStyle(e.join(\"  \"),o)}(t,f,g,y,_)):af((s?\"\":c)+(o?\"\":function(t,e,n){return'<span style=\"'+n+\";\"+(e?\"margin-left:2px\":\"\")+'\">'+Ic(t)+\"</span>\"}(p,!s,m))+(a?\"\":function(t,e,n,i){var r=n?\"10px\":\"20px\";return'<span style=\"'+(e?\"float:right;margin-left:\"+r:\"\")+\";\"+i+'\">'+O(t,(function(t){return Ic(t)})).join(\"&nbsp;&nbsp;\")+\"</span>\"}(f,g,y,_)),n)}}}};function rf(t,e,n,i,r,o){if(t){var a=ef(t);a.planLayout(t);var s={useUTC:r,renderMode:n,orderMode:i,markupStyleCreator:e};return a.build(s,t,0,o)}}function of(t){var e=t.__gapLevelBetweenSubBlocks;return{html:Jd[e],richText:Qd[e]}}function af(t,e){return'<div style=\"'+(\"margin: \"+e+\"px 0 0\")+\";\"+'line-height:1;\">'+t+'<div style=\"clear:both\"></div></div>'}function sf(t,e,n){return t.markupStyleCreator.wrapRichTextStyle(e,n)}function lf(t,e){return kc(t.getData().getItemVisual(e,\"style\")[t.visualDrawType])}function uf(t,e){var n=t.get(\"padding\");return null!=n?n:\"richText\"===e?[8,10]:10}var hf=function(){function t(){this.richTextStyles={},this._nextStyleNameId=dr()}return t.prototype._generateStyleName=function(){return\"__EC_aUTo_\"+this._nextStyleNameId++},t.prototype.makeTooltipMarker=function(t,e,n){var i=\"richText\"===n?this._generateStyleName():null,r=Lc({color:e,type:t,renderMode:n,markerId:i});return H(r)?r:(this.richTextStyles[i]=r.style,r.content)},t.prototype.wrapRichTextStyle=function(t,e){var n={};F(e)?P(e,(function(t){return I(n,t)})):I(n,e);var i=this._generateStyleName();return this.richTextStyles[i]=n,\"{\"+i+\"|\"+t+\"}\"},t}();function cf(t){var e,n,i,r,o=t.series,a=t.dataIndex,s=t.multipleSeries,l=o.getData(),u=l.mapDimensionsAll(\"defaultedTooltip\"),h=u.length,c=o.getRawValue(a),p=F(c),d=lf(o,a);if(h>1||p&&!h){var f=function(t,e,n,i,r){var o=e.getData(),a=R(t,(function(t,e,n){var i=o.getDimensionInfo(n);return t||i&&!1!==i.tooltip&&null!=i.displayName}),!1),s=[],l=[],u=[];function h(t,e){var n=o.getDimensionInfo(e);n&&!1!==n.otherDims.tooltip&&(a?u.push(tf(\"nameValue\",{markerType:\"subItem\",markerColor:r,name:n.displayName,value:t,valueType:n.type})):(s.push(t),l.push(n.type)))}return i.length?P(i,(function(t){h(Md(o,n,t),t)})):P(t,h),{inlineValues:s,inlineValueTypes:l,blocks:u}}(c,o,a,u,d);e=f.inlineValues,n=f.inlineValueTypes,i=f.blocks,r=f.inlineValues[0]}else if(h){var g=l.getDimensionInfo(u[0]);r=e=Md(l,a,u[0]),n=g.type}else r=e=p?c[0]:c;var y=Dr(o),v=y&&o.name||\"\",m=l.getName(a),_=s?v:m;return tf(\"section\",{header:v,noHeader:s||!y,sortParam:r,blocks:[tf(\"nameValue\",{markerType:\"item\",markerColor:d,name:_,noName:!ot(_),value:e,valueType:n})].concat(i||[])})}var pf=kr();function df(t,e){return t.getName(e)||t.getId(e)}var ff=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e._selectedDataIndicesMap={},e}return n(e,t),e.prototype.init=function(t,e,n){this.seriesIndex=this.componentIndex,this.dataTask=Dd({count:yf,reset:vf}),this.dataTask.context={model:this},this.mergeDefaultAndTheme(t,n),(pf(this).sourceManager=new Zd(this)).prepareSource();var i=this.getInitialData(t,n);_f(i,this),this.dataTask.context.data=i,pf(this).dataBeforeProcessed=i,gf(this),this._initSelectedMapFromData(i)},e.prototype.mergeDefaultAndTheme=function(t,e){var n=Fc(this),i=n?Hc(t):{},r=this.subType;Xc.hasClass(r)&&(r+=\"Series\"),S(t,e.getTheme().get(this.subType)),S(t,this.getDefaultOption()),br(t,\"label\",[\"show\"]),this.fillDataTextStyle(t.data),n&&Gc(t,i,n)},e.prototype.mergeOption=function(t,e){t=S(this.option,t,!0),this.fillDataTextStyle(t.data);var n=Fc(this);n&&Gc(this.option,t,n);var i=pf(this).sourceManager;i.dirty(),i.prepareSource();var r=this.getInitialData(t,e);_f(r,this),this.dataTask.dirty(),this.dataTask.context.data=r,pf(this).dataBeforeProcessed=r,gf(this),this._initSelectedMapFromData(r)},e.prototype.fillDataTextStyle=function(t){if(t&&!Z(t))for(var e=[\"show\"],n=0;n<t.length;n++)t[n]&&t[n].label&&br(t[n],\"label\",e)},e.prototype.getInitialData=function(t,e){},e.prototype.appendData=function(t){this.getRawData().appendData(t.data)},e.prototype.getData=function(t){var e=bf(this);if(e){var n=e.context.data;return null==t?n:n.getLinkedData(t)}return pf(this).data},e.prototype.getAllData=function(){var t=this.getData();return t&&t.getLinkedDataAll?t.getLinkedDataAll():[{data:t}]},e.prototype.setData=function(t){var e=bf(this);if(e){var n=e.context;n.outputData=t,e!==this.dataTask&&(n.data=t)}pf(this).data=t},e.prototype.getSource=function(){return pf(this).sourceManager.getSource()},e.prototype.getRawData=function(){return pf(this).dataBeforeProcessed},e.prototype.getBaseAxis=function(){var t=this.coordinateSystem;return t&&t.getBaseAxis&&t.getBaseAxis()},e.prototype.formatTooltip=function(t,e,n){return cf({series:this,dataIndex:t,multipleSeries:e})},e.prototype.isAnimationEnabled=function(){if(a.node)return!1;var t=this.getShallow(\"animation\");return t&&this.getData().count()>this.getShallow(\"animationThreshold\")&&(t=!1),!!t},e.prototype.restoreData=function(){this.dataTask.dirty()},e.prototype.getColorFromPalette=function(t,e,n){var i=this.ecModel,r=_p.prototype.getColorFromPalette.call(this,t,e,n);return r||(r=i.getColorFromPalette(t,e,n)),r},e.prototype.coordDimToDataDim=function(t){return this.getRawData().mapDimensionsAll(t)},e.prototype.getProgressive=function(){return this.get(\"progressive\")},e.prototype.getProgressiveThreshold=function(){return this.get(\"progressiveThreshold\")},e.prototype.select=function(t,e){this._innerSelect(this.getData(e),t)},e.prototype.unselect=function(t,e){var n=this.option.selectedMap;if(n)for(var i=this.getData(e),r=0;r<t.length;r++){var o=df(i,t[r]);n[o]=!1,this._selectedDataIndicesMap[o]=-1}},e.prototype.toggleSelect=function(t,e){for(var n=[],i=0;i<t.length;i++)n[0]=t[i],this.isSelected(t[i],e)?this.unselect(n,e):this.select(n,e)},e.prototype.getSelectedDataIndices=function(){for(var t=this._selectedDataIndicesMap,e=E(t),n=[],i=0;i<e.length;i++){var r=t[e[i]];r>=0&&n.push(r)}return n},e.prototype.isSelected=function(t,e){var n=this.option.selectedMap;return n&&n[df(this.getData(e),t)]||!1},e.prototype._innerSelect=function(t,e){var n,i,r=this.option.selectedMode,o=e.length;if(r&&o)if(\"multiple\"===r)for(var a=this.option.selectedMap||(this.option.selectedMap={}),s=0;s<o;s++){var l=e[s];a[h=df(t,l)]=!0,this._selectedDataIndicesMap[h]=t.getRawIndex(l)}else if(\"single\"===r||!0===r){var u=e[o-1],h=df(t,u);this.option.selectedMap=((n={})[h]=!0,n),this._selectedDataIndicesMap=((i={})[h]=t.getRawIndex(u),i)}},e.prototype._initSelectedMapFromData=function(t){if(!this.option.selectedMap){var e=[];t.hasItemOption&&t.each((function(n){var i=t.getRawDataItem(n);i&&i.selected&&e.push(n)})),e.length>0&&this._innerSelect(t,e)}},e.registerClass=function(t){return Xc.registerClass(t)},e.protoInitialize=function(){var t=e.prototype;t.type=\"series.__base__\",t.seriesIndex=0,t.useColorPaletteOnData=!1,t.ignoreStyleOnData=!1,t.hasSymbolVisual=!1,t.defaultSymbol=\"circle\",t.visualStyleAccessPath=\"itemStyle\",t.visualDrawType=\"fill\"}(),e}(Xc);function gf(t){var e=t.name;Dr(t)||(t.name=function(t){var e=t.getRawData(),n=e.mapDimensionsAll(\"seriesName\"),i=[];return P(n,(function(t){var n=e.getDimensionInfo(t);n.displayName&&i.push(n.displayName)})),i.join(\" \")}(t)||e)}function yf(t){return t.model.getRawData().count()}function vf(t){var e=t.model;return e.setData(e.getRawData().cloneShallow()),mf}function mf(t,e){e.outputData&&t.end>e.outputData.count()&&e.model.getRawData().cloneShallow(e.outputData)}function _f(t,e){P(r(t.CHANGABLE_METHODS,t.DOWNSAMPLE_METHODS),(function(n){t.wrapMethod(n,B(xf,e))}))}function xf(t,e){var n=bf(t);return n&&n.setOutputEnd((e||this).count()),e}function bf(t){var e=(t.ecModel||{}).scheduler,n=e&&e.getPipeline(t.uid);if(n){var i=n.currentTask;if(i){var r=i.agentStubMap;r&&(i=r.get(t.uid))}return i}}L(ff,Td),L(ff,_p),Yr(ff,Xc);var wf=function(){function t(){this.group=new Ei,this.uid=Nh(\"viewComponent\")}return t.prototype.init=function(t,e){},t.prototype.render=function(t,e,n,i){},t.prototype.dispose=function(t,e){},t.prototype.updateView=function(t,e,n,i){},t.prototype.updateLayout=function(t,e,n,i){},t.prototype.updateVisual=function(t,e,n,i){},t.prototype.blurSeries=function(t,e){},t}();function Sf(){var t=kr();return function(e){var n=t(e),i=e.pipelineContext,r=!!n.large,o=!!n.progressiveRender,a=n.large=!(!i||!i.large),s=n.progressiveRender=!(!i||!i.progressiveRender);return!(r===a&&o===s)&&\"reset\"}}Ur(wf),Kr(wf);var Mf=kr(),If=Sf(),Tf=function(){function t(){this.group=new Ei,this.uid=Nh(\"viewChart\"),this.renderTask=Dd({plan:Af,reset:Lf}),this.renderTask.context={view:this}}return t.prototype.init=function(t,e){},t.prototype.render=function(t,e,n,i){},t.prototype.highlight=function(t,e,n,i){Df(t.getData(),i,\"emphasis\")},t.prototype.downplay=function(t,e,n,i){Df(t.getData(),i,\"normal\")},t.prototype.remove=function(t,e){this.group.removeAll()},t.prototype.dispose=function(t,e){},t.prototype.updateView=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.updateLayout=function(t,e,n,i){this.render(t,e,n,i)},t.prototype.updateVisual=function(t,e,n,i){this.render(t,e,n,i)},t.markUpdateMethod=function(t,e){Mf(t).updateMethod=e},t.protoInitialize=void(t.prototype.type=\"chart\"),t}();function Cf(t,e,n){t&&(\"emphasis\"===e?js:qs)(t,n)}function Df(t,e,n){var i=Lr(t,e),r=e&&null!=e.highlightKey?function(t){var e=bs[t];return null==e&&xs<=32&&(e=bs[t]=xs++),e}(e.highlightKey):null;null!=i?P(xr(i),(function(e){Cf(t.getItemGraphicEl(e),n,r)})):t.eachItemGraphicEl((function(t){Cf(t,n,r)}))}function Af(t){return If(t.model)}function Lf(t){var e=t.model,n=t.ecModel,i=t.api,r=t.payload,o=e.pipelineContext.progressiveRender,a=t.view,s=r&&Mf(r).updateMethod,l=o?\"incrementalPrepareRender\":s&&a[s]?s:\"render\";return\"render\"!==l&&a[l](e,n,i,r),kf[l]}Ur(Tf),Kr(Tf);var kf={incrementalPrepareRender:{progress:function(t,e){e.view.incrementalRender(t,e.model,e.ecModel,e.api,e.payload)}},render:{forceFirstProgress:!0,progress:function(t,e){e.view.render(e.model,e.ecModel,e.api,e.payload)}}},Pf=\"\\0__throttleOriginMethod\",Of=\"\\0__throttleRate\",Rf=\"\\0__throttleType\";function Nf(t,e,n){var i,r,o,a,s,l=0,u=0,h=null;function c(){u=(new Date).getTime(),h=null,t.apply(o,a||[])}e=e||0;var p=function(){for(var t=[],p=0;p<arguments.length;p++)t[p]=arguments[p];i=(new Date).getTime(),o=this,a=t;var d=s||e,f=s||n;s=null,r=i-(f?l:u)-d,clearTimeout(h),f?h=setTimeout(c,d):r>=0?c():h=setTimeout(c,-r),l=i};return p.clear=function(){h&&(clearTimeout(h),h=null)},p.debounceNextCall=function(t){s=t},p}function zf(t,e,n,i){var r=t[e];if(r){var o=r[Pf]||r,a=r[Rf];if(r[Of]!==n||a!==i){if(null==n||!i)return t[e]=o;(r=t[e]=Nf(o,n,\"debounce\"===i))[Pf]=o,r[Rf]=i,r[Of]=n}return r}}var Ef=kr(),Vf={itemStyle:$r(Lh,!0),lineStyle:$r(Ch,!0)},Bf={lineStyle:\"stroke\",itemStyle:\"fill\"};function Ff(t,e){var n=t.visualStyleMapper||Vf[e];return n||(console.warn(\"Unkown style type '\"+e+\"'.\"),Vf.itemStyle)}function Gf(t,e){var n=t.visualDrawType||Bf[e];return n||(console.warn(\"Unkown style type '\"+e+\"'.\"),\"fill\")}var Hf={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var n=t.getData(),i=t.visualStyleAccessPath||\"itemStyle\",r=t.getModel(i),o=Ff(t,i)(r),a=r.getShallow(\"decal\");a&&(n.setVisual(\"decal\",a),a.dirty=!0);var s=Gf(t,i),l=o[s],u=G(l)?l:null,h=\"auto\"===o.fill||\"auto\"===o.stroke;if(!o[s]||u||h){var c=t.getColorFromPalette(t.name,null,e.getSeriesCount());o[s]||(o[s]=c,n.setVisual(\"colorFromPalette\",!0)),o.fill=\"auto\"===o.fill||\"function\"==typeof o.fill?c:o.fill,o.stroke=\"auto\"===o.stroke||\"function\"==typeof o.stroke?c:o.stroke}if(n.setVisual(\"style\",o),n.setVisual(\"drawType\",s),!e.isSeriesFiltered(t)&&u)return n.setVisual(\"colorFromPalette\",!1),{dataEach:function(e,n){var i=t.getDataParams(n),r=I({},o);r[s]=u(i),e.setItemVisual(n,\"style\",r)}}}},Wf=new Oh,Uf={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){if(!t.ignoreStyleOnData&&!e.isSeriesFiltered(t)){var n=t.getData(),i=t.visualStyleAccessPath||\"itemStyle\",r=Ff(t,i),o=n.getVisual(\"drawType\");return{dataEach:n.hasItemOption?function(t,e){var n=t.getRawDataItem(e);if(n&&n[i]){Wf.option=n[i];var a=r(Wf);I(t.ensureUniqueItemVisual(e,\"style\"),a),Wf.option.decal&&(t.setItemVisual(e,\"decal\",Wf.option.decal),Wf.option.decal.dirty=!0),o in a&&t.setItemVisual(e,\"colorFromPalette\",!1)}}:null}}}},Xf={performRawSeries:!0,overallReset:function(t){var e=ht();t.eachSeries((function(t){if(t.useColorPaletteOnData){var n=e.get(t.type);n||(n={},e.set(t.type,n)),Ef(t).scope=n}})),t.eachSeries((function(e){if(e.useColorPaletteOnData&&!t.isSeriesFiltered(e)){var n=e.getRawData(),i={},r=e.getData(),o=Ef(e).scope,a=e.visualStyleAccessPath||\"itemStyle\",s=Gf(e,a);r.each((function(t){var e=r.getRawIndex(t);i[e]=t})),n.each((function(t){var a=i[t];if(r.getItemVisual(a,\"colorFromPalette\")){var l=r.ensureUniqueItemVisual(a,\"style\"),u=n.getName(t)||t+\"\",h=n.count();l[s]=e.getColorFromPalette(u,o,h)}}))}}))}},Yf=Math.PI;var Zf=function(){function t(t,e,n,i){this._stageTaskMap=ht(),this.ecInstance=t,this.api=e,n=this._dataProcessorHandlers=n.slice(),i=this._visualHandlers=i.slice(),this._allHandlers=n.concat(i)}return t.prototype.restoreData=function(t,e){t.restoreData(e),this._stageTaskMap.each((function(t){var e=t.overallTask;e&&e.dirty()}))},t.prototype.getPerformArgs=function(t,e){if(t.__pipeline){var n=this._pipelineMap.get(t.__pipeline.id),i=n.context,r=!e&&n.progressiveEnabled&&(!i||i.progressiveRender)&&t.__idxInPipeline>n.blockIndex?n.step:null,o=i&&i.modDataCount;return{step:r,modBy:null!=o?Math.ceil(o/r):null,modDataCount:o}}},t.prototype.getPipeline=function(t){return this._pipelineMap.get(t)},t.prototype.updateStreamModes=function(t,e){var n=this._pipelineMap.get(t.uid),i=t.getData().count(),r=n.progressiveEnabled&&e.incrementalPrepareRender&&i>=n.threshold,o=t.get(\"large\")&&i>=t.get(\"largeThreshold\"),a=\"mod\"===t.get(\"progressiveChunkMode\")?i:null;t.pipelineContext=n.context={progressiveRender:r,modDataCount:a,large:o}},t.prototype.restorePipelines=function(t){var e=this,n=e._pipelineMap=ht();t.eachSeries((function(t){var i=t.getProgressive(),r=t.uid;n.set(r,{id:r,head:null,tail:null,threshold:t.getProgressiveThreshold(),progressiveEnabled:i&&!(t.preventIncremental&&t.preventIncremental()),blockIndex:-1,step:Math.round(i||700),count:0}),e._pipe(t,t.dataTask)}))},t.prototype.prepareStageTasks=function(){var t=this._stageTaskMap,e=this.api.getModel(),n=this.api;P(this._allHandlers,(function(i){var r=t.get(i.uid)||t.set(i.uid,{}),o=\"\";rt(!(i.reset&&i.overallReset),o),i.reset&&this._createSeriesStageTask(i,r,e,n),i.overallReset&&this._createOverallStageTask(i,r,e,n)}),this)},t.prototype.prepareView=function(t,e,n,i){var r=t.renderTask,o=r.context;o.model=e,o.ecModel=n,o.api=i,r.__block=!t.incrementalPrepareRender,this._pipe(e,r)},t.prototype.performDataProcessorTasks=function(t,e){this._performStageTasks(this._dataProcessorHandlers,t,e,{block:!0})},t.prototype.performVisualTasks=function(t,e,n){this._performStageTasks(this._visualHandlers,t,e,n)},t.prototype._performStageTasks=function(t,e,n,i){i=i||{};var r=!1,o=this;function a(t,e){return t.setDirty&&(!t.dirtyMap||t.dirtyMap.get(e.__pipeline.id))}P(t,(function(t,s){if(!i.visualType||i.visualType===t.visualType){var l=o._stageTaskMap.get(t.uid),u=l.seriesTaskMap,h=l.overallTask;if(h){var c,p=h.agentStubMap;p.each((function(t){a(i,t)&&(t.dirty(),c=!0)})),c&&h.dirty(),o.updatePayload(h,n);var d=o.getPerformArgs(h,i.block);p.each((function(t){t.perform(d)})),h.perform(d)&&(r=!0)}else u&&u.each((function(s,l){a(i,s)&&s.dirty();var u=o.getPerformArgs(s,i.block);u.skip=!t.performRawSeries&&e.isSeriesFiltered(s.context.model),o.updatePayload(s,n),s.perform(u)&&(r=!0)}))}})),this.unfinished=r||this.unfinished},t.prototype.performSeriesTasks=function(t){var e;t.eachSeries((function(t){e=t.dataTask.perform()||e})),this.unfinished=e||this.unfinished},t.prototype.plan=function(){this._pipelineMap.each((function(t){var e=t.tail;do{if(e.__block){t.blockIndex=e.__idxInPipeline;break}e=e.getUpstream()}while(e)}))},t.prototype.updatePayload=function(t,e){\"remain\"!==e&&(t.context.payload=e)},t.prototype._createSeriesStageTask=function(t,e,n,i){var r=this,o=e.seriesTaskMap,a=e.seriesTaskMap=ht(),s=t.seriesType,l=t.getTargetSeries;function u(e){var s=e.uid,l=a.set(s,o&&o.get(s)||Dd({plan:Jf,reset:Qf,count:ng}));l.context={model:e,ecModel:n,api:i,useClearVisual:t.isVisual&&!t.isLayout,plan:t.plan,reset:t.reset,scheduler:r},r._pipe(e,l)}t.createOnAllSeries?n.eachRawSeries(u):s?n.eachRawSeriesByType(s,u):l&&l(n,i).each(u)},t.prototype._createOverallStageTask=function(t,e,n,i){var r=this,o=e.overallTask=e.overallTask||Dd({reset:jf});o.context={ecModel:n,api:i,overallReset:t.overallReset,scheduler:r};var a=o.agentStubMap,s=o.agentStubMap=ht(),l=t.seriesType,u=t.getTargetSeries,h=!0,c=!1,p=\"\";function d(t){var e=t.uid,n=s.set(e,a&&a.get(e)||(c=!0,Dd({reset:qf,onDirty:$f})));n.context={model:t,overallProgress:h},n.agent=o,n.__block=h,r._pipe(t,n)}rt(!t.createOnAllSeries,p),l?n.eachRawSeriesByType(l,d):u?u(n,i).each(d):(h=!1,P(n.getSeries(),d)),c&&o.dirty()},t.prototype._pipe=function(t,e){var n=t.uid,i=this._pipelineMap.get(n);!i.head&&(i.head=e),i.tail&&i.tail.pipe(e),i.tail=e,e.__idxInPipeline=i.count++,e.__pipeline=i},t.wrapStageHandler=function(t,e){return G(t)&&(t={overallReset:t,seriesType:ig(t)}),t.uid=Nh(\"stageHandler\"),e&&(t.visualType=e),t},t}();function jf(t){t.overallReset(t.ecModel,t.api,t.payload)}function qf(t){return t.overallProgress&&Kf}function Kf(){this.agent.dirty(),this.getDownstream().dirty()}function $f(){this.agent&&this.agent.dirty()}function Jf(t){return t.plan?t.plan(t.model,t.ecModel,t.api,t.payload):null}function Qf(t){t.useClearVisual&&t.data.clearAllVisual();var e=t.resetDefines=xr(t.reset(t.model,t.ecModel,t.api,t.payload));return e.length>1?O(e,(function(t,e){return eg(e)})):tg}var tg=eg(0);function eg(t){return function(e,n){var i=n.data,r=n.resetDefines[t];if(r&&r.dataEach)for(var o=e.start;o<e.end;o++)r.dataEach(i,o);else r&&r.progress&&r.progress(e,i)}}function ng(t){return t.data.count()}function ig(t){rg=null;try{t(og,ag)}catch(t){}return rg}var rg,og={},ag={};function sg(t,e){for(var n in e.prototype)t[n]=ft}sg(og,wp),sg(ag,Cp),og.eachSeriesByType=og.eachRawSeriesByType=function(t){rg=t},og.eachComponent=function(t){\"series\"===t.mainType&&t.subType&&(rg=t.subType)};var lg=[\"#37A2DA\",\"#32C5E9\",\"#67E0E3\",\"#9FE6B8\",\"#FFDB5C\",\"#ff9f7f\",\"#fb7293\",\"#E062AE\",\"#E690D1\",\"#e7bcf3\",\"#9d96f5\",\"#8378EA\",\"#96BFFF\"],ug={color:lg,colorLayer:[[\"#37A2DA\",\"#ffd85c\",\"#fd7b5f\"],[\"#37A2DA\",\"#67E0E3\",\"#FFDB5C\",\"#ff9f7f\",\"#E062AE\",\"#9d96f5\"],[\"#37A2DA\",\"#32C5E9\",\"#9FE6B8\",\"#FFDB5C\",\"#ff9f7f\",\"#fb7293\",\"#e7bcf3\",\"#8378EA\",\"#96BFFF\"],lg]},hg=\"#B9B8CE\",cg=\"#100C2A\",pg=function(){return{axisLine:{lineStyle:{color:hg}},splitLine:{lineStyle:{color:\"#484753\"}},splitArea:{areaStyle:{color:[\"rgba(255,255,255,0.02)\",\"rgba(255,255,255,0.05)\"]}},minorSplitLine:{lineStyle:{color:\"#20203B\"}}}},dg=[\"#4992ff\",\"#7cffb2\",\"#fddd60\",\"#ff6e76\",\"#58d9f9\",\"#05c091\",\"#ff8a45\",\"#8d48e3\",\"#dd79ff\"],fg={darkMode:!0,color:dg,backgroundColor:cg,axisPointer:{lineStyle:{color:\"#817f91\"},crossStyle:{color:\"#817f91\"},label:{color:\"#fff\"}},legend:{textStyle:{color:hg}},textStyle:{color:hg},title:{textStyle:{color:\"#EEF1FA\"},subtextStyle:{color:\"#B9B8CE\"}},toolbox:{iconStyle:{borderColor:hg}},dataZoom:{borderColor:\"#71708A\",textStyle:{color:hg},brushStyle:{color:\"rgba(135,163,206,0.3)\"},handleStyle:{color:\"#353450\",borderColor:\"#C5CBE3\"},moveHandleStyle:{color:\"#B0B6C3\",opacity:.3},fillerColor:\"rgba(135,163,206,0.2)\",emphasis:{handleStyle:{borderColor:\"#91B7F2\",color:\"#4D587D\"},moveHandleStyle:{color:\"#636D9A\",opacity:.7}},dataBackground:{lineStyle:{color:\"#71708A\",width:1},areaStyle:{color:\"#71708A\"}},selectedDataBackground:{lineStyle:{color:\"#87A3CE\"},areaStyle:{color:\"#87A3CE\"}}},visualMap:{textStyle:{color:hg}},timeline:{lineStyle:{color:hg},label:{color:hg},controlStyle:{color:hg,borderColor:hg}},calendar:{itemStyle:{color:cg},dayLabel:{color:hg},monthLabel:{color:hg},yearLabel:{color:hg}},timeAxis:pg(),logAxis:pg(),valueAxis:pg(),categoryAxis:pg(),line:{symbol:\"circle\"},graph:{color:dg},gauge:{title:{color:hg},axisLine:{lineStyle:{color:[[1,\"rgba(207,212,219,0.2)\"]]}},axisLabel:{color:hg},detail:{color:\"#EEF1FA\"}},candlestick:{itemStyle:{color:\"#f64e56\",color0:\"#54ea92\",borderColor:\"#f64e56\",borderColor0:\"#54ea92\"}}};fg.categoryAxis.splitLine.show=!1;var gg=function(){function t(){}return t.prototype.normalizeQuery=function(t){var e={},n={},i={};if(H(t)){var r=Wr(t);e.mainType=r.main||null,e.subType=r.sub||null}else{var o=[\"Index\",\"Name\",\"Id\"],a={name:1,dataIndex:1,dataType:1};P(t,(function(t,r){for(var s=!1,l=0;l<o.length;l++){var u=o[l],h=r.lastIndexOf(u);if(h>0&&h===r.length-u.length){var c=r.slice(0,h);\"data\"!==c&&(e.mainType=c,e[u.toLowerCase()]=t,s=!0)}}a.hasOwnProperty(r)&&(n[r]=t,s=!0),s||(i[r]=t)}))}return{cptQuery:e,dataQuery:n,otherQuery:i}},t.prototype.filter=function(t,e){var n=this.eventInfo;if(!n)return!0;var i=n.targetEl,r=n.packedEvent,o=n.model,a=n.view;if(!o||!a)return!0;var s=e.cptQuery,l=e.dataQuery;return u(s,o,\"mainType\")&&u(s,o,\"subType\")&&u(s,o,\"index\",\"componentIndex\")&&u(s,o,\"name\")&&u(s,o,\"id\")&&u(l,r,\"name\")&&u(l,r,\"dataIndex\")&&u(l,r,\"dataType\")&&(!a.filterForExposedEvent||a.filterForExposedEvent(t,e.otherQuery,i,r));function u(t,e,n,i){return null==t[n]||e[i||n]===t[n]}},t.prototype.afterTrigger=function(){this.eventInfo=null},t}(),yg={createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){var n=t.getData();if(t.legendIcon&&n.setVisual(\"legendIcon\",t.legendIcon),t.hasSymbolVisual){var i=t.get(\"symbol\"),r=t.get(\"symbolSize\"),o=t.get(\"symbolKeepAspect\"),a=t.get(\"symbolRotate\"),s=t.get(\"symbolOffset\"),l=G(i),u=G(r),h=G(a),c=G(s),p=l||u||h||c,d=!l&&i?i:t.defaultSymbol,f=u?null:r,g=h?null:a,y=c?null:s;if(n.setVisual({legendIcon:t.legendIcon||d,symbol:d,symbolSize:f,symbolKeepAspect:o,symbolRotate:g,symbolOffset:y}),!e.isSeriesFiltered(t))return{dataEach:p?function(e,n){var o=t.getRawValue(n),p=t.getDataParams(n);l&&e.setItemVisual(n,\"symbol\",i(o,p)),u&&e.setItemVisual(n,\"symbolSize\",r(o,p)),h&&e.setItemVisual(n,\"symbolRotate\",a(o,p)),c&&e.setItemVisual(n,\"symbolOffset\",s(o,p))}:null}}}};function vg(t,e,n){switch(n){case\"color\":return t.getItemVisual(e,\"style\")[t.getVisual(\"drawType\")];case\"opacity\":return t.getItemVisual(e,\"style\").opacity;case\"symbol\":case\"symbolSize\":case\"liftZ\":return t.getItemVisual(e,n);default:0}}function mg(t,e){switch(e){case\"color\":return t.getVisual(\"style\")[t.getVisual(\"drawType\")];case\"opacity\":return t.getVisual(\"style\").opacity;case\"symbol\":case\"symbolSize\":case\"liftZ\":return t.getVisual(e);default:0}}function _g(t,e,n,i){switch(n){case\"color\":t.ensureUniqueItemVisual(e,\"style\")[t.getVisual(\"drawType\")]=i,t.setItemVisual(e,\"colorFromPalette\",!1);break;case\"opacity\":t.ensureUniqueItemVisual(e,\"style\").opacity=i;break;case\"symbol\":case\"symbolSize\":case\"liftZ\":t.setItemVisual(e,n,i);break;default:0}}var xg=2*Math.PI,bg=La.CMD,wg=[\"top\",\"right\",\"bottom\",\"left\"];function Sg(t,e,n,i,r){var o=n.width,a=n.height;switch(t){case\"top\":i.set(n.x+o/2,n.y-e),r.set(0,-1);break;case\"bottom\":i.set(n.x+o/2,n.y+a+e),r.set(0,1);break;case\"left\":i.set(n.x-e,n.y+a/2),r.set(-1,0);break;case\"right\":i.set(n.x+o+e,n.y+a/2),r.set(1,0)}}function Mg(t,e,n,i,r,o,a,s,l){a-=t,s-=e;var u=Math.sqrt(a*a+s*s),h=(a/=u)*n+t,c=(s/=u)*n+e;if(Math.abs(i-r)%xg<1e-4)return l[0]=h,l[1]=c,u-n;if(o){var p=i;i=Na(r),r=Na(p)}else i=Na(i),r=Na(r);i>r&&(r+=xg);var d=Math.atan2(s,a);if(d<0&&(d+=xg),d>=i&&d<=r||d+xg>=i&&d+xg<=r)return l[0]=h,l[1]=c,u-n;var f=n*Math.cos(i)+t,g=n*Math.sin(i)+e,y=n*Math.cos(r)+t,v=n*Math.sin(r)+e,m=(f-a)*(f-a)+(g-s)*(g-s),_=(y-a)*(y-a)+(v-s)*(v-s);return m<_?(l[0]=f,l[1]=g,Math.sqrt(m)):(l[0]=y,l[1]=v,Math.sqrt(_))}function Ig(t,e,n,i,r,o,a,s){var l=r-t,u=o-e,h=n-t,c=i-e,p=Math.sqrt(h*h+c*c),d=(l*(h/=p)+u*(c/=p))/p;s&&(d=Math.min(Math.max(d,0),1)),d*=p;var f=a[0]=t+d*h,g=a[1]=e+d*c;return Math.sqrt((f-r)*(f-r)+(g-o)*(g-o))}function Tg(t,e,n,i,r,o,a){n<0&&(t+=n,n=-n),i<0&&(e+=i,i=-i);var s=t+n,l=e+i,u=a[0]=Math.min(Math.max(r,t),s),h=a[1]=Math.min(Math.max(o,e),l);return Math.sqrt((u-r)*(u-r)+(h-o)*(h-o))}var Cg=[];function Dg(t,e,n){var i=Tg(e.x,e.y,e.width,e.height,t.x,t.y,Cg);return n.set(Cg[0],Cg[1]),i}function Ag(t,e,n){for(var i,r,o=0,a=0,s=0,l=0,u=1/0,h=e.data,c=t.x,p=t.y,d=0;d<h.length;){var f=h[d++];1===d&&(s=o=h[d],l=a=h[d+1]);var g=u;switch(f){case bg.M:o=s=h[d++],a=l=h[d++];break;case bg.L:g=Ig(o,a,h[d],h[d+1],c,p,Cg,!0),o=h[d++],a=h[d++];break;case bg.C:g=Ho(o,a,h[d++],h[d++],h[d++],h[d++],h[d],h[d+1],c,p,Cg),o=h[d++],a=h[d++];break;case bg.Q:g=jo(o,a,h[d++],h[d++],h[d],h[d+1],c,p,Cg),o=h[d++],a=h[d++];break;case bg.A:var y=h[d++],v=h[d++],m=h[d++],_=h[d++],x=h[d++],b=h[d++];d+=1;var w=!!(1-h[d++]);i=Math.cos(x)*m+y,r=Math.sin(x)*_+v,d<=1&&(s=i,l=r),g=Mg(y,v,_,x,x+b,w,(c-y)*_/m+y,p,Cg),o=Math.cos(x+b)*m+y,a=Math.sin(x+b)*_+v;break;case bg.R:g=Tg(s=o=h[d++],l=a=h[d++],h[d++],h[d++],c,p,Cg);break;case bg.Z:g=Ig(o,a,s,l,c,p,Cg,!0),o=s,a=l}g<u&&(u=g,n.set(Cg[0],Cg[1]))}return u}var Lg=new ai,kg=new ai,Pg=new ai,Og=new ai,Rg=new ai;function Ng(t,e){if(t){var n=t.getTextGuideLine(),i=t.getTextContent();if(i&&n){var r=t.textGuideLineConfig||{},o=[[0,0],[0,0],[0,0]],a=r.candidates||wg,s=i.getBoundingRect().clone();s.applyTransform(i.getComputedTransform());var l=1/0,u=r.anchor,h=t.getComputedTransform(),c=h&&Zn([],h),p=e.get(\"length2\")||0;u&&Pg.copy(u);for(var d=0;d<a.length;d++){Sg(a[d],0,s,Lg,Og),ai.scaleAndAdd(kg,Lg,Og,p),kg.transform(c);var f=t.getBoundingRect(),g=u?u.distance(kg):t instanceof Ka?Ag(kg,t.path,Pg):Dg(kg,f,Pg);g<l&&(l=g,kg.transform(h),Pg.transform(h),Pg.toArray(o[0]),kg.toArray(o[1]),Lg.toArray(o[2]))}Vg(o,e.get(\"minTurnAngle\")),n.setShape({points:o})}}}var zg=[],Eg=new ai;function Vg(t,e){if(e<=180&&e>0){e=e/180*Math.PI,Lg.fromArray(t[0]),kg.fromArray(t[1]),Pg.fromArray(t[2]),ai.sub(Og,Lg,kg),ai.sub(Rg,Pg,kg);var n=Og.len(),i=Rg.len();if(!(n<.001||i<.001)){Og.scale(1/n),Rg.scale(1/i);var r=Og.dot(Rg);if(Math.cos(e)<r){var o=Ig(kg.x,kg.y,Pg.x,Pg.y,Lg.x,Lg.y,zg,!1);Eg.fromArray(zg),Eg.scaleAndAdd(Rg,o/Math.tan(Math.PI-e));var a=Pg.x!==kg.x?(Eg.x-kg.x)/(Pg.x-kg.x):(Eg.y-kg.y)/(Pg.y-kg.y);if(isNaN(a))return;a<0?ai.copy(Eg,kg):a>1&&ai.copy(Eg,Pg),Eg.toArray(t[1])}}}}function Bg(t,e,n){if(n<=180&&n>0){n=n/180*Math.PI,Lg.fromArray(t[0]),kg.fromArray(t[1]),Pg.fromArray(t[2]),ai.sub(Og,kg,Lg),ai.sub(Rg,Pg,kg);var i=Og.len(),r=Rg.len();if(!(i<.001||r<.001))if(Og.scale(1/i),Rg.scale(1/r),Og.dot(e)<Math.cos(n)){var o=Ig(kg.x,kg.y,Pg.x,Pg.y,Lg.x,Lg.y,zg,!1);Eg.fromArray(zg);var a=Math.PI/2,s=a+Math.acos(Rg.dot(e))-n;if(s>=a)ai.copy(Eg,Pg);else{Eg.scaleAndAdd(Rg,o/Math.tan(Math.PI/2-s));var l=Pg.x!==kg.x?(Eg.x-kg.x)/(Pg.x-kg.x):(Eg.y-kg.y)/(Pg.y-kg.y);if(isNaN(l))return;l<0?ai.copy(Eg,kg):l>1&&ai.copy(Eg,Pg)}Eg.toArray(t[1])}}}function Fg(t,e,n,i){var r=\"normal\"===n,o=r?t:t.ensureState(n);o.ignore=e;var a=i.get(\"smooth\");a&&!0===a&&(a=.3),o.shape=o.shape||{},a>0&&(o.shape.smooth=a);var s=i.getModel(\"lineStyle\").getLineStyle();r?t.useStyle(s):o.style=s}function Gg(t,e){var n=e.smooth,i=e.points;if(i)if(t.moveTo(i[0][0],i[0][1]),n>0&&i.length>=3){var r=Lt(i[0],i[1]),o=Lt(i[1],i[2]);if(!r||!o)return t.lineTo(i[1][0],i[1][1]),void t.lineTo(i[2][0],i[2][1]);var a=Math.min(r,o)*n,s=Ot([],i[1],i[0],a/r),l=Ot([],i[1],i[2],a/o),u=Ot([],s,l,.5);t.bezierCurveTo(s[0],s[1],s[0],s[1],u[0],u[1]),t.bezierCurveTo(l[0],l[1],l[0],l[1],i[2][0],i[2][1])}else for(var h=1;h<i.length;h++)t.lineTo(i[h][0],i[h][1])}function Hg(t,e,n){var i=t.getTextGuideLine(),r=t.getTextContent();if(r){for(var o=e.normal,a=o.get(\"show\"),s=r.ignore,l=0;l<Ms.length;l++){var u=Ms[l],h=e[u],c=\"normal\"===u;if(h){var p=h.get(\"show\");if((c?s:tt(r.states[u]&&r.states[u].ignore,s))||!tt(p,a)){var d=c?i:i&&i.states.normal;d&&(d.ignore=!0);continue}i||(i=new au,t.setTextGuideLine(i),c||!s&&a||Fg(i,!0,\"normal\",e.normal),t.stateProxy&&(i.stateProxy=t.stateProxy)),Fg(i,!1,u,h)}}if(i){T(i.style,n),i.style.fill=null;var f=o.get(\"showAbove\");(t.textGuideLineConfig=t.textGuideLineConfig||{}).showAbove=f||!1,i.buildPath=Gg}}else i&&t.removeTextGuideLine()}function Wg(t,e){e=e||\"labelLine\";for(var n={normal:t.getModel(e)},i=0;i<Ss.length;i++){var r=Ss[i];n[r]=t.getModel([r,e])}return n}function Ug(t,e,n,i,r,o){var a=t.length;if(!(a<2)){t.sort((function(t,n){return t.rect[e]-n.rect[e]}));for(var s,l=0,u=!1,h=0,c=0;c<a;c++){var p=t[c],d=p.rect;(s=d[e]-l)<0&&(d[e]-=s,p.label[e]-=s,u=!0),h+=Math.max(-s,0),l=d[e]+d[n]}h>0&&o&&x(-h/a,0,a);var f,g,y=t[0],v=t[a-1];return m(),f<0&&b(-f,.8),g<0&&b(g,.8),m(),_(f,g,1),_(g,f,-1),m(),f<0&&w(-f),g<0&&w(g),u}function m(){f=y.rect[e]-i,g=r-v.rect[e]-v.rect[n]}function _(t,e,n){if(t<0){var i=Math.min(e,-t);if(i>0){x(i*n,0,a);var r=i+t;r<0&&b(-r*n,1)}else b(-t*n,1)}}function x(n,i,r){0!==n&&(u=!0);for(var o=i;o<r;o++){var a=t[o];a.rect[e]+=n,a.label[e]+=n}}function b(i,r){for(var o=[],s=0,l=1;l<a;l++){var u=t[l-1].rect,h=Math.max(t[l].rect[e]-u[e]-u[n],0);o.push(h),s+=h}if(s){var c=Math.min(Math.abs(i)/s,r);if(i>0)for(l=0;l<a-1;l++){x(o[l]*c,0,l+1)}else for(l=a-1;l>0;l--){x(-(o[l-1]*c),l,a)}}}function w(t){var e=t<0?-1:1;t=Math.abs(t);for(var n=Math.ceil(t/(a-1)),i=0;i<a-1;i++)if(e>0?x(n,0,i+1):x(-n,a-i-1,a),(t-=n)<=0)return}}function Xg(t,e,n,i){return Ug(t,\"y\",\"height\",e,n,i)}function Yg(t){if(t){for(var e=[],n=0;n<t.length;n++)e.push(t[n].slice());return e}}function Zg(t,e){var n=t.label,i=e&&e.getTextGuideLine();return{dataIndex:t.dataIndex,dataType:t.dataType,seriesIndex:t.seriesModel.seriesIndex,text:t.label.style.text,rect:t.hostRect,labelRect:t.rect,align:n.style.align,verticalAlign:n.style.verticalAlign,labelLinePoints:Yg(i&&i.shape.points)}}var jg=[\"align\",\"verticalAlign\",\"width\",\"height\",\"fontSize\"],qg=new oi,Kg=kr(),$g=kr();function Jg(t,e,n){for(var i=0;i<n.length;i++){var r=n[i];null!=e[r]&&(t[r]=e[r])}}var Qg=[\"x\",\"y\",\"rotation\"],ty=function(){function t(){this._labelList=[],this._chartViewList=[]}return t.prototype.clearLabels=function(){this._labelList=[],this._chartViewList=[]},t.prototype._addLabel=function(t,e,n,i,r){var o=i.style,a=i.__hostTarget.textConfig||{},s=i.getComputedTransform(),l=i.getBoundingRect().plain();gi.applyTransform(l,l,s),s?qg.setLocalTransform(s):(qg.x=qg.y=qg.rotation=qg.originX=qg.originY=0,qg.scaleX=qg.scaleY=1);var u,h=i.__hostTarget;if(h){u=h.getBoundingRect().plain();var c=h.getComputedTransform();gi.applyTransform(u,u,c)}var p=u&&h.getTextGuideLine();this._labelList.push({label:i,labelLine:p,seriesModel:n,dataIndex:t,dataType:e,layoutOption:r,computedLayoutOption:null,rect:l,hostRect:u,priority:u?u.width*u.height:0,defaultAttr:{ignore:i.ignore,labelGuideIgnore:p&&p.ignore,x:qg.x,y:qg.y,scaleX:qg.scaleX,scaleY:qg.scaleY,rotation:qg.rotation,style:{x:o.x,y:o.y,align:o.align,verticalAlign:o.verticalAlign,width:o.width,height:o.height,fontSize:o.fontSize},cursor:i.cursor,attachedPos:a.position,attachedRot:a.rotation}})},t.prototype.addLabelsOfSeries=function(t){var e=this;this._chartViewList.push(t);var n=t.__model,i=n.get(\"labelLayout\");(G(i)||E(i).length)&&t.group.traverse((function(t){if(t.ignore)return!0;var r=t.getTextContent(),o=_s(t);r&&!r.disableLabelLayout&&e._addLabel(o.dataIndex,o.dataType,n,r,i)}))},t.prototype.updateLayoutConfig=function(t){var e=t.getWidth(),n=t.getHeight();function i(t,e){return function(){Ng(t,e)}}for(var r=0;r<this._labelList.length;r++){var o=this._labelList[r],a=o.label,s=a.__hostTarget,l=o.defaultAttr,u=void 0;u=(u=\"function\"==typeof o.layoutOption?o.layoutOption(Zg(o,s)):o.layoutOption)||{},o.computedLayoutOption=u;var h=Math.PI/180;s&&s.setTextConfig({local:!1,position:null!=u.x||null!=u.y?null:l.attachedPos,rotation:null!=u.rotate?u.rotate*h:l.attachedRot,offset:[u.dx||0,u.dy||0]});var c=!1;if(null!=u.x?(a.x=Zi(u.x,e),a.setStyle(\"x\",0),c=!0):(a.x=l.x,a.setStyle(\"x\",l.style.x)),null!=u.y?(a.y=Zi(u.y,n),a.setStyle(\"y\",0),c=!0):(a.y=l.y,a.setStyle(\"y\",l.style.y)),u.labelLinePoints){var p=s.getTextGuideLine();p&&(p.setShape({points:u.labelLinePoints}),c=!1)}Kg(a).needsUpdateLabelLine=c,a.rotation=null!=u.rotate?u.rotate*h:l.rotation,a.scaleX=l.scaleX,a.scaleY=l.scaleY;for(var d=0;d<jg.length;d++){var f=jg[d];a.setStyle(f,null!=u[f]?u[f]:l.style[f])}if(u.draggable){if(a.draggable=!0,a.cursor=\"move\",s){var g=o.seriesModel;if(null!=o.dataIndex)g=o.seriesModel.getData(o.dataType).getItemModel(o.dataIndex);a.on(\"drag\",i(s,g.getModel(\"labelLine\")))}}else a.off(\"drag\"),a.cursor=l.cursor}},t.prototype.layout=function(t){var e,n=t.getWidth(),i=t.getHeight(),r=function(t){for(var e=[],n=0;n<t.length;n++){var i=t[n];if(!i.defaultAttr.ignore){var r=i.label,o=r.getComputedTransform(),a=r.getBoundingRect(),s=!o||o[1]<1e-5&&o[2]<1e-5,l=r.style.margin||0,u=a.clone();u.applyTransform(o),u.x-=l/2,u.y-=l/2,u.width+=l,u.height+=l;var h=s?new Mu(a,o):null;e.push({label:r,labelLine:i.labelLine,rect:u,localRect:a,obb:h,priority:i.priority,defaultAttr:i.defaultAttr,layoutOption:i.computedLayoutOption,axisAligned:s,transform:o})}}return e}(this._labelList),o=N(r,(function(t){return\"shiftX\"===t.layoutOption.moveOverlap})),a=N(r,(function(t){return\"shiftY\"===t.layoutOption.moveOverlap}));Ug(o,\"x\",\"width\",0,n,e),Xg(a,0,i),function(t){var e=[];t.sort((function(t,e){return e.priority-t.priority}));var n=new gi(0,0,0,0);function i(t){if(!t.ignore){var e=t.ensureState(\"emphasis\");null==e.ignore&&(e.ignore=!1)}t.ignore=!0}for(var r=0;r<t.length;r++){var o=t[r],a=o.axisAligned,s=o.localRect,l=o.transform,u=o.label,h=o.labelLine;n.copy(o.rect),n.width-=.1,n.height-=.1,n.x+=.05,n.y+=.05;for(var c=o.obb,p=!1,d=0;d<e.length;d++){var f=e[d];if(n.intersect(f.rect)){if(a&&f.axisAligned){p=!0;break}if(f.obb||(f.obb=new Mu(f.localRect,f.transform)),c||(c=new Mu(s,l)),c.intersect(f.obb)){p=!0;break}}}p?(i(u),h&&i(h)):(u.attr(\"ignore\",o.defaultAttr.ignore),h&&h.attr(\"ignore\",o.defaultAttr.labelGuideIgnore),e.push(o))}}(N(r,(function(t){return t.layoutOption.hideOverlap})))},t.prototype.processLabelsOverall=function(){var t=this;P(this._chartViewList,(function(e){var n=e.__model,i=e.ignoreLabelLineUpdate,r=n.isAnimationEnabled();e.group.traverse((function(e){if(e.ignore)return!0;var o=!i,a=e.getTextContent();!o&&a&&(o=Kg(a).needsUpdateLabelLine),o&&t._updateLabelLine(e,n),r&&t._animateLabels(e,n)}))}))},t.prototype._updateLabelLine=function(t,e){var n=t.getTextContent(),i=_s(t),r=i.dataIndex;if(n&&null!=r){var o=e.getData(i.dataType),a=o.getItemModel(r),s={},l=o.getItemVisual(r,\"style\"),u=o.getVisual(\"drawType\");s.stroke=l[u];var h=a.getModel(\"labelLine\");Hg(t,Wg(a),s),Ng(t,h)}},t.prototype._animateLabels=function(t,e){var n=t.getTextContent(),i=t.getTextGuideLine();if(n&&!n.ignore&&!n.invisible&&!t.disableLabelAnimation&&!Zu(t)){var r=(d=Kg(n)).oldLayout,o=_s(t),a=o.dataIndex,s={x:n.x,y:n.y,rotation:n.rotation},l=e.getData(o.dataType);if(r){n.attr(r);var u=t.prevStates;u&&(D(u,\"select\")>=0&&n.attr(d.oldLayoutSelect),D(u,\"emphasis\")>=0&&n.attr(d.oldLayoutEmphasis)),Hu(n,s,e,a)}else if(n.attr(s),!_h(n).valueAnimation){var h=tt(n.style.opacity,1);n.style.opacity=0,Wu(n,{style:{opacity:h}},e,a)}if(d.oldLayout=s,n.states.select){var c=d.oldLayoutSelect={};Jg(c,s,Qg),Jg(c,n.states.select,Qg)}if(n.states.emphasis){var p=d.oldLayoutEmphasis={};Jg(p,s,Qg),Jg(p,n.states.emphasis,Qg)}bh(n,a,l,e,e)}if(i&&!i.ignore&&!i.invisible){r=(d=$g(i)).oldLayout;var d,f={points:i.shape.points};r?(i.attr({shape:r}),Hu(i,{shape:f},e)):(i.setShape(f),i.style.strokePercent=0,Wu(i,{style:{strokePercent:1}},e)),d.oldLayout=f}},t}();function ey(t,e){function n(e,n){var i=[];return e.eachComponent({mainType:\"series\",subType:t,query:n},(function(t){i.push(t.seriesIndex)})),i}P([[t+\"ToggleSelect\",\"toggleSelect\"],[t+\"Select\",\"select\"],[t+\"UnSelect\",\"unselect\"]],(function(t){e(t[0],(function(e,i,r){e=I({},e),r.dispatchAction(I(e,{type:t[1],seriesIndex:n(i,e)}))}))}))}function ny(t,e,n,i,r){var o=t+e;n.isSilent(o)||i.eachComponent({mainType:\"series\",subType:\"pie\"},(function(t){for(var e=t.seriesIndex,i=r.selected,a=0;a<i.length;a++)if(i[a].seriesIndex===e){var s=t.getData(),l=Lr(s,r.fromActionPayload);n.trigger(o,{type:o,seriesId:t.id,name:F(l)?s.getName(l[0]):s.getName(l),selected:I({},t.option.selectedMap)})}}))}function iy(t,e,n){for(var i;t&&(!e(t)||(i=t,!n));)t=t.__hostTarget||t.parent;return i}var ry=Math.round(9*Math.random()),oy=function(){function t(){this._id=\"__ec_inner_\"+ry++}return t.prototype.get=function(t){return this._guard(t)[this._id]},t.prototype.set=function(t,e){var n=this._guard(t);return\"function\"==typeof Object.defineProperty?Object.defineProperty(n,this._id,{value:e,enumerable:!1,configurable:!0}):n[this._id]=e,this},t.prototype.delete=function(t){return!!this.has(t)&&(delete this._guard(t)[this._id],!0)},t.prototype.has=function(t){return!!this._guard(t)[this._id]},t.prototype._guard=function(t){if(t!==Object(t))throw TypeError(\"Value of WeakMap is not a non-null object.\");return t},t}(),ay=Ka.extend({type:\"triangle\",shape:{cx:0,cy:0,width:0,height:0},buildPath:function(t,e){var n=e.cx,i=e.cy,r=e.width/2,o=e.height/2;t.moveTo(n,i-o),t.lineTo(n+r,i+o),t.lineTo(n-r,i+o),t.closePath()}}),sy=Ka.extend({type:\"diamond\",shape:{cx:0,cy:0,width:0,height:0},buildPath:function(t,e){var n=e.cx,i=e.cy,r=e.width/2,o=e.height/2;t.moveTo(n,i-o),t.lineTo(n+r,i),t.lineTo(n,i+o),t.lineTo(n-r,i),t.closePath()}}),ly=Ka.extend({type:\"pin\",shape:{x:0,y:0,width:0,height:0},buildPath:function(t,e){var n=e.x,i=e.y,r=e.width/5*3,o=Math.max(r,e.height),a=r/2,s=a*a/(o-a),l=i-o+a+s,u=Math.asin(s/a),h=Math.cos(u)*a,c=Math.sin(u),p=Math.cos(u),d=.6*a,f=.7*a;t.moveTo(n-h,l+s),t.arc(n,l,a,Math.PI-u,2*Math.PI+u),t.bezierCurveTo(n+h-c*d,l+s+p*d,n,i-f,n,i),t.bezierCurveTo(n,i-f,n-h+c*d,l+s+p*d,n-h,l+s),t.closePath()}}),uy=Ka.extend({type:\"arrow\",shape:{x:0,y:0,width:0,height:0},buildPath:function(t,e){var n=e.height,i=e.width,r=e.x,o=e.y,a=i/3*2;t.moveTo(r,o),t.lineTo(r+a,o+n),t.lineTo(r,o+n/4*3),t.lineTo(r-a,o+n),t.lineTo(r,o),t.closePath()}}),hy={line:function(t,e,n,i,r){r.x1=t,r.y1=e+i/2,r.x2=t+n,r.y2=e+i/2},rect:function(t,e,n,i,r){r.x=t,r.y=e,r.width=n,r.height=i},roundRect:function(t,e,n,i,r){r.x=t,r.y=e,r.width=n,r.height=i,r.r=Math.min(n,i)/4},square:function(t,e,n,i,r){var o=Math.min(n,i);r.x=t,r.y=e,r.width=o,r.height=o},circle:function(t,e,n,i,r){r.cx=t+n/2,r.cy=e+i/2,r.r=Math.min(n,i)/2},diamond:function(t,e,n,i,r){r.cx=t+n/2,r.cy=e+i/2,r.width=n,r.height=i},pin:function(t,e,n,i,r){r.x=t+n/2,r.y=e+i/2,r.width=n,r.height=i},arrow:function(t,e,n,i,r){r.x=t+n/2,r.y=e+i/2,r.width=n,r.height=i},triangle:function(t,e,n,i,r){r.cx=t+n/2,r.cy=e+i/2,r.width=n,r.height=i}},cy={};P({line:uu,rect:ls,roundRect:ls,square:ls,circle:Nl,diamond:sy,pin:ly,arrow:uy,triangle:ay},(function(t,e){cy[e]=new t}));var py=Ka.extend({type:\"symbol\",shape:{symbolType:\"\",x:0,y:0,width:0,height:0},calculateTextPosition:function(t,e,n){var i=Ti(t,e,n),r=this.shape;return r&&\"pin\"===r.symbolType&&\"inside\"===e.position&&(i.y=n.y+.4*n.height),i},buildPath:function(t,e,n){var i=e.symbolType;if(\"none\"!==i){var r=cy[i];r||(r=cy[i=\"rect\"]),hy[i](e.x,e.y,e.width,e.height,r.shape),r.buildPath(t,r.shape,n)}}});function dy(t,e){if(\"image\"!==this.type){var n=this.style;this.__isEmptyBrush?(n.stroke=t,n.fill=e||\"#fff\",n.lineWidth=2):\"line\"===this.shape.symbolType?n.stroke=t:n.fill=t,this.markRedraw()}}function fy(t,e,n,i,r,o,a){var s,l=0===t.indexOf(\"empty\");return l&&(t=t.substr(5,1).toLowerCase()+t.substr(6)),(s=0===t.indexOf(\"image://\")?zu(t.slice(8),new gi(e,n,i,r),a?\"center\":\"cover\"):0===t.indexOf(\"path://\")?Nu(t.slice(7),{},new gi(e,n,i,r),a?\"center\":\"cover\"):new py({shape:{symbolType:t,x:e,y:n,width:i,height:r}})).__isEmptyBrush=l,s.setColor=dy,o&&s.setColor(o),s}function gy(t,e,n){for(var i=\"radial\"===e.type?function(t,e,n){var i=n.width,r=n.height,o=Math.min(i,r),a=null==e.x?.5:e.x,s=null==e.y?.5:e.y,l=null==e.r?.5:e.r;return e.global||(a=a*i+n.x,s=s*r+n.y,l*=o),t.createRadialGradient(a,s,0,a,s,l)}(t,e,n):function(t,e,n){var i=null==e.x?0:e.x,r=null==e.x2?1:e.x2,o=null==e.y?0:e.y,a=null==e.y2?0:e.y2;return e.global||(i=i*n.width+n.x,r=r*n.width+n.x,o=o*n.height+n.y,a=a*n.height+n.y),i=isNaN(i)?0:i,r=isNaN(r)?1:r,o=isNaN(o)?0:o,a=isNaN(a)?0:a,t.createLinearGradient(i,o,r,a)}(t,e,n),r=e.colorStops,o=0;o<r.length;o++)i.addColorStop(r[o].offset,r[o].color);return i}function yy(t,e){if(t===e||!t&&!e)return!1;if(!t||!e||t.length!==e.length)return!0;for(var n=0;n<t.length;n++)if(t[n]!==e[n])return!0;return!1}function vy(t,e){return t&&\"solid\"!==t&&e>0?(e=e||1,\"dashed\"===t?[4*e,2*e]:\"dotted\"===t?[e]:U(t)?[t]:F(t)?t:null):null}var my=new La(!0);function _y(t){var e=t.stroke;return!(null==e||\"none\"===e||!(t.lineWidth>0))}function xy(t){var e=t.fill;return null!=e&&\"none\"!==e}function by(t,e){if(null!=e.fillOpacity&&1!==e.fillOpacity){var n=t.globalAlpha;t.globalAlpha=e.fillOpacity*e.opacity,t.fill(),t.globalAlpha=n}else t.fill()}function wy(t,e){if(null!=e.strokeOpacity&&1!==e.strokeOpacity){var n=t.globalAlpha;t.globalAlpha=e.strokeOpacity*e.opacity,t.stroke(),t.globalAlpha=n}else t.stroke()}function Sy(t,e,n){var i=no(e.image,e.__image,n);if(ro(i)){var r=t.createPattern(i,e.repeat||\"repeat\");if(\"function\"==typeof DOMMatrix&&r.setTransform){var o=new DOMMatrix;o.rotateSelf(0,0,(e.rotation||0)/Math.PI*180),o.scaleSelf(e.scaleX||1,e.scaleY||1),o.translateSelf(e.x||0,e.y||0),r.setTransform(o)}return r}}var My=[\"shadowBlur\",\"shadowOffsetX\",\"shadowOffsetY\"],Iy=[[\"lineCap\",\"butt\"],[\"lineJoin\",\"miter\"],[\"miterLimit\",10]];function Ty(t,e,n,i,r){var o=!1;if(!i&&e===(n=n||{}))return!1;if(i||e.opacity!==n.opacity){o||(Ay(t,r),o=!0);var a=Math.max(Math.min(e.opacity,1),0);t.globalAlpha=isNaN(a)?_o.opacity:a}(i||e.blend!==n.blend)&&(o||(Ay(t,r),o=!0),t.globalCompositeOperation=e.blend||_o.blend);for(var s=0;s<My.length;s++){var l=My[s];(i||e[l]!==n[l])&&(o||(Ay(t,r),o=!0),t[l]=t.dpr*(e[l]||0))}return(i||e.shadowColor!==n.shadowColor)&&(o||(Ay(t,r),o=!0),t.shadowColor=e.shadowColor||_o.shadowColor),o}function Cy(t,e,n,i,r){var o=Ly(e,r.inHover),a=i?null:n&&Ly(n,r.inHover)||{};if(o===a)return!1;var s=Ty(t,o,a,i,r);if((i||o.fill!==a.fill)&&(s||(Ay(t,r),s=!0),t.fillStyle=o.fill),(i||o.stroke!==a.stroke)&&(s||(Ay(t,r),s=!0),t.strokeStyle=o.stroke),(i||o.opacity!==a.opacity)&&(s||(Ay(t,r),s=!0),t.globalAlpha=null==o.opacity?1:o.opacity),e.hasStroke()){var l=o.lineWidth/(o.strokeNoScale&&e&&e.getLineScale?e.getLineScale():1);t.lineWidth!==l&&(s||(Ay(t,r),s=!0),t.lineWidth=l)}for(var u=0;u<Iy.length;u++){var h=Iy[u],c=h[0];(i||o[c]!==a[c])&&(s||(Ay(t,r),s=!0),t[c]=o[c]||h[1])}return s}function Dy(t,e){var n=e.transform,i=t.dpr||1;n?t.setTransform(i*n[0],i*n[1],i*n[2],i*n[3],i*n[4],i*n[5]):t.setTransform(i,0,0,i,0,0)}function Ay(t,e){e.batchFill&&t.fill(),e.batchStroke&&t.stroke(),e.batchFill=\"\",e.batchStroke=\"\"}function Ly(t,e){return e&&t.__hoverStyle||t.style}function ky(t,e){Py(t,e,{inHover:!1,viewWidth:0,viewHeight:0},!0)}function Py(t,e,n,i){var r=e.transform;if(!e.shouldBePainted(n.viewWidth,n.viewHeight,!1,!1))return e.__dirty&=-2,void(e.__isRendered=!1);var o=e.__clipPaths,a=n.prevElClipPaths,s=!1,l=!1;if(a&&!yy(o,a)||(a&&a.length&&(Ay(t,n),t.restore(),l=s=!0,n.prevElClipPaths=null,n.allClipped=!1,n.prevEl=null),o&&o.length&&(Ay(t,n),t.save(),function(t,e,n){for(var i=!1,r=0;r<t.length;r++){var o=t[r];i=i||o.isZeroArea(),Dy(e,o),e.beginPath(),o.buildPath(e,o.shape),e.clip()}n.allClipped=i}(o,t,n),s=!0),n.prevElClipPaths=o),n.allClipped)e.__isRendered=!1;else{e.beforeBrush&&e.beforeBrush(),e.innerBeforeBrush();var u=n.prevEl;u||(l=s=!0);var h,c,p=e instanceof Ka&&e.autoBatch&&function(t){var e=xy(t),n=_y(t);return!(t.lineDash||!(+e^+n)||e&&\"string\"!=typeof t.fill||n&&\"string\"!=typeof t.stroke||t.strokePercent<1||t.strokeOpacity<1||t.fillOpacity<1)}(e.style);s||(h=r,c=u.transform,h&&c?h[0]!==c[0]||h[1]!==c[1]||h[2]!==c[2]||h[3]!==c[3]||h[4]!==c[4]||h[5]!==c[5]:h||c)?(Ay(t,n),Dy(t,e)):p||Ay(t,n);var d=Ly(e,n.inHover);e instanceof Ka?(1!==n.lastDrawType&&(l=!0,n.lastDrawType=1),Cy(t,e,u,l,n),p&&(n.batchFill||n.batchStroke)||t.beginPath(),function(t,e,n,i){var r=_y(n),o=xy(n),a=n.strokePercent,s=a<1,l=!e.path;e.silent&&!s||!l||e.createPathProxy();var u=e.path||my;if(!i){var h=n.fill,c=n.stroke,p=o&&!!h.colorStops,d=r&&!!c.colorStops,f=o&&!!h.image,g=r&&!!c.image,y=void 0,v=void 0,m=void 0,_=void 0,x=void 0;(p||d)&&(x=e.getBoundingRect()),p&&(y=e.__dirty?gy(t,h,x):e.__canvasFillGradient,e.__canvasFillGradient=y),d&&(v=e.__dirty?gy(t,c,x):e.__canvasStrokeGradient,e.__canvasStrokeGradient=v),f&&(m=e.__dirty||!e.__canvasFillPattern?Sy(t,h,e):e.__canvasFillPattern,e.__canvasFillPattern=m),g&&(_=e.__dirty||!e.__canvasStrokePattern?Sy(t,c,e):e.__canvasStrokePattern,e.__canvasStrokePattern=m),p?t.fillStyle=y:f&&(m?t.fillStyle=m:o=!1),d?t.strokeStyle=v:g&&(_?t.strokeStyle=_:r=!1)}var b=n.lineDash&&n.lineWidth>0&&vy(n.lineDash,n.lineWidth),w=n.lineDashOffset,S=!!t.setLineDash,M=e.getGlobalScale();if(u.setScale(M[0],M[1],e.segmentIgnoreThreshold),b){var I=n.strokeNoScale&&e.getLineScale?e.getLineScale():1;I&&1!==I&&(b=O(b,(function(t){return t/I})),w/=I)}var T=!0;(l||4&e.__dirty||b&&!S&&r)&&(u.setDPR(t.dpr),s?u.setContext(null):(u.setContext(t),T=!1),u.reset(),b&&!S&&(u.setLineDash(b),u.setLineDashOffset(w)),e.buildPath(u,e.shape,i),u.toStatic(),e.pathUpdated()),T&&u.rebuildPath(t,s?a:1),b&&S&&(t.setLineDash(b),t.lineDashOffset=w),i||(n.strokeFirst?(r&&wy(t,n),o&&by(t,n)):(o&&by(t,n),r&&wy(t,n))),b&&S&&t.setLineDash([])}(t,e,d,p),p&&(n.batchFill=d.fill||\"\",n.batchStroke=d.stroke||\"\")):e instanceof Ja?(3!==n.lastDrawType&&(l=!0,n.lastDrawType=3),Cy(t,e,u,l,n),function(t,e,n){var i=n.text;if(null!=i&&(i+=\"\"),i){t.font=n.font||vi,t.textAlign=n.textAlign,t.textBaseline=n.textBaseline;var r=void 0;if(t.setLineDash){var o=n.lineDash&&n.lineWidth>0&&vy(n.lineDash,n.lineWidth),a=n.lineDashOffset;if(o){var s=n.strokeNoScale&&e.getLineScale?e.getLineScale():1;s&&1!==s&&(o=O(o,(function(t){return t/s})),a/=s),t.setLineDash(o),t.lineDashOffset=a,r=!0}}n.strokeFirst?(_y(n)&&t.strokeText(i,n.x,n.y),xy(n)&&t.fillText(i,n.x,n.y)):(xy(n)&&t.fillText(i,n.x,n.y),_y(n)&&t.strokeText(i,n.x,n.y)),r&&t.setLineDash([])}}(t,e,d)):e instanceof es?(2!==n.lastDrawType&&(l=!0,n.lastDrawType=2),function(t,e,n,i,r){Ty(t,Ly(e,r.inHover),n&&Ly(n,r.inHover),i,r)}(t,e,u,l,n),function(t,e,n){var i=e.__image=no(n.image,e.__image,e,e.onload);if(i&&ro(i)){var r=n.x||0,o=n.y||0,a=e.getWidth(),s=e.getHeight(),l=i.width/i.height;if(null==a&&null!=s?a=s*l:null==s&&null!=a?s=a/l:null==a&&null==s&&(a=i.width,s=i.height),n.sWidth&&n.sHeight){var u=n.sx||0,h=n.sy||0;t.drawImage(i,u,h,n.sWidth,n.sHeight,r,o,a,s)}else if(n.sx&&n.sy){var c=a-(u=n.sx),p=s-(h=n.sy);t.drawImage(i,u,h,c,p,r,o,a,s)}else t.drawImage(i,r,o,a,s)}}(t,e,d)):e instanceof Tu&&(4!==n.lastDrawType&&(l=!0,n.lastDrawType=4),function(t,e,n){var i=e.getDisplayables(),r=e.getTemporalDisplayables();t.save();var o,a,s={prevElClipPaths:null,prevEl:null,allClipped:!1,viewWidth:n.viewWidth,viewHeight:n.viewHeight,inHover:n.inHover};for(o=e.getCursor(),a=i.length;o<a;o++){(h=i[o]).beforeBrush&&h.beforeBrush(),h.innerBeforeBrush(),Py(t,h,s,o===a-1),h.innerAfterBrush(),h.afterBrush&&h.afterBrush(),s.prevEl=h}for(var l=0,u=r.length;l<u;l++){var h;(h=r[l]).beforeBrush&&h.beforeBrush(),h.innerBeforeBrush(),Py(t,h,s,l===u-1),h.innerAfterBrush(),h.afterBrush&&h.afterBrush(),s.prevEl=h}e.clearTemporalDisplayables(),e.notClear=!0,t.restore()}(t,e,n)),p&&i&&Ay(t,n),e.innerAfterBrush(),e.afterBrush&&e.afterBrush(),n.prevEl=e,e.__dirty=0,e.__isRendered=!0}}var Oy,Ry=new oy,Ny=new Ae(100),zy=[\"symbol\",\"symbolSize\",\"symbolKeepAspect\",\"color\",\"backgroundColor\",\"dashArrayX\",\"dashArrayY\",\"maxTileWidth\",\"maxTileHeight\"];function Ey(t,e){if(\"none\"===t)return null;var n=e.getDevicePixelRatio(),i=e.getZr(),r=\"svg\"===i.painter.type;t.dirty&&Ry.delete(t);var o=Ry.get(t);if(o)return o;var a=T(t,{symbol:\"rect\",symbolSize:1,symbolKeepAspect:!0,color:\"rgba(0, 0, 0, 0.2)\",backgroundColor:null,dashArrayX:5,dashArrayY:5,rotation:0,maxTileWidth:512,maxTileHeight:512});\"none\"===a.backgroundColor&&(a.backgroundColor=null);var s={repeat:\"repeat\"};return function(t){for(var e,o=[n],s=!0,l=0;l<zy.length;++l){var u=a[zy[l]],h=typeof u;if(null!=u&&!F(u)&&\"string\"!==h&&\"number\"!==h&&\"boolean\"!==h){s=!1;break}o.push(u)}if(s){e=o.join(\",\")+(r?\"-svg\":\"\");var c=Ny.get(e);c&&(r?t.svgElement=c:t.image=c)}var p,d=By(a.dashArrayX),f=function(t){if(!t||\"object\"==typeof t&&0===t.length)return[0,0];if(\"number\"==typeof t){var e=Math.ceil(t);return[e,e]}var n=O(t,(function(t){return Math.ceil(t)}));return t.length%2?n.concat(n):n}(a.dashArrayY),g=Vy(a.symbol),y=(b=d,O(b,(function(t){return Fy(t)}))),v=Fy(f),m=!r&&C(),_=r&&i.painter.createSVGElement(\"g\"),x=function(){for(var t=1,e=0,n=y.length;e<n;++e)t=gr(t,y[e]);var i=1;for(e=0,n=g.length;e<n;++e)i=gr(i,g[e].length);t*=i;var r=v*y.length*g.length;return{width:Math.max(1,Math.min(t,a.maxTileWidth)),height:Math.max(1,Math.min(r,a.maxTileHeight))}}();var b;m&&(m.width=x.width*n,m.height=x.height*n,p=m.getContext(\"2d\"));(function(){p&&(p.clearRect(0,0,m.width,m.height),a.backgroundColor&&(p.fillStyle=a.backgroundColor,p.fillRect(0,0,m.width,m.height)));for(var t=0,e=0;e<f.length;++e)t+=f[e];if(t<=0)return;var o=-v,s=0,l=0,u=0;for(;o<x.height;){if(s%2==0){for(var h=l/2%g.length,c=0,y=0,b=0;c<2*x.width;){var w=0;for(e=0;e<d[u].length;++e)w+=d[u][e];if(w<=0)break;if(y%2==0){var S=.5*(1-a.symbolSize),M=c+d[u][y]*S,I=o+f[s]*S,T=d[u][y]*a.symbolSize,C=f[s]*a.symbolSize,D=b/2%g[h].length;A(M,I,T,C,g[h][D])}c+=d[u][y],++b,++y===d[u].length&&(y=0)}++u===d.length&&(u=0)}o+=f[s],++l,++s===f.length&&(s=0)}function A(t,e,o,s,l){var u=r?1:n,h=fy(l,t*u,e*u,o*u,s*u,a.color,a.symbolKeepAspect);r?_.appendChild(i.painter.paintOne(h)):ky(p,h)}})(),s&&Ny.put(e,m||_);t.image=m,t.svgElement=_,t.svgWidth=x.width,t.svgHeight=x.height}(s),s.rotation=a.rotation,s.scaleX=s.scaleY=r?1:1/n,Ry.set(t,s),t.dirty=!1,s}function Vy(t){if(!t||0===t.length)return[[\"rect\"]];if(\"string\"==typeof t)return[[t]];for(var e=!0,n=0;n<t.length;++n)if(\"string\"!=typeof t[n]){e=!1;break}if(e)return Vy([t]);var i=[];for(n=0;n<t.length;++n)\"string\"==typeof t[n]?i.push([t[n]]):i.push(t[n]);return i}function By(t){if(!t||0===t.length)return[[0,0]];if(\"number\"==typeof t)return[[r=Math.ceil(t),r]];for(var e=!0,n=0;n<t.length;++n)if(\"number\"!=typeof t[n]){e=!1;break}if(e)return By([t]);var i=[];for(n=0;n<t.length;++n)if(\"number\"==typeof t[n]){var r=Math.ceil(t[n]);i.push([r,r])}else{(r=O(t[n],(function(t){return Math.ceil(t)}))).length%2==1?i.push(r.concat(r)):i.push(r)}return i}function Fy(t){for(var e=0,n=0;n<t.length;++n)e+=t[n];return t.length%2==1?2*e:e}function Gy(t){H(t)&&(t=(new DOMParser).parseFromString(t,\"text/xml\"));var e=t;for(9===e.nodeType&&(e=e.firstChild);\"svg\"!==e.nodeName.toLowerCase()||1!==e.nodeType;)e=e.nextSibling;return e}var Hy={fill:\"fill\",stroke:\"stroke\",\"stroke-width\":\"lineWidth\",opacity:\"opacity\",\"fill-opacity\":\"fillOpacity\",\"stroke-opacity\":\"strokeOpacity\",\"stroke-dasharray\":\"lineDash\",\"stroke-dashoffset\":\"lineDashOffset\",\"stroke-linecap\":\"lineCap\",\"stroke-linejoin\":\"lineJoin\",\"stroke-miterlimit\":\"miterLimit\",\"font-family\":\"fontFamily\",\"font-size\":\"fontSize\",\"font-style\":\"fontStyle\",\"font-weight\":\"fontWeight\",\"text-anchor\":\"textAlign\",visibility:\"visibility\",display:\"display\"},Wy=E(Hy),Uy={\"alignment-baseline\":\"textBaseline\",\"stop-color\":\"stopColor\"},Xy=E(Uy),Yy=function(){function t(){this._defs={},this._root=null}return t.prototype.parse=function(t,e){e=e||{};var n=Gy(t);if(!n)throw new Error(\"Illegal svg\");this._defsUsePending=[];var i=new Ei;this._root=i;var r=[],o=n.getAttribute(\"viewBox\")||\"\",a=parseFloat(n.getAttribute(\"width\")||e.width),s=parseFloat(n.getAttribute(\"height\")||e.height);isNaN(a)&&(a=null),isNaN(s)&&(s=null),Jy(n,i,null,!0,!1);for(var l,u,h=n.firstChild;h;)this._parseNode(h,i,r,null,!1,!1),h=h.nextSibling;if(function(t,e){for(var n=0;n<e.length;n++){var i=e[n];i[0].style[i[1]]=t[i[2]]}}(this._defs,this._defsUsePending),this._defsUsePending=[],o){var c=nv(o);c.length>=4&&(l={x:parseFloat(c[0]||0),y:parseFloat(c[1]||0),width:parseFloat(c[2]),height:parseFloat(c[3])})}if(l&&null!=a&&null!=s&&(u=sv(l,{x:0,y:0,width:a,height:s}),!e.ignoreViewBox)){var p=i;(i=new Ei).add(p),p.scaleX=p.scaleY=u.scale,p.x=u.x,p.y=u.y}return e.ignoreRootClip||null==a||null==s||i.setClipPath(new ls({shape:{x:0,y:0,width:a,height:s}})),{root:i,width:a,height:s,viewBoxRect:l,viewBoxTransform:u,named:r}},t.prototype._parseNode=function(t,e,n,i,r,o){var a,s=t.nodeName.toLowerCase(),l=i;if(\"defs\"===s&&(r=!0),\"text\"===s&&(o=!0),\"defs\"===s||\"switch\"===s)a=e;else{if(!r){var u=Oy[s];if(u&&dt(Oy,s)){a=u.call(this,t,e);var h=t.getAttribute(\"name\");if(h){var c={name:h,namedFrom:null,svgNodeTagLower:s,el:a};n.push(c),\"g\"===s&&(l=c)}else i&&n.push({name:i.name,namedFrom:i,svgNodeTagLower:s,el:a});e.add(a)}}var p=Zy[s];if(p&&dt(Zy,s)){var d=p.call(this,t),f=t.getAttribute(\"id\");f&&(this._defs[f]=d)}}if(a&&a.isGroup)for(var g=t.firstChild;g;)1===g.nodeType?this._parseNode(g,a,n,l,r,o):3===g.nodeType&&o&&this._parseText(g,a),g=g.nextSibling},t.prototype._parseText=function(t,e){var n=new Ja({style:{text:t.textContent},silent:!0,x:this._textX||0,y:this._textY||0});Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),function(t,e){var n=e.__selfStyle;if(n){var i=n.textBaseline,r=i;i&&\"auto\"!==i?\"baseline\"===i?r=\"alphabetic\":\"before-edge\"===i||\"text-before-edge\"===i?r=\"top\":\"after-edge\"===i||\"text-after-edge\"===i?r=\"bottom\":\"central\"!==i&&\"mathematical\"!==i||(r=\"middle\"):r=\"alphabetic\",t.style.textBaseline=r}var o=e.__inheritedStyle;if(o){var a=o.textAlign,s=a;a&&(\"middle\"===a&&(s=\"center\"),t.style.textAlign=s)}}(n,e);var i=n.style,r=i.fontSize;r&&r<9&&(i.fontSize=9,n.scaleX*=r/9,n.scaleY*=r/9);var o=(i.fontSize||i.fontFamily)&&[i.fontStyle,i.fontWeight,(i.fontSize||12)+\"px\",i.fontFamily||\"sans-serif\"].join(\" \");i.font=o;var a=n.getBoundingRect();return this._textX+=a.width,e.add(n),n},t.internalField=void(Oy={g:function(t,e){var n=new Ei;return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n},rect:function(t,e){var n=new ls;return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n.setShape({x:parseFloat(t.getAttribute(\"x\")||\"0\"),y:parseFloat(t.getAttribute(\"y\")||\"0\"),width:parseFloat(t.getAttribute(\"width\")||\"0\"),height:parseFloat(t.getAttribute(\"height\")||\"0\")}),n.silent=!0,n},circle:function(t,e){var n=new Nl;return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n.setShape({cx:parseFloat(t.getAttribute(\"cx\")||\"0\"),cy:parseFloat(t.getAttribute(\"cy\")||\"0\"),r:parseFloat(t.getAttribute(\"r\")||\"0\")}),n.silent=!0,n},line:function(t,e){var n=new uu;return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n.setShape({x1:parseFloat(t.getAttribute(\"x1\")||\"0\"),y1:parseFloat(t.getAttribute(\"y1\")||\"0\"),x2:parseFloat(t.getAttribute(\"x2\")||\"0\"),y2:parseFloat(t.getAttribute(\"y2\")||\"0\")}),n.silent=!0,n},ellipse:function(t,e){var n=new El;return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n.setShape({cx:parseFloat(t.getAttribute(\"cx\")||\"0\"),cy:parseFloat(t.getAttribute(\"cy\")||\"0\"),rx:parseFloat(t.getAttribute(\"rx\")||\"0\"),ry:parseFloat(t.getAttribute(\"ry\")||\"0\")}),n.silent=!0,n},polygon:function(t,e){var n,i=t.getAttribute(\"points\");i&&(n=$y(i));var r=new ru({shape:{points:n||[]},silent:!0});return Ky(e,r),Jy(t,r,this._defsUsePending,!1,!1),r},polyline:function(t,e){var n,i=t.getAttribute(\"points\");i&&(n=$y(i));var r=new au({shape:{points:n||[]},silent:!0});return Ky(e,r),Jy(t,r,this._defsUsePending,!1,!1),r},image:function(t,e){var n=new es;return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n.setStyle({image:t.getAttribute(\"xlink:href\"),x:+t.getAttribute(\"x\"),y:+t.getAttribute(\"y\"),width:+t.getAttribute(\"width\"),height:+t.getAttribute(\"height\")}),n.silent=!0,n},text:function(t,e){var n=t.getAttribute(\"x\")||\"0\",i=t.getAttribute(\"y\")||\"0\",r=t.getAttribute(\"dx\")||\"0\",o=t.getAttribute(\"dy\")||\"0\";this._textX=parseFloat(n)+parseFloat(r),this._textY=parseFloat(i)+parseFloat(o);var a=new Ei;return Ky(e,a),Jy(t,a,this._defsUsePending,!1,!0),a},tspan:function(t,e){var n=t.getAttribute(\"x\"),i=t.getAttribute(\"y\");null!=n&&(this._textX=parseFloat(n)),null!=i&&(this._textY=parseFloat(i));var r=t.getAttribute(\"dx\")||\"0\",o=t.getAttribute(\"dy\")||\"0\",a=new Ei;return Ky(e,a),Jy(t,a,this._defsUsePending,!1,!0),this._textX+=parseFloat(r),this._textY+=parseFloat(o),a},path:function(t,e){var n=Ol(t.getAttribute(\"d\")||\"\");return Ky(e,n),Jy(t,n,this._defsUsePending,!1,!1),n.silent=!0,n}}),t}(),Zy={lineargradient:function(t){var e=parseInt(t.getAttribute(\"x1\")||\"0\",10),n=parseInt(t.getAttribute(\"y1\")||\"0\",10),i=parseInt(t.getAttribute(\"x2\")||\"10\",10),r=parseInt(t.getAttribute(\"y2\")||\"0\",10),o=new mu(e,n,i,r);return jy(t,o),qy(t,o),o},radialgradient:function(t){var e=parseInt(t.getAttribute(\"cx\")||\"0\",10),n=parseInt(t.getAttribute(\"cy\")||\"0\",10),i=parseInt(t.getAttribute(\"r\")||\"0\",10),r=new _u(e,n,i);return jy(t,r),qy(t,r),r}};function jy(t,e){\"userSpaceOnUse\"===t.getAttribute(\"gradientUnits\")&&(e.global=!0)}function qy(t,e){for(var n=t.firstChild;n;){if(1===n.nodeType&&\"stop\"===n.nodeName.toLocaleLowerCase()){var i=n.getAttribute(\"offset\"),r=void 0;r=i&&i.indexOf(\"%\")>0?parseInt(i,10)/100:i?parseFloat(i):0;var o={};av(n,o,o);var a=o.stopColor||n.getAttribute(\"stop-color\")||\"#000000\";e.colorStops.push({offset:r,color:a})}n=n.nextSibling}}function Ky(t,e){t&&t.__inheritedStyle&&(e.__inheritedStyle||(e.__inheritedStyle={}),T(e.__inheritedStyle,t.__inheritedStyle))}function $y(t){for(var e=nv(t),n=[],i=0;i<e.length;i+=2){var r=parseFloat(e[i]),o=parseFloat(e[i+1]);n.push([r,o])}return n}function Jy(t,e,n,i,r){var o=e,a=o.__inheritedStyle=o.__inheritedStyle||{},s={};1===t.nodeType&&(function(t,e){var n=t.getAttribute(\"transform\");if(n){n=n.replace(/,/g,\" \");var i=[],r=null;n.replace(iv,(function(t,e,n){return i.push(e,n),\"\"}));for(var o=i.length-1;o>0;o-=2){var a=i[o],s=i[o-1],l=nv(a);switch(r=r||[1,0,0,1,0,0],s){case\"translate\":Un(r,r,[parseFloat(l[0]),parseFloat(l[1]||\"0\")]);break;case\"scale\":Yn(r,r,[parseFloat(l[0]),parseFloat(l[1]||l[0])]);break;case\"rotate\":Xn(r,r,-parseFloat(l[0])*rv);break;case\"skewX\":Wn(r,[1,0,Math.tan(parseFloat(l[0])*rv),1,0,0],r);break;case\"skewY\":Wn(r,[1,Math.tan(parseFloat(l[0])*rv),0,1,0,0],r);break;case\"matrix\":r[0]=parseFloat(l[0]),r[1]=parseFloat(l[1]),r[2]=parseFloat(l[2]),r[3]=parseFloat(l[3]),r[4]=parseFloat(l[4]),r[5]=parseFloat(l[5])}}e.setLocalTransform(r)}}(t,e),av(t,a,s),i||function(t,e,n){for(var i=0;i<Wy.length;i++){var r=Wy[i];null!=(o=t.getAttribute(r))&&(e[Hy[r]]=o)}for(i=0;i<Xy.length;i++){var o;r=Xy[i];null!=(o=t.getAttribute(r))&&(n[Uy[r]]=o)}}(t,a,s)),o.style=o.style||{},null!=a.fill&&(o.style.fill=tv(o,\"fill\",a.fill,n)),null!=a.stroke&&(o.style.stroke=tv(o,\"stroke\",a.stroke,n)),P([\"lineWidth\",\"opacity\",\"fillOpacity\",\"strokeOpacity\",\"miterLimit\",\"fontSize\"],(function(t){null!=a[t]&&(o.style[t]=parseFloat(a[t]))})),P([\"lineDashOffset\",\"lineCap\",\"lineJoin\",\"fontWeight\",\"fontFamily\",\"fontStyle\",\"textAlign\"],(function(t){null!=a[t]&&(o.style[t]=a[t])})),r&&(o.__selfStyle=s),a.lineDash&&(o.style.lineDash=O(nv(a.lineDash),(function(t){return parseFloat(t)}))),\"hidden\"!==a.visibility&&\"collapse\"!==a.visibility||(o.invisible=!0),\"none\"===a.display&&(o.ignore=!0)}var Qy=/^url\\(\\s*#(.*?)\\)/;function tv(t,e,n,i){var r=n&&n.match(Qy);if(!r)return\"none\"===n&&(n=null),n;var o=ot(r[1]);i.push([t,e,o])}var ev=/-?([0-9]*\\.)?[0-9]+([eE]-?[0-9]+)?/g;function nv(t){return t.match(ev)||[]}var iv=/(translate|scale|rotate|skewX|skewY|matrix)\\(([\\-\\s0-9\\.eE,]*)\\)/g,rv=Math.PI/180;var ov=/([^\\s:;]+)\\s*:\\s*([^:;]+)/g;function av(t,e,n){var i,r=t.getAttribute(\"style\");if(r)for(ov.lastIndex=0;null!=(i=ov.exec(r));){var o=i[1],a=dt(Hy,o)?Hy[o]:null;a&&(e[a]=i[2]);var s=dt(Uy,o)?Uy[o]:null;s&&(n[s]=i[2])}}function sv(t,e){var n=e.width/t.width,i=e.height/t.height,r=Math.min(n,i);return{scale:r,x:-(t.x+t.width/2)*r+(e.x+e.width/2),y:-(t.y+t.height/2)*r+(e.y+e.height/2)}}function lv(t,e){return Math.abs(t-e)<1e-8}function uv(t,e,n){var i=0,r=t[0];if(!r)return!1;for(var o=1;o<t.length;o++){var a=t[o];i+=Va(r[0],r[1],a[0],a[1],e,n),r=a}var s=t[0];return lv(r[0],s[0])&&lv(r[1],s[1])||(i+=Va(r[0],r[1],s[0],s[1],e,n)),0!==i}var hv=[],cv=function(){function t(t){this.name=t}return t.prototype.getCenter=function(){},t}(),pv=function(t){function e(e,n,i){var r=t.call(this,e)||this;if(r.type=\"geoJSON\",r.geometries=n,i)i=[i[0],i[1]];else{var o=r.getBoundingRect();i=[o.x+o.width/2,o.y+o.height/2]}return r._center=i,r}return n(e,t),e.prototype.getBoundingRect=function(){var t=this._rect;if(t)return t;for(var e=Number.MAX_VALUE,n=[e,e],i=[-e,-e],r=[],o=[],a=this.geometries,s=0;s<a.length;s++){if(\"polygon\"===a[s].type)ra(a[s].exterior,r,o),Nt(n,n,r),zt(i,i,o)}return 0===s&&(n[0]=n[1]=i[0]=i[1]=0),this._rect=new gi(n[0],n[1],i[0]-n[0],i[1]-n[1])},e.prototype.contain=function(t){var e=this.getBoundingRect(),n=this.geometries;if(!e.contain(t[0],t[1]))return!1;t:for(var i=0,r=n.length;i<r;i++)if(\"polygon\"===n[i].type){var o=n[i].exterior,a=n[i].interiors;if(uv(o,t[0],t[1])){for(var s=0;s<(a?a.length:0);s++)if(uv(a[s],t[0],t[1]))continue t;return!0}}return!1},e.prototype.transformTo=function(t,e,n,i){var r=this.getBoundingRect(),o=r.width/r.height;n?i||(i=n/o):n=o*i;for(var a=new gi(t,e,n,i),s=r.calculateTransform(a),l=this.geometries,u=0;u<l.length;u++)if(\"polygon\"===l[u].type){for(var h=l[u].exterior,c=l[u].interiors,p=0;p<h.length;p++)Rt(h[p],h[p],s);for(var d=0;d<(c?c.length:0);d++)for(p=0;p<c[d].length;p++)Rt(c[d][p],c[d][p],s)}(r=this._rect).copy(a),this._center=[r.x+r.width/2,r.y+r.height/2]},e.prototype.cloneShallow=function(t){null==t&&(t=this.name);var n=new e(t,this.geometries,this._center);return n._rect=this._rect,n.transformTo=null,n},e.prototype.getCenter=function(){return this._center},e.prototype.setCenter=function(t){this._center=t},e}(cv),dv=function(t){function e(e,n){var i=t.call(this,e)||this;return i.type=\"geoSVG\",i._elOnlyForCalculate=n,i}return n(e,t),e.prototype.getCenter=function(){var t=this._center;return t||(t=this._center=this._calculateCenter()),t},e.prototype._calculateCenter=function(){for(var t=this._elOnlyForCalculate,e=t.getBoundingRect(),n=[e.x+e.width/2,e.y+e.height/2],i=Gn(hv),r=t;r&&!r.isGeoSVGGraphicRoot;)Wn(i,r.getLocalTransform(),i),r=r.parent;return Zn(i,i),Rt(n,n,i),n},e}(cv),fv=ht([\"rect\",\"circle\",\"line\",\"ellipse\",\"polygon\",\"polyline\",\"path\",\"text\",\"tspan\",\"g\"]),gv=function(){function t(t,e){this.type=\"geoSVG\",this._usedGraphicMap=ht(),this._freedGraphics=[],this._mapName=t,this._parsedXML=Gy(e)}return t.prototype.load=function(){var t=this._firstGraphic;if(!t){t=this._firstGraphic=this._buildGraphic(this._parsedXML),this._freedGraphics.push(t),this._boundingRect=this._firstGraphic.boundingRect.clone();var e=function(t){var e=[],n=ht();return P(t,(function(t){if(null==t.namedFrom){var i=new dv(t.name,t.el);e.push(i),n.set(t.name,i)}})),{regions:e,regionsMap:n}}(t.named),n=e.regions,i=e.regionsMap;this._regions=n,this._regionsMap=i}return{boundingRect:this._boundingRect,regions:this._regions,regionsMap:this._regionsMap}},t.prototype._buildGraphic=function(t){var e,n,i,r;try{rt(null!=(n=(e=t&&(i=t,r={ignoreViewBox:!0,ignoreRootClip:!0},(new Yy).parse(i,r))||{}).root))}catch(t){throw new Error(\"Invalid svg format\\n\"+t.message)}var o=new Ei;o.add(n),o.isGeoSVGGraphicRoot=!0;var a=e.width,s=e.height,l=e.viewBoxRect,u=this._boundingRect;if(!u){var h=void 0,c=void 0,p=void 0,d=void 0;if(null!=a?(h=0,p=a):l&&(h=l.x,p=l.width),null!=s?(c=0,d=s):l&&(c=l.y,d=l.height),null==h||null==c){var f=n.getBoundingRect();null==h&&(h=f.x,p=f.width),null==c&&(c=f.y,d=f.height)}u=this._boundingRect=new gi(h,c,p,d)}if(l){var g=sv(l,u);n.scaleX=n.scaleY=g.scale,n.x=g.x,n.y=g.y}o.setClipPath(new ls({shape:u.plain()}));var y=[];return P(e.named,(function(t){var e;null!=fv.get(t.svgNodeTagLower)&&(y.push(t),(e=t.el).silent=!1,e.isGroup&&e.traverse((function(t){t.silent=!1})))})),{root:o,boundingRect:u,named:y}},t.prototype.useGraphic=function(t){var e=this._usedGraphicMap,n=e.get(t);return n||(n=this._freedGraphics.pop()||this._buildGraphic(this._parsedXML),e.set(t,n),n)},t.prototype.freeGraphic=function(t){var e=this._usedGraphicMap,n=e.get(t);n&&(e.removeKey(t),this._freedGraphics.push(n))},t}();function yv(t,e,n){for(var i=[],r=e[0],o=e[1],a=0;a<t.length;a+=2){var s=t.charCodeAt(a)-64,l=t.charCodeAt(a+1)-64;s=s>>1^-(1&s),l=l>>1^-(1&l),r=s+=r,o=l+=o,i.push([s/n,l/n])}return i}function vv(t,e){return O(N((t=function(t){if(!t.UTF8Encoding)return t;var e=t,n=e.UTF8Scale;null==n&&(n=1024);for(var i=e.features,r=0;r<i.length;r++){var o=i[r].geometry;if(\"Polygon\"===o.type)for(var a=o.coordinates,s=0;s<a.length;s++)a[s]=yv(a[s],o.encodeOffsets[s],n);else if(\"MultiPolygon\"===o.type)for(a=o.coordinates,s=0;s<a.length;s++)for(var l=a[s],u=0;u<l.length;u++)l[u]=yv(l[u],o.encodeOffsets[s][u],n)}return e.UTF8Encoding=!1,e}(t)).features,(function(t){return t.geometry&&t.properties&&t.geometry.coordinates.length>0})),(function(t){var n=t.properties,i=t.geometry,r=[];if(\"Polygon\"===i.type){var o=i.coordinates;r.push({type:\"polygon\",exterior:o[0],interiors:o.slice(1)})}\"MultiPolygon\"===i.type&&P(o=i.coordinates,(function(t){t[0]&&r.push({type:\"polygon\",exterior:t[0],interiors:t.slice(1)})}));var a=new pv(n[e||\"name\"],r,n.cp);return a.properties=n,a}))}for(var mv=[126,25],_v=\"南海诸岛\",xv=[[[0,3.5],[7,11.2],[15,11.9],[30,7],[42,.7],[52,.7],[56,7.7],[59,.7],[64,.7],[64,0],[5,0],[0,3.5]],[[13,16.1],[19,14.7],[16,21.7],[11,23.1],[13,16.1]],[[12,32.2],[14,38.5],[15,38.5],[13,32.2],[12,32.2]],[[16,47.6],[12,53.2],[13,53.2],[18,47.6],[16,47.6]],[[6,64.4],[8,70],[9,70],[8,64.4],[6,64.4]],[[23,82.6],[29,79.8],[30,79.8],[25,82.6],[23,82.6]],[[37,70.7],[43,62.3],[44,62.3],[39,70.7],[37,70.7]],[[48,51.1],[51,45.5],[53,45.5],[50,51.1],[48,51.1]],[[51,35],[51,28.7],[53,28.7],[53,35],[51,35]],[[52,22.4],[55,17.5],[56,17.5],[53,22.4],[52,22.4]],[[58,12.6],[62,7],[63,7],[60,12.6],[58,12.6]],[[0,3.5],[0,93.1],[64,93.1],[64,0],[63,0],[63,92.4],[1,92.4],[1,3.5],[0,3.5]]],bv=0;bv<xv.length;bv++)for(var wv=0;wv<xv[bv].length;wv++)xv[bv][wv][0]/=10.5,xv[bv][wv][1]/=-14,xv[bv][wv][0]+=mv[0],xv[bv][wv][1]+=mv[1];var Sv={\"南海诸岛\":[32,80],\"广东\":[0,-10],\"香港\":[10,5],\"澳门\":[-10,10],\"天津\":[5,5]};var Mv={Russia:[100,60],\"United States\":[-99,38],\"United States of America\":[-99,38]};var Iv=[[[123.45165252685547,25.73527164402261],[123.49731445312499,25.73527164402261],[123.49731445312499,25.750734064600884],[123.45165252685547,25.750734064600884],[123.45165252685547,25.73527164402261]]];var Tv=function(){function t(t,e,n){var i;this.type=\"geoJSON\",this._parsedMap=ht(),this._mapName=t,this._specialAreas=n,this._geoJSON=H(i=e)?\"undefined\"!=typeof JSON&&JSON.parse?JSON.parse(i):new Function(\"return (\"+i+\");\")():i}return t.prototype.load=function(t,e){e=e||\"name\";var n=this._parsedMap.get(e);if(!n){var i=this._parseToRegions(e);n=this._parsedMap.set(e,{regions:i,boundingRect:Cv(i)})}var r=ht(),o=[];return P(n.regions,(function(e){var n=e.name;t&&t.hasOwnProperty(n)&&(e=e.cloneShallow(n=t[n])),o.push(e),r.set(n,e)})),{regions:o,boundingRect:n.boundingRect||new gi(0,0,0,0),regionsMap:r}},t.prototype._parseToRegions=function(t){var e,n=this._mapName,i=this._geoJSON;try{e=i?vv(i,t):[]}catch(t){throw new Error(\"Invalid geoJson format\\n\"+t.message)}return function(t,e){if(\"china\"===t){for(var n=0;n<e.length;n++)if(e[n].name===_v)return;e.push(new pv(_v,O(xv,(function(t){return{type:\"polygon\",exterior:t}})),mv))}}(n,e),P(e,(function(t){var e=t.name;!function(t,e){if(\"china\"===t){var n=Sv[e.name];if(n){var i=e.getCenter();i[0]+=n[0]/10.5,i[1]+=-n[1]/14,e.setCenter(i)}}}(n,t),function(t,e){if(\"world\"===t){var n=Mv[e.name];if(n){var i=[n[0],n[1]];e.setCenter(i)}}}(n,t),function(t,e){\"china\"===t&&\"台湾\"===e.name&&e.geometries.push({type:\"polygon\",exterior:Iv[0]})}(n,t);var i=this._specialAreas&&this._specialAreas[e];i&&t.transformTo(i.left,i.top,i.width,i.height)}),this),e},t.prototype.getMapForUser=function(){return{geoJson:this._geoJSON,geoJSON:this._geoJSON,specialAreas:this._specialAreas}},t}();function Cv(t){for(var e,n=0;n<t.length;n++){var i=t[n].getBoundingRect();(e=e||i.clone()).union(i)}return e}var Dv=ht(),Av=function(t,e,n){if(e.svg){var i=new gv(t,e.svg);Dv.set(t,i)}else{var r=e.geoJson||e.geoJSON;r&&!e.features?n=e.specialAreas:r=e;i=new Tv(t,r,n);Dv.set(t,i)}},Lv=function(t){return Dv.get(t)},kv=function(t){var e=Dv.get(t);return e&&\"geoJSON\"===e.type&&e.getMapForUser()},Pv=function(t,e,n){var i=Dv.get(t);if(i)return i.load(e,n)},Ov=rt,Rv=P,Nv=G,zv=X,Ev=D,Vv=\"undefined\"!=typeof window,Bv=2e3,Fv=4500,Gv={PROCESSOR:{FILTER:1e3,SERIES_FILTER:800,STATISTIC:5e3},VISUAL:{LAYOUT:1e3,PROGRESSIVE_LAYOUT:1100,GLOBAL:Bv,CHART:3e3,POST_CHART_LAYOUT:4600,COMPONENT:4e3,BRUSH:5e3,CHART_ITEM:Fv,ARIA:6e3,DECAL:7e3}},Hv=/^[a-zA-Z0-9_]+$/,Wv=\"__connectUpdateStatus\";function Uv(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];if(!this.isDisposed())return Yv(this,t,e);_m(this.id)}}function Xv(t){return function(){for(var e=[],n=0;n<arguments.length;n++)e[n]=arguments[n];return Yv(this,t,e)}}function Yv(t,e,n){return n[0]=n[0]&&n[0].toLowerCase(),Ft.prototype[e].apply(t,n)}var Zv,jv,qv,Kv,$v,Jv,Qv,tm,em,nm,im,rm,om,am,sm,lm,um,hm,cm,pm,dm,fm=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e}(Ft),gm=fm.prototype;gm.on=Xv(\"on\"),gm.off=Xv(\"off\");var ym=function(t){function e(e,n,i){var r=t.call(this,new gg)||this;r._chartsViews=[],r._chartsMap={},r._componentsViews=[],r._componentsMap={},r._pendingActions=[],i=i||{},\"string\"==typeof n&&(n=Cm[n]),r._dom=e;var o=\"canvas\",a=!1,s=r._zr=Hi(e,{renderer:i.renderer||o,devicePixelRatio:i.devicePixelRatio,width:i.width,height:i.height,useDirtyRect:null==i.useDirtyRect?a:i.useDirtyRect});r._throttledZrFlush=Nf(V(s.flush,s),17),(n=w(n))&&Jp(n,!0),r._theme=n,r._locale=function(t){if(H(t)){var e=Fh[t.toUpperCase()]||{};return t===Eh||t===Vh?w(e):S(w(e),w(Fh.EN),!1)}return S(w(t),w(Fh.EN),!1)}(i.locale||Hh),r._coordSysMgr=new Ap;var l=r._api=um(r);function u(t,e){return t.__prio-e.__prio}return _e(Tm,u),_e(wm,u),r._scheduler=new Zf(r,l,wm,Tm),r._messageCenter=new fm,r._labelManager=new ty,r._initEvents(),r.resize=V(r.resize,r),s.animation.on(\"frame\",r._onframe,r),nm(s,r),im(s,r),st(r),r}return n(e,t),e.prototype._onframe=function(){if(!this._disposed){dm(this);var t=this._scheduler;if(this.__optionUpdated){var e=this.__optionUpdated.silent;this.__flagInMainProcess=!0,Zv(this),Kv.update.call(this),this._zr.flush(),this.__flagInMainProcess=!1,this.__optionUpdated=!1,tm.call(this,e),em.call(this,e)}else if(t.unfinished){var n=1,i=this._model,r=this._api;t.unfinished=!1;do{var o=+new Date;t.performSeriesTasks(i),t.performDataProcessorTasks(i),Jv(this,i),t.performVisualTasks(i),sm(this,this._model,r,\"remain\"),n-=+new Date-o}while(n>0&&t.unfinished);t.unfinished||this._zr.flush()}}},e.prototype.getDom=function(){return this._dom},e.prototype.getId=function(){return this.id},e.prototype.getZr=function(){return this._zr},e.prototype.setOption=function(t,e,n){if(this._disposed)_m(this.id);else{var i,r,o;if(zv(e)&&(n=e.lazyUpdate,i=e.silent,r=e.replaceMerge,o=e.transition,e=e.notMerge),this.__flagInMainProcess=!0,!this._model||e){var a=new kp(this._api),s=this._theme,l=this._model=new wp;l.scheduler=this._scheduler,l.init(null,null,null,s,this._locale,a)}this._model.setOption(t,{replaceMerge:r},Sm),cm(this,o),n?(this.__optionUpdated={silent:i},this.__flagInMainProcess=!1,this.getZr().wakeUp()):(Zv(this),Kv.update.call(this),this._zr.flush(),this.__optionUpdated=!1,this.__flagInMainProcess=!1,tm.call(this,i),em.call(this,i))}},e.prototype.setTheme=function(){console.error(\"ECharts#setTheme() is DEPRECATED in ECharts 3.0\")},e.prototype.getModel=function(){return this._model},e.prototype.getOption=function(){return this._model&&this._model.getOption()},e.prototype.getWidth=function(){return this._zr.getWidth()},e.prototype.getHeight=function(){return this._zr.getHeight()},e.prototype.getDevicePixelRatio=function(){return this._zr.painter.dpr||Vv&&window.devicePixelRatio||1},e.prototype.getRenderedCanvas=function(t){if(a.canvasSupported)return t=t||{},this._zr.painter.getRenderedCanvas({backgroundColor:t.backgroundColor||this._model.get(\"backgroundColor\"),pixelRatio:t.pixelRatio||this.getDevicePixelRatio()})},e.prototype.getSvgDataURL=function(){if(a.svgSupported){var t=this._zr;return P(t.storage.getDisplayList(),(function(t){t.stopAnimation(null,!0)})),t.painter.toDataURL()}},e.prototype.getDataURL=function(t){if(!this._disposed){var e=(t=t||{}).excludeComponents,n=this._model,i=[],r=this;Rv(e,(function(t){n.eachComponent({mainType:t},(function(t){var e=r._componentsMap[t.__viewId];e.group.ignore||(i.push(e),e.group.ignore=!0)}))}));var o=\"svg\"===this._zr.painter.getType()?this.getSvgDataURL():this.getRenderedCanvas(t).toDataURL(\"image/\"+(t&&t.type||\"png\"));return Rv(i,(function(t){t.group.ignore=!1})),o}_m(this.id)},e.prototype.getConnectedDataURL=function(t){if(this._disposed)_m(this.id);else if(a.canvasSupported){var e=\"svg\"===t.type,n=this.group,i=Math.min,r=Math.max,o=1/0;if(Lm[n]){var s=o,l=o,u=-1/0,h=-1/0,c=[],p=t&&t.pixelRatio||this.getDevicePixelRatio();P(Am,(function(o,a){if(o.group===n){var p=e?o.getZr().painter.getSvgDom().innerHTML:o.getRenderedCanvas(w(t)),d=o.getDom().getBoundingClientRect();s=i(d.left,s),l=i(d.top,l),u=r(d.right,u),h=r(d.bottom,h),c.push({dom:p,left:d.left,top:d.top})}}));var d=(u*=p)-(s*=p),f=(h*=p)-(l*=p),g=C(),y=Hi(g,{renderer:e?\"svg\":\"canvas\"});if(y.resize({width:d,height:f}),e){var v=\"\";return Rv(c,(function(t){var e=t.left-s,n=t.top-l;v+='<g transform=\"translate('+e+\",\"+n+')\">'+t.dom+\"</g>\"})),y.painter.getSvgRoot().innerHTML=v,t.connectedBackgroundColor&&y.painter.setBackgroundColor(t.connectedBackgroundColor),y.refreshImmediately(),y.painter.toDataURL()}return t.connectedBackgroundColor&&y.add(new ls({shape:{x:0,y:0,width:d,height:f},style:{fill:t.connectedBackgroundColor}})),Rv(c,(function(t){var e=new es({style:{x:t.left*p-s,y:t.top*p-l,image:t.dom}});y.add(e)})),y.refreshImmediately(),g.toDataURL(\"image/\"+(t&&t.type||\"png\"))}return this.getDataURL(t)}},e.prototype.convertToPixel=function(t,e){return $v(this,\"convertToPixel\",t,e)},e.prototype.convertFromPixel=function(t,e){return $v(this,\"convertFromPixel\",t,e)},e.prototype.containPixel=function(t,e){var n;if(!this._disposed)return P(Or(this._model,t),(function(t,i){i.indexOf(\"Models\")>=0&&P(t,(function(t){var r=t.coordinateSystem;if(r&&r.containPoint)n=n||!!r.containPoint(e);else if(\"seriesModels\"===i){var o=this._chartsMap[t.__viewId];o&&o.containPoint&&(n=n||o.containPoint(e,t))}else 0}),this)}),this),!!n;_m(this.id)},e.prototype.getVisual=function(t,e){var n=Or(this._model,t,{defaultMainType:\"series\"}),i=n.seriesModel;var r=i.getData(),o=n.hasOwnProperty(\"dataIndexInside\")?n.dataIndexInside:n.hasOwnProperty(\"dataIndex\")?r.indexOfRawIndex(n.dataIndex):null;return null!=o?vg(r,o,e):mg(r,e)},e.prototype.getViewOfComponentModel=function(t){return this._componentsMap[t.__viewId]},e.prototype.getViewOfSeriesModel=function(t){return this._chartsMap[t.__viewId]},e.prototype._initEvents=function(){var t,e,n,i=this;Rv(mm,(function(t){var e=function(e){var n,r=i.getModel(),o=e.target,a=\"globalout\"===t;if(a?n={}:o&&iy(o,(function(t){var e=_s(t);if(e&&null!=e.dataIndex){var i=e.dataModel||r.getSeriesByIndex(e.seriesIndex);return n=i&&i.getDataParams(e.dataIndex,e.dataType)||{},!0}if(e.eventData)return n=I({},e.eventData),!0}),!0),n){var s=n.componentType,l=n.componentIndex;\"markLine\"!==s&&\"markPoint\"!==s&&\"markArea\"!==s||(s=\"series\",l=n.seriesIndex);var u=s&&null!=l&&r.getComponent(s,l),h=u&&i[\"series\"===u.mainType?\"_chartsMap\":\"_componentsMap\"][u.__viewId];0,n.event=e,n.type=t,i._$eventProcessor.eventInfo={targetEl:o,packedEvent:n,model:u,view:h},i.trigger(t,n)}};e.zrEventfulCallAtLast=!0,i._zr.on(t,e,i)})),Rv(bm,(function(t,e){i._messageCenter.on(e,(function(t){this.trigger(e,t)}),i)})),Rv([\"selectchanged\"],(function(t){i._messageCenter.on(t,(function(e){this.trigger(t,e)}),i)})),t=this._messageCenter,e=this,n=this._api,t.on(\"selectchanged\",(function(t){var i=n.getModel();t.isFromClick?(ny(\"map\",\"selectchanged\",e,i,t),ny(\"pie\",\"selectchanged\",e,i,t)):\"select\"===t.fromAction?(ny(\"map\",\"selected\",e,i,t),ny(\"pie\",\"selected\",e,i,t)):\"unselect\"===t.fromAction&&(ny(\"map\",\"unselected\",e,i,t),ny(\"pie\",\"unselected\",e,i,t))}))},e.prototype.isDisposed=function(){return this._disposed},e.prototype.clear=function(){this._disposed?_m(this.id):this.setOption({series:[]},!0)},e.prototype.dispose=function(){if(this._disposed)_m(this.id);else{this._disposed=!0,Vr(this.getDom(),Om,\"\");var t=this._api,e=this._model;Rv(this._componentsViews,(function(n){n.dispose(e,t)})),Rv(this._chartsViews,(function(n){n.dispose(e,t)})),this._zr.dispose(),delete Am[this.id]}},e.prototype.resize=function(t){if(this._disposed)_m(this.id);else{this._zr.resize(t);var e=this._model;if(this._loadingFX&&this._loadingFX.resize(),e){var n=e.resetOption(\"media\"),i=t&&t.silent;this.__optionUpdated&&(null==i&&(i=this.__optionUpdated.silent),n=!0,this.__optionUpdated=!1),this.__flagInMainProcess=!0,n&&Zv(this),Kv.update.call(this,{type:\"resize\",animation:I({duration:0},t&&t.animation)}),this.__flagInMainProcess=!1,tm.call(this,i),em.call(this,i)}}},e.prototype.showLoading=function(t,e){if(this._disposed)_m(this.id);else if(zv(t)&&(e=t,t=\"\"),t=t||\"default\",this.hideLoading(),Dm[t]){var n=Dm[t](this._api,e),i=this._zr;this._loadingFX=n,i.add(n)}},e.prototype.hideLoading=function(){this._disposed?_m(this.id):(this._loadingFX&&this._zr.remove(this._loadingFX),this._loadingFX=null)},e.prototype.makeActionFromEvent=function(t){var e=I({},t);return e.type=bm[t.type],e},e.prototype.dispatchAction=function(t,e){if(this._disposed)_m(this.id);else if(zv(e)||(e={silent:!!e}),xm[t.type]&&this._model)if(this.__flagInMainProcess)this._pendingActions.push(t);else{var n=e.silent;Qv.call(this,t,n);var i=e.flush;i?this._zr.flush():!1!==i&&a.browser.weChat&&this._throttledZrFlush(),tm.call(this,n),em.call(this,n)}},e.prototype.updateLabelLayout=function(){var t=this._labelManager;t.updateLayoutConfig(this._api),t.layout(this._api),t.processLabelsOverall()},e.prototype.appendData=function(t){if(this._disposed)_m(this.id);else{var e=t.seriesIndex,n=this.getModel().getSeriesByIndex(e);0,n.appendData(t),this._scheduler.unfinished=!0,this.getZr().wakeUp()}},e.internalField=function(){function t(t){for(var e=[],n=t.currentStates,i=0;i<n.length;i++){var r=n[i];\"emphasis\"!==r&&\"blur\"!==r&&\"select\"!==r&&e.push(r)}t.selected&&t.states.select&&e.push(\"select\"),2===t.hoverState&&t.states.emphasis?e.push(\"emphasis\"):1===t.hoverState&&t.states.blur&&e.push(\"blur\"),t.useStates(e)}function e(t,e){t.preventAutoZ||i(e.group,t.get(\"z\")||0,t.get(\"zlevel\")||0,-1/0)}function i(t,e,n,r){var o=t.getTextContent(),a=t.getTextGuideLine();if(t.isGroup)for(var s=t.childrenRef(),l=0;l<s.length;l++)r=Math.max(i(s[l],e,n,r),r);else t.z=e,t.zlevel=n,r=Math.max(t.z2,r);if(o&&(o.z=e,o.zlevel=n,isFinite(r)&&(o.z2=r+2)),a){var u=t.textGuideLineConfig;a.z=e,a.zlevel=n,isFinite(r)&&(a.z2=r+(u&&u.showAbove?1:-1))}return r}function r(t,e){e.group.traverse((function(t){if(!Zu(t)){var e=t.getTextContent(),n=t.getTextGuideLine();t.stateTransition&&(t.stateTransition=null),e&&e.stateTransition&&(e.stateTransition=null),n&&n.stateTransition&&(n.stateTransition=null),t.hasState()?(t.prevStates=t.currentStates,t.clearStates()):t.prevStates&&(t.prevStates=null)}}))}function o(e,n){var i=e.getModel(\"stateAnimation\"),r=e.isAnimationEnabled(),o=i.get(\"duration\"),a=o>0?{duration:o,delay:i.get(\"delay\"),easing:i.get(\"easing\")}:null;n.group.traverse((function(e){if(e.states&&e.states.emphasis){if(Zu(e))return;if(e instanceof Ka&&function(t){var e=ws(t);e.normalFill=t.style.fill,e.normalStroke=t.style.stroke;var n=t.states.select||{};e.selectFill=n.style&&n.style.fill||null,e.selectStroke=n.style&&n.style.stroke||null}(e),e.__dirty){var n=e.prevStates;n&&e.useStates(n)}if(r){e.stateTransition=a;var i=e.getTextContent(),o=e.getTextGuideLine();i&&(i.stateTransition=a),o&&(o.stateTransition=a)}e.__dirty&&t(e)}}))}Zv=function(t){var e=t._scheduler;e.restorePipelines(t._model),e.prepareStageTasks(),jv(t,!0),jv(t,!1),e.plan()},jv=function(t,e){for(var n=t._model,i=t._scheduler,r=e?t._componentsViews:t._chartsViews,o=e?t._componentsMap:t._chartsMap,a=t._zr,s=t._api,l=0;l<r.length;l++)r[l].__alive=!1;function u(t){var l=t.__requireNewView;t.__requireNewView=!1;var u=\"_ec_\"+t.id+\"_\"+t.type,h=!l&&o[u];if(!h){var c=Wr(t.type),p=e?wf.getClass(c.main,c.sub):Tf.getClass(c.sub);0,(h=new p).init(n,s),o[u]=h,r.push(h),a.add(h.group)}t.__viewId=h.__id=u,h.__alive=!0,h.__model=t,h.group.__ecComponentInfo={mainType:t.mainType,index:t.componentIndex},!e&&i.prepareView(h,t,n,s)}e?n.eachComponent((function(t,e){\"series\"!==t&&u(e)})):n.eachSeries(u);for(l=0;l<r.length;){var h=r[l];h.__alive?l++:(!e&&h.renderTask.dispose(),a.remove(h.group),h.dispose(n,s),r.splice(l,1),o[h.__id]===h&&delete o[h.__id],h.__id=h.group.__ecComponentInfo=null)}},qv=function(t,e,n,i,r){var o=t._model;if(o.setUpdatePayload(n),i){var a={};a[i+\"Id\"]=n[i+\"Id\"],a[i+\"Index\"]=n[i+\"Index\"],a[i+\"Name\"]=n[i+\"Name\"];var s={mainType:i,query:a};r&&(s.subType=r);var l,u=n.excludeSeriesId;null!=u&&(l=ht(),Rv(xr(u),(function(t){var e=Cr(t,null);null!=e&&l.set(e,!0)}))),gl(n)&&el(t._api),o&&o.eachComponent(s,(function(e){if(!l||null==l.get(e.id)){if(gl(n))if(e instanceof ff)n.type!==Ts||n.notBlur||function(t,e,n){var i=t.seriesIndex,r=t.getData(e.dataType),o=Lr(r,e);o=(F(o)?o[0]:o)||0;var a=r.getItemGraphicEl(o);if(!a)for(var s=r.count(),l=0;!a&&l<s;)a=r.getItemGraphicEl(l++);if(a){var u=_s(a);nl(i,u.focus,u.blurScope,n)}else{var h=t.get([\"emphasis\",\"focus\"]),c=t.get([\"emphasis\",\"blurScope\"]);null!=h&&nl(i,h,c,n)}}(e,n,t._api);else{var r=rl(e.mainType,e.componentIndex,n.name,t._api),o=r.focusSelf,a=r.dispatchers;n.type===Ts&&o&&!n.notBlur&&il(e.mainType,e.componentIndex,t._api),a&&Rv(a,(function(t){n.type===Ts?js(t):qs(t)}))}else fl(n)&&e instanceof ff&&(!function(t,e,n){if(fl(e)){var i=e.dataType,r=Lr(t.getData(i),e);F(r)||(r=[r]),t[e.type===Ls?\"toggleSelect\":e.type===Ds?\"select\":\"unselect\"](r,i)}}(e,n,t._api),ol(e),pm(t));h(t[\"series\"===i?\"_chartsMap\":\"_componentsMap\"][e.__viewId])}}),t)}else Rv([].concat(t._componentsViews).concat(t._chartsViews),h);function h(i){i&&i.__alive&&i[e]&&i[e](i.__model,o,t._api,n)}},Kv={prepareAndUpdate:function(t){Zv(this),Kv.update.call(this,t)},update:function(t){var e=this._model,n=this._api,i=this._zr,r=this._coordSysMgr,o=this._scheduler;if(e){e.setUpdatePayload(t),o.restoreData(e,t),o.performSeriesTasks(e),r.create(e,n),o.performDataProcessorTasks(e,t),Jv(this,e),r.update(e,n),rm(e),o.performVisualTasks(e,t),om(this,e,n,t);var s=e.get(\"backgroundColor\")||\"transparent\",l=e.get(\"darkMode\");if(a.canvasSupported)i.setBackgroundColor(s),null!=l&&\"auto\"!==l&&i.setDarkMode(l);else{var u=He(s);s=Je(u,\"rgb\"),0===u[3]&&(s=\"transparent\")}lm(e,n)}},updateTransform:function(t){var e=this,n=this._model,i=this._api;if(n){n.setUpdatePayload(t);var r=[];n.eachComponent((function(o,a){if(\"series\"!==o){var s=e.getViewOfComponentModel(a);if(s&&s.__alive)if(s.updateTransform){var l=s.updateTransform(a,n,i,t);l&&l.update&&r.push(s)}else r.push(s)}}));var o=ht();n.eachSeries((function(r){var a=e._chartsMap[r.__viewId];if(a.updateTransform){var s=a.updateTransform(r,n,i,t);s&&s.update&&o.set(r.uid,1)}else o.set(r.uid,1)})),rm(n),this._scheduler.performVisualTasks(n,t,{setDirty:!0,dirtyMap:o}),sm(this,n,i,t,o),lm(n,this._api)}},updateView:function(t){var e=this._model;e&&(e.setUpdatePayload(t),Tf.markUpdateMethod(t,\"updateView\"),rm(e),this._scheduler.performVisualTasks(e,t,{setDirty:!0}),om(this,this._model,this._api,t),lm(e,this._api))},updateVisual:function(t){var e=this,n=this._model;n&&(n.setUpdatePayload(t),n.eachSeries((function(t){t.getData().clearAllVisual()})),Tf.markUpdateMethod(t,\"updateVisual\"),rm(n),this._scheduler.performVisualTasks(n,t,{visualType:\"visual\",setDirty:!0}),n.eachComponent((function(i,r){if(\"series\"!==i){var o=e.getViewOfComponentModel(r);o&&o.__alive&&o.updateVisual(r,n,e._api,t)}})),n.eachSeries((function(i){e._chartsMap[i.__viewId].updateVisual(i,n,e._api,t)})),lm(n,this._api))},updateLayout:function(t){Kv.update.call(this,t)}},$v=function(t,e,n,i){if(t._disposed)_m(t.id);else{for(var r,o=t._model,a=t._coordSysMgr.getCoordinateSystems(),s=Or(o,n),l=0;l<a.length;l++){var u=a[l];if(u[e]&&null!=(r=u[e](o,s,i)))return r}0}},Jv=function(t,e){var n=t._chartsMap,i=t._scheduler;e.eachSeries((function(t){i.updateStreamModes(t,n[t.__viewId])}))},Qv=function(t,e){var n=this,i=this.getModel(),r=t.type,o=t.escapeConnect,a=xm[r],s=a.actionInfo,l=(s.update||\"update\").split(\":\"),u=l.pop(),h=null!=l[0]&&Wr(l[0]);this.__flagInMainProcess=!0;var c=[t],p=!1;t.batch&&(p=!0,c=O(t.batch,(function(e){return(e=T(I({},e),t)).batch=null,e})));var d,f=[],g=fl(t),y=gl(t);if(Rv(c,(function(e){if((d=(d=a.action(e,n._model,n._api))||I({},e)).type=s.event||d.type,f.push(d),y){var i=Rr(t),r=i.queryOptionMap,o=i.mainTypeSpecified?r.keys()[0]:\"series\";qv(n,u,e,o),pm(n)}else g?(qv(n,u,e,\"series\"),pm(n)):h&&qv(n,u,e,h.main,h.sub)})),\"none\"===u||y||g||h||(this.__optionUpdated?(Zv(this),Kv.update.call(this,t),this.__optionUpdated=!1):Kv[u].call(this,t)),d=p?{type:s.event||r,escapeConnect:o,batch:f}:f[0],this.__flagInMainProcess=!1,!e){var v=this._messageCenter;if(v.trigger(d.type,d),g){var m={type:\"selectchanged\",escapeConnect:o,selected:al(i),isFromClick:t.isFromClick||!1,fromAction:t.type,fromActionPayload:t};v.trigger(m.type,m)}}},tm=function(t){for(var e=this._pendingActions;e.length;){var n=e.shift();Qv.call(this,n,t)}},em=function(t){!t&&this.trigger(\"updated\")},nm=function(t,e){t.on(\"rendered\",(function(n){e.trigger(\"rendered\",n),!t.animation.isFinished()||e.__optionUpdated||e._scheduler.unfinished||e._pendingActions.length||e.trigger(\"finished\")}))},im=function(t,e){t.on(\"mouseover\",(function(t){var n=iy(t.target,dl);n&&(!function(t,e,n){var i=_s(t),r=rl(i.componentMainType,i.componentIndex,i.componentHighDownName,n),o=r.dispatchers,a=r.focusSelf;o?(a&&il(i.componentMainType,i.componentIndex,n),P(o,(function(t){return Ys(t,e)}))):(nl(i.seriesIndex,i.focus,i.blurScope,n),\"self\"===i.focus&&il(i.componentMainType,i.componentIndex,n),Ys(t,e))}(n,t,e._api),pm(e))})).on(\"mouseout\",(function(t){var n=iy(t.target,dl);n&&(!function(t,e,n){el(n);var i=_s(t),r=rl(i.componentMainType,i.componentIndex,i.componentHighDownName,n).dispatchers;r?P(r,(function(t){return Zs(t,e)})):Zs(t,e)}(n,t,e._api),pm(e))})).on(\"click\",(function(t){var n=iy(t.target,(function(t){return null!=_s(t).dataIndex}),!0);if(n){var i=n.selected?\"unselect\":\"select\",r=_s(n);e._api.dispatchAction({type:i,dataType:r.dataType,dataIndexInside:r.dataIndex,seriesIndex:r.seriesIndex,isFromClick:!0})}}))},rm=function(t){t.clearColorPalette(),t.eachSeries((function(t){t.clearColorPalette()}))},om=function(t,e,n,i){am(t,e,n,i),Rv(t._chartsViews,(function(t){t.__alive=!1})),sm(t,e,n,i),Rv(t._chartsViews,(function(t){t.__alive||t.remove(e,n)}))},am=function(t,n,i,a,s){Rv(s||t._componentsViews,(function(t){var s=t.__model;r(s,t),t.render(s,n,i,a),e(s,t),o(s,t)}))},sm=function(t,n,i,s,l){var u=t._scheduler,h=t._labelManager;h.clearLabels();var c=!1;n.eachSeries((function(e){var n=t._chartsMap[e.__viewId];n.__alive=!0;var i=n.renderTask;u.updatePayload(i,s),r(e,n),l&&l.get(e.uid)&&i.dirty(),i.perform(u.getPerformArgs(i))&&(c=!0),e.__transientTransitionOpt=null,n.group.silent=!!e.get(\"silent\"),function(t,e){var n=t.get(\"blendMode\")||null;0;e.group.traverse((function(t){t.isGroup||(t.style.blend=n),t.eachPendingDisplayable&&t.eachPendingDisplayable((function(t){t.style.blend=n}))}))}(e,n),ol(e),h.addLabelsOfSeries(n)})),u.unfinished=c||u.unfinished,h.updateLayoutConfig(i),h.layout(i),h.processLabelsOverall(),n.eachSeries((function(n){var i=t._chartsMap[n.__viewId];e(n,i),o(n,i)})),function(t,e){var n=t._zr.storage,i=0;n.traverse((function(t){t.isGroup||i++})),i>e.get(\"hoverLayerThreshold\")&&!a.node&&!a.worker&&e.eachSeries((function(e){if(!e.preventUsingHoverLayer){var n=t._chartsMap[e.__viewId];n.__alive&&n.group.traverse((function(t){t.states.emphasis&&(t.states.emphasis.hoverLayer=!0)}))}}))}(t,n)},lm=function(t,e){Rv(Im,(function(n){n(t,e)}))},pm=function(t){t.__needsUpdateStatus=!0,t.getZr().wakeUp()},dm=function(e){e.__needsUpdateStatus&&(e.getZr().storage.traverse((function(e){Zu(e)||t(e)})),e.__needsUpdateStatus=!1)},um=function(t){return new(function(e){function i(){return null!==e&&e.apply(this,arguments)||this}return n(i,e),i.prototype.getCoordinateSystems=function(){return t._coordSysMgr.getCoordinateSystems()},i.prototype.getComponentByElement=function(e){for(;e;){var n=e.__ecComponentInfo;if(null!=n)return t._model.getComponent(n.mainType,n.index);e=e.parent}},i.prototype.enterEmphasis=function(e,n){js(e,n),pm(t)},i.prototype.leaveEmphasis=function(e,n){qs(e,n),pm(t)},i.prototype.enterBlur=function(e){Ks(e),pm(t)},i.prototype.leaveBlur=function(e){$s(e),pm(t)},i.prototype.enterSelect=function(e){Js(e),pm(t)},i.prototype.leaveSelect=function(e){Qs(e),pm(t)},i.prototype.getModel=function(){return t.getModel()},i.prototype.getViewOfComponentModel=function(e){return t.getViewOfComponentModel(e)},i.prototype.getViewOfSeriesModel=function(e){return t.getViewOfSeriesModel(e)},i}(Cp))(t)},hm=function(t){function e(t,e){for(var n=0;n<t.length;n++){t[n][Wv]=e}}Rv(bm,(function(n,i){t._messageCenter.on(i,(function(n){if(Lm[t.group]&&0!==t[Wv]){if(n&&n.escapeConnect)return;var i=t.makeActionFromEvent(n),r=[];Rv(Am,(function(e){e!==t&&e.group===t.group&&r.push(e)})),e(r,0),Rv(r,(function(t){1!==t[Wv]&&t.dispatchAction(i)})),e(r,2)}}))}))},cm=function(t,e){var n=t._model;P(xr(e),(function(t){var e,i=t.from,r=t.to;null==r&&vr(e);var o={includeMainTypes:[\"series\"],enableAll:!1,enableNone:!1},a=i?Or(n,i,o):null,s=Or(n,r,o).seriesModel;null==s&&(e=\"\"),a&&a.seriesModel!==s&&(e=\"\"),null!=e&&vr(e),s.__transientTransitionOpt={from:i?i.dimension:null,to:r.dimension,dividingMethod:t.dividingMethod}}))}}(),e}(Ft),vm=ym.prototype;vm.on=Uv(\"on\"),vm.off=Uv(\"off\"),vm.one=function(t,e,n){var i=this;yr(),this.on.call(this,t,(function n(){for(var r=[],o=0;o<arguments.length;o++)r[o]=arguments[o];e&&e.apply&&e.apply(this,r),i.off(t,n)}),n)};var mm=[\"click\",\"dblclick\",\"mouseover\",\"mouseout\",\"mousemove\",\"mousedown\",\"mouseup\",\"globalout\",\"contextmenu\"];function _m(t){0}var xm={},bm={},wm=[],Sm=[],Mm=[],Im=[],Tm=[],Cm={},Dm={},Am={},Lm={},km=+new Date-0,Pm=+new Date-0,Om=\"_echarts_instance_\";function Rm(t){Lm[t]=!1}var Nm=Rm;function zm(t){return Am[function(t,e){return t.getAttribute?t.getAttribute(e):t[e]}(t,Om)]}function Em(t,e){Cm[t]=e}function Vm(t){Ev(Sm,t)<0&&Sm.push(t)}function Bm(t,e){Zm(wm,t,e,2e3)}function Fm(t){Ev(Mm,t)<0&&t&&Mm.push(t)}function Gm(t){Ev(Im,t)<0&&t&&Im.push(t)}function Hm(t,e,n){\"function\"==typeof e&&(n=e,e=\"\");var i=zv(t)?t.type:[t,t={event:e}][0];t.event=(t.event||i).toLowerCase(),e=t.event,bm[e]||(Ov(Hv.test(i)&&Hv.test(e)),xm[i]||(xm[i]={action:n,actionInfo:t}),bm[e]=i)}function Wm(t,e){Ap.register(t,e)}function Um(t,e){Zm(Tm,t,e,1e3,\"layout\")}function Xm(t,e){Zm(Tm,t,e,3e3,\"visual\")}var Ym=[];function Zm(t,e,n,i,r){if((Nv(e)||zv(e))&&(n=e,e=i),!(Ev(Ym,n)>=0)){Ym.push(n);var o=Zf.wrapStageHandler(n,r);o.__prio=e,o.__raw=n,t.push(o)}}function jm(t,e){Dm[t]=e}function qm(t,e,n){Av(t,e,n)}var Km=function(t){var e=(t=w(t)).type,n=\"\";e||vr(n);var i=e.split(\":\");2!==i.length&&vr(n);var r=!1;\"echarts\"===i[0]&&(e=i[1],r=!0),t.__isBuiltIn=r,Ud.set(e,t)};Xm(Bv,Hf),Xm(Fv,Uf),Xm(Fv,Xf),Xm(Bv,yg),Xm(Fv,{createOnAllSeries:!0,performRawSeries:!0,reset:function(t,e){if(t.hasSymbolVisual&&!e.isSeriesFiltered(t))return{dataEach:t.getData().hasItemOption?function(t,e){var n=t.getItemModel(e),i=n.getShallow(\"symbol\",!0),r=n.getShallow(\"symbolSize\",!0),o=n.getShallow(\"symbolRotate\",!0),a=n.getShallow(\"symbolOffset\",!0),s=n.getShallow(\"symbolKeepAspect\",!0);null!=i&&t.setItemVisual(e,\"symbol\",i),null!=r&&t.setItemVisual(e,\"symbolSize\",r),null!=o&&t.setItemVisual(e,\"symbolRotate\",o),null!=a&&t.setItemVisual(e,\"symbolOffset\",a),null!=s&&t.setItemVisual(e,\"symbolKeepAspect\",s)}:null}}}),Xm(7e3,(function(t,e){t.eachRawSeries((function(n){if(!t.isSeriesFiltered(n)){var i=n.getData();i.hasItemVisual()&&i.each((function(t){var n=i.getItemVisual(t,\"decal\");n&&(i.ensureUniqueItemVisual(t,\"style\").decal=Ey(n,e))}));var r=i.getVisual(\"decal\");if(r)i.getVisual(\"style\").decal=Ey(r,e)}}))})),Vm(Jp),Bm(900,(function(t){var e=ht();t.eachSeries((function(t){var n=t.get(\"stack\");if(n){var i=e.get(n)||e.set(n,[]),r=t.getData(),o={stackResultDimension:r.getCalculationInfo(\"stackResultDimension\"),stackedOverDimension:r.getCalculationInfo(\"stackedOverDimension\"),stackedDimension:r.getCalculationInfo(\"stackedDimension\"),stackedByDimension:r.getCalculationInfo(\"stackedByDimension\"),isStackedByIndex:r.getCalculationInfo(\"isStackedByIndex\"),data:r,seriesModel:t};if(!o.stackedDimension||!o.isStackedByIndex&&!o.stackedByDimension)return;i.length&&r.setCalculationInfo(\"stackedOnSeries\",i[i.length-1].seriesModel),i.push(o)}})),e.each(Qp)})),jm(\"default\",(function(t,e){T(e=e||{},{text:\"loading\",textColor:\"#000\",fontSize:12,fontWeight:\"normal\",fontStyle:\"normal\",fontFamily:\"sans-serif\",maskColor:\"rgba(255, 255, 255, 0.8)\",showSpinner:!0,color:\"#5470c6\",spinnerRadius:10,lineWidth:5,zlevel:0});var n=new Ei,i=new ls({style:{fill:e.maskColor},zlevel:e.zlevel,z:1e4});n.add(i);var r,o=new cs({style:{text:e.text,fill:e.textColor,fontSize:e.fontSize,fontWeight:e.fontWeight,fontStyle:e.fontStyle,fontFamily:e.fontFamily},zlevel:e.zlevel,z:10001}),a=new ls({style:{fill:\"none\"},textContent:o,textConfig:{position:\"right\",distance:10},zlevel:e.zlevel,z:10001});return n.add(a),e.showSpinner&&((r=new gu({shape:{startAngle:-Yf/2,endAngle:-Yf/2+.1,r:e.spinnerRadius},style:{stroke:e.color,lineCap:\"round\",lineWidth:e.lineWidth},zlevel:e.zlevel,z:10001})).animateShape(!0).when(1e3,{endAngle:3*Yf/2}).start(\"circularInOut\"),r.animateShape(!0).when(1e3,{startAngle:3*Yf/2}).delay(300).start(\"circularInOut\"),n.add(r)),n.resize=function(){var n=o.getBoundingRect().width,s=e.showSpinner?e.spinnerRadius:0,l=(t.getWidth()-2*s-(e.showSpinner&&n?10:0)-n)/2-(e.showSpinner&&n?0:5+n/2)+(e.showSpinner?0:n/2)+(n?0:s),u=t.getHeight()/2;e.showSpinner&&r.setShape({cx:l,cy:u}),a.setShape({x:l-s,y:u-s,width:2*s,height:2*s}),i.setShape({x:0,y:0,width:t.getWidth(),height:t.getHeight()})},n.resize(),n})),Hm({type:Ts,event:Ts,update:Ts},ft),Hm({type:Cs,event:Cs,update:Cs},ft),Hm({type:Ds,event:Ds,update:Ds},ft),Hm({type:As,event:As,update:As},ft),Hm({type:Ls,event:Ls,update:Ls},ft),Em(\"light\",ug),Em(\"dark\",fg);var $m=[],Jm={registerPreprocessor:Vm,registerProcessor:Bm,registerPostInit:Fm,registerPostUpdate:Gm,registerAction:Hm,registerCoordinateSystem:Wm,registerLayout:Um,registerVisual:Xm,registerTransform:Km,registerLoading:jm,registerMap:qm,PRIORITY:Gv,ComponentModel:Xc,ComponentView:wf,SeriesModel:ff,ChartView:Tf,registerComponentModel:function(t){Xc.registerClass(t)},registerComponentView:function(t){wf.registerClass(t)},registerSeriesModel:function(t){ff.registerClass(t)},registerChartView:function(t){Tf.registerClass(t)},registerSubTypeDefaulter:function(t,e){Xc.registerSubTypeDefaulter(t,e)},registerPainter:function(t,e){Wi(t,e)}};function Qm(t){F(t)?P(t,(function(t){Qm(t)})):D($m,t)>=0||($m.push(t),G(t)&&(t={install:t}),t.install(Jm))}function t_(t){return null==t?0:t.length||1}function e_(t){return t}var n_=function(){function t(t,e,n,i,r,o){this._old=t,this._new=e,this._oldKeyGetter=n||e_,this._newKeyGetter=i||e_,this.context=r,this._diffModeMultiple=\"multiple\"===o}return t.prototype.add=function(t){return this._add=t,this},t.prototype.update=function(t){return this._update=t,this},t.prototype.updateManyToOne=function(t){return this._updateManyToOne=t,this},t.prototype.updateOneToMany=function(t){return this._updateOneToMany=t,this},t.prototype.remove=function(t){return this._remove=t,this},t.prototype.execute=function(){this[this._diffModeMultiple?\"_executeMultiple\":\"_executeOneToOne\"]()},t.prototype._executeOneToOne=function(){var t=this._old,e=this._new,n={},i=new Array(t.length),r=new Array(e.length);this._initIndexMap(t,null,i,\"_oldKeyGetter\"),this._initIndexMap(e,n,r,\"_newKeyGetter\");for(var o=0;o<t.length;o++){var a=i[o],s=n[a],l=t_(s);if(l>1){var u=s.shift();1===s.length&&(n[a]=s[0]),this._update&&this._update(u,o)}else 1===l?(n[a]=null,this._update&&this._update(s,o)):this._remove&&this._remove(o)}this._performRestAdd(r,n)},t.prototype._executeMultiple=function(){var t=this._old,e=this._new,n={},i={},r=[],o=[];this._initIndexMap(t,n,r,\"_oldKeyGetter\"),this._initIndexMap(e,i,o,\"_newKeyGetter\");for(var a=0;a<r.length;a++){var s=r[a],l=n[s],u=i[s],h=t_(l),c=t_(u);if(h>1&&1===c)this._updateManyToOne&&this._updateManyToOne(u,l),i[s]=null;else if(1===h&&c>1)this._updateOneToMany&&this._updateOneToMany(u,l),i[s]=null;else if(1===h&&1===c)this._update&&this._update(u,l),i[s]=null;else if(h>1)for(var p=0;p<h;p++)this._remove&&this._remove(l[p]);else this._remove&&this._remove(l)}this._performRestAdd(o,i)},t.prototype._performRestAdd=function(t,e){for(var n=0;n<t.length;n++){var i=t[n],r=e[i],o=t_(r);if(o>1)for(var a=0;a<o;a++)this._add&&this._add(r[a]);else 1===o&&this._add&&this._add(r);e[i]=null}},t.prototype._initIndexMap=function(t,e,n,i){for(var r=this._diffModeMultiple,o=0;o<t.length;o++){var a=\"_ec_\"+this[i](t[o],o);if(r||(n[o]=a),e){var s=e[a],l=t_(s);0===l?(e[a]=o,r&&n.push(a)):1===l?e[a]=[s,o]:s.push(o)}}},t}();function i_(t,e){return t.hasOwnProperty(e)||(t[e]=[]),t[e]}function r_(t){return\"category\"===t?\"ordinal\":\"time\"===t?\"time\":\"float\"}var o_,a_,s_,l_,u_,h_,c_,p_,d_,f_,g_,y_,v_,m_,__=function(t){this.otherDims={},null!=t&&I(this,t)},x_=Math.floor,b_=X,w_=O,S_=\"undefined\",M_={float:typeof Float64Array===S_?Array:Float64Array,int:typeof Int32Array===S_?Array:Int32Array,ordinal:Array,number:Array,time:Array},I_=typeof Uint32Array===S_?Array:Uint32Array,T_=typeof Int32Array===S_?Array:Int32Array,C_=typeof Uint16Array===S_?Array:Uint16Array,D_=[\"hasItemOption\",\"_nameList\",\"_idList\",\"_invertedIndicesMap\",\"_rawData\",\"_dimValueGetter\",\"_count\",\"_rawCount\",\"_nameDimIdx\",\"_idDimIdx\",\"_nameRepeatCount\"],A_=[\"_extent\",\"_approximateExtent\",\"_rawExtent\"],L_=function(){function t(t,e){this.type=\"list\",this._count=0,this._rawCount=0,this._storage={},this._storageArr=[],this._nameList=[],this._idList=[],this._visual={},this._layout={},this._itemVisuals=[],this._itemLayouts=[],this._graphicEls=[],this._rawExtent={},this._extent={},this._approximateExtent={},this._calculationInfo={},this.hasItemOption=!0,this.TRANSFERABLE_METHODS=[\"cloneShallow\",\"downSample\",\"lttbDownSample\",\"map\"],this.CHANGABLE_METHODS=[\"filterSelf\",\"selectRange\"],this.DOWNSAMPLE_METHODS=[\"downSample\",\"lttbDownSample\"],this.getRawIndex=u_,t=t||[\"x\",\"y\"];for(var n={},i=[],r={},o=0;o<t.length;o++){var a=t[o],s=H(a)?new __({name:a}):a instanceof __?a:new __(a),l=s.name;s.type=s.type||\"float\",s.coordDim||(s.coordDim=l,s.coordDimIndex=0);var u=s.otherDims=s.otherDims||{};i.push(l),n[l]=s,s.index=o,s.createInvertedIndices&&(r[l]=[]),0===u.itemName&&(this._nameDimIdx=o,this._nameOrdinalMeta=s.ordinalMeta),0===u.itemId&&(this._idDimIdx=o,this._idOrdinalMeta=s.ordinalMeta)}this.dimensions=i,this._dimensionInfos=n,this.hostModel=e,this._dimensionsSummary=function(t){var e={},n=e.encode={},i=ht(),r=[],o=[],a=e.userOutput={dimensionNames:t.dimensions.slice(),encode:{}};P(t.dimensions,(function(e){var s,l=t.getDimensionInfo(e),u=l.coordDim;if(u){var h=l.coordDimIndex;i_(n,u)[h]=e,l.isExtraCoord||(i.set(u,1),\"ordinal\"!==(s=l.type)&&\"time\"!==s&&(r[0]=e),i_(a.encode,u)[h]=l.index),l.defaultTooltip&&o.push(e)}qc.each((function(t,e){var i=i_(n,e),r=l.otherDims[e];null!=r&&!1!==r&&(i[r]=l.name)}))}));var s=[],l={};i.each((function(t,e){var i=n[e];l[e]=i[0],s=s.concat(i)})),e.dataDimsOnCoord=s,e.encodeFirstDimNotExtra=l;var u=n.label;u&&u.length&&(r=u.slice());var h=n.tooltip;return h&&h.length?o=h.slice():o.length||(o=r.slice()),n.defaultedLabel=r,n.defaultedTooltip=o,e}(this),this._invertedIndicesMap=r,this.userOutput=this._dimensionsSummary.userOutput}return t.prototype.getDimension=function(t){return\"number\"!=typeof t&&(isNaN(t)||this._dimensionInfos.hasOwnProperty(t))||(t=this.dimensions[t]),t},t.prototype.getDimensionInfo=function(t){return this._dimensionInfos[this.getDimension(t)]},t.prototype.getDimensionsOnCoord=function(){return this._dimensionsSummary.dataDimsOnCoord.slice()},t.prototype.mapDimension=function(t,e){var n=this._dimensionsSummary;if(null==e)return n.encodeFirstDimNotExtra[t];var i=n.encode[t];return i?i[e]:null},t.prototype.mapDimensionsAll=function(t){return(this._dimensionsSummary.encode[t]||[]).slice()},t.prototype.initData=function(t,e,n){var i=ad(t)||k(t),r=i?new dd(t,this.dimensions.length):t;this._rawData=r;var o=r.getSource().sourceFormat;this._storage={},this._indices=null,this._dontMakeIdFromName=null!=this._idDimIdx||o===tp||!!r.fillStorage,this._nameList=(e||[]).slice(),this._idList=[],this._nameRepeatCount={},n||(this.hasItemOption=!1),this.defaultDimValueGetter=o_[o],this._dimValueGetter=n=n||this.defaultDimValueGetter,this._dimValueGetterArrayRows=o_.arrayRows,this._rawExtent={},this._initDataFromProvider(0,r.count()),r.pure&&(this.hasItemOption=!1)},t.prototype.getProvider=function(){return this._rawData},t.prototype.appendData=function(t){var e=this._rawData,n=this.count();e.appendData(t);var i=e.count();e.persistent||(i+=n),this._initDataFromProvider(n,i,!0)},t.prototype.appendValues=function(t,e){for(var n=this._storage,i=this.dimensions,r=i.length,o=this._rawExtent,a=this.count(),s=a+Math.max(t.length,e?e.length:0),l=0;l<r;l++){var u=i[l];o[u]||(o[u]=y_()),l_(n,this._dimensionInfos[u],s,!0)}for(var h=w_(i,(function(t){return o[t]})),c=this._storageArr=w_(i,(function(t){return n[t]})),p=[],d=a;d<s;d++){for(var f=d-a,g=0;g<r;g++){u=i[g];var y=this._dimValueGetterArrayRows(t[f]||p,u,f,g);c[g][d]=y;var v=h[g];y<v[0]&&(v[0]=y),y>v[1]&&(v[1]=y)}e&&(this._nameList[d]=e[f],this._dontMakeIdFromName||d_(this,d))}this._rawCount=this._count=s,this._extent={},a_(this)},t.prototype._initDataFromProvider=function(t,e,n){if(!(t>=e)){for(var i=this._rawData,r=this._storage,o=this.dimensions,a=o.length,s=this._dimensionInfos,l=this._nameList,u=this._idList,h=this._rawExtent,c=i.getSource().sourceFormat===Kc,p=0;p<a;p++){var d=o[p];h[d]||(h[d]=y_()),l_(r,s[d],e,n)}var f=this._storageArr=w_(o,(function(t){return r[t]})),g=w_(o,(function(t){return h[t]}));if(i.fillStorage)i.fillStorage(t,e,f,g);else for(var y=[],v=t;v<e;v++){y=i.getItem(v,y);for(var m=0;m<a;m++){d=o[m];var _=f[m],x=this._dimValueGetter(y,d,v,m);_[v]=x;var b=g[m];x<b[0]&&(b[0]=x),x>b[1]&&(b[1]=x)}if(c&&!i.pure&&y){var w=y.name;null==l[v]&&null!=w&&(l[v]=Cr(w,null));var S=y.id;null==u[v]&&null!=S&&(u[v]=Cr(S,null))}this._dontMakeIdFromName||d_(this,v)}!i.persistent&&i.clean&&i.clean(),this._rawCount=this._count=e,this._extent={},a_(this)}},t.prototype.count=function(){return this._count},t.prototype.getIndices=function(){var t,e=this._indices;if(e){var n=e.constructor,i=this._count;if(n===Array){t=new n(i);for(var r=0;r<i;r++)t[r]=e[r]}else t=new n(e.buffer,0,i)}else{t=new(n=s_(this))(this.count());for(r=0;r<t.length;r++)t[r]=r}return t},t.prototype.getByDimIdx=function(t,e){if(!(e>=0&&e<this._count))return NaN;var n=this._storageArr[t];return n?n[this.getRawIndex(e)]:NaN},t.prototype.get=function(t,e){if(!(e>=0&&e<this._count))return NaN;var n=this._storage[t];return n?n[this.getRawIndex(e)]:NaN},t.prototype.getByRawIndex=function(t,e){if(!(e>=0&&e<this._rawCount))return NaN;var n=this._storage[t];return n?n[e]:NaN},t.prototype.getValues=function(t,e){var n=[];F(t)||(e=t,t=this.dimensions);for(var i=0,r=t.length;i<r;i++)n.push(this.get(t[i],e));return n},t.prototype.hasValue=function(t){for(var e=this._dimensionsSummary.dataDimsOnCoord,n=0,i=e.length;n<i;n++)if(isNaN(this.get(e[n],t)))return!1;return!0},t.prototype.getDataExtent=function(t){t=this.getDimension(t);var e=this._storage[t],n=y_();if(!e)return n;var i,r=this.count();if(!this._indices)return this._rawExtent[t].slice();if(i=this._extent[t])return i.slice();for(var o=(i=n)[0],a=i[1],s=0;s<r;s++){var l=e[this.getRawIndex(s)];l<o&&(o=l),l>a&&(a=l)}return i=[o,a],this._extent[t]=i,i},t.prototype.getApproximateExtent=function(t){return t=this.getDimension(t),this._approximateExtent[t]||this.getDataExtent(t)},t.prototype.setApproximateExtent=function(t,e){e=this.getDimension(e),this._approximateExtent[e]=t.slice()},t.prototype.getCalculationInfo=function(t){return this._calculationInfo[t]},t.prototype.setCalculationInfo=function(t,e){b_(t)?I(this._calculationInfo,t):this._calculationInfo[t]=e},t.prototype.getSum=function(t){var e=0;if(this._storage[t])for(var n=0,i=this.count();n<i;n++){var r=this.get(t,n);isNaN(r)||(e+=r)}return e},t.prototype.getMedian=function(t){var e=[];this.each(t,(function(t){isNaN(t)||e.push(t)}));var n=e.sort((function(t,e){return t-e})),i=this.count();return 0===i?0:i%2==1?n[(i-1)/2]:(n[i/2]+n[i/2-1])/2},t.prototype.rawIndexOf=function(t,e){var n=t&&this._invertedIndicesMap[t];var i=n[e];return null==i||isNaN(i)?-1:i},t.prototype.indexOfName=function(t){for(var e=0,n=this.count();e<n;e++)if(this.getName(e)===t)return e;return-1},t.prototype.indexOfRawIndex=function(t){if(t>=this._rawCount||t<0)return-1;if(!this._indices)return t;var e=this._indices,n=e[t];if(null!=n&&n<this._count&&n===t)return t;for(var i=0,r=this._count-1;i<=r;){var o=(i+r)/2|0;if(e[o]<t)i=o+1;else{if(!(e[o]>t))return o;r=o-1}}return-1},t.prototype.indicesOfNearest=function(t,e,n){var i=this._storage[t],r=[];if(!i)return r;null==n&&(n=1/0);for(var o=1/0,a=-1,s=0,l=0,u=this.count();l<u;l++){var h=e-i[this.getRawIndex(l)],c=Math.abs(h);c<=n&&((c<o||c===o&&h>=0&&a<0)&&(o=c,a=h,s=0),h===a&&(r[s++]=l))}return r.length=s,r},t.prototype.getRawDataItem=function(t){if(this._rawData.persistent)return this._rawData.getItem(this.getRawIndex(t));for(var e=[],n=0;n<this.dimensions.length;n++){var i=this.dimensions[n];e.push(this.get(i,t))}return e},t.prototype.getName=function(t){var e=this.getRawIndex(t),n=this._nameList[e];return null==n&&null!=this._nameDimIdx&&(n=p_(this,this._nameDimIdx,this._nameOrdinalMeta,e)),null==n&&(n=\"\"),n},t.prototype.getId=function(t){return c_(this,this.getRawIndex(t))},t.prototype.each=function(t,e,n,i){var r=this;if(this._count){\"function\"==typeof t&&(i=n,n=e,e=t,t=[]);var o=n||i||this,a=w_(f_(t),this.getDimension,this);0;for(var s=a.length,l=w_(a,(function(t){return r._dimensionInfos[t].index})),u=this._storageArr,h=0,c=this.count();h<c;h++){var p=this.getRawIndex(h);switch(s){case 0:e.call(o,h);break;case 1:e.call(o,u[l[0]][p],h);break;case 2:e.call(o,u[l[0]][p],u[l[1]][p],h);break;default:for(var d=0,f=[];d<s;d++)f[d]=u[l[d]][p];f[d]=h,e.apply(o,f)}}}},t.prototype.filterSelf=function(t,e,n,i){var r=this;if(this._count){\"function\"==typeof t&&(i=n,n=e,e=t,t=[]);var o=n||i||this,a=w_(f_(t),this.getDimension,this);0;for(var s=this.count(),l=new(s_(this))(s),u=[],h=a.length,c=0,p=w_(a,(function(t){return r._dimensionInfos[t].index})),d=p[0],f=this._storageArr,g=0;g<s;g++){var y=void 0,v=this.getRawIndex(g);if(0===h)y=e.call(o,g);else if(1===h){var m=f[d][v];y=e.call(o,m,g)}else{for(var _=0;_<h;_++)u[_]=f[p[_]][v];u[_]=g,y=e.apply(o,u)}y&&(l[c++]=v)}return c<s&&(this._indices=l),this._count=c,this._extent={},this.getRawIndex=this._indices?h_:u_,this}},t.prototype.selectRange=function(t){var e=this,n=this._count;if(n){var i=[];for(var r in t)t.hasOwnProperty(r)&&i.push(r);0;var o=i.length;if(o){var a=this.count(),s=new(s_(this))(a),l=0,u=i[0],h=w_(i,(function(t){return e._dimensionInfos[t].index})),c=t[u][0],p=t[u][1],d=this._storageArr,f=!1;if(!this._indices){var g=0;if(1===o){for(var y=d[h[0]],v=0;v<n;v++){((b=y[v])>=c&&b<=p||isNaN(b))&&(s[l++]=g),g++}f=!0}else if(2===o){y=d[h[0]];var m=d[h[1]],_=t[i[1]][0],x=t[i[1]][1];for(v=0;v<n;v++){var b=y[v],w=m[v];(b>=c&&b<=p||isNaN(b))&&(w>=_&&w<=x||isNaN(w))&&(s[l++]=g),g++}f=!0}}if(!f)if(1===o)for(v=0;v<a;v++){var S=this.getRawIndex(v);((b=d[h[0]][S])>=c&&b<=p||isNaN(b))&&(s[l++]=S)}else for(v=0;v<a;v++){for(var M=!0,I=(S=this.getRawIndex(v),0);I<o;I++){var T=i[I];((b=d[h[I]][S])<t[T][0]||b>t[T][1])&&(M=!1)}M&&(s[l++]=this.getRawIndex(v))}return l<a&&(this._indices=s),this._count=l,this._extent={},this.getRawIndex=this._indices?h_:u_,this}}},t.prototype.mapArray=function(t,e,n,i){\"function\"==typeof t&&(i=n,n=e,e=t,t=[]),n=n||i||this;var r=[];return this.each(t,(function(){r.push(e&&e.apply(this,arguments))}),n),r},t.prototype.map=function(t,e,n,i){var r=n||i||this,o=w_(f_(t),this.getDimension,this);var a=g_(this,o),s=a._storage;a._indices=this._indices,a.getRawIndex=a._indices?h_:u_;for(var l=[],u=o.length,h=this.count(),c=[],p=a._rawExtent,d=0;d<h;d++){for(var f=0;f<u;f++)c[f]=this.get(o[f],d);c[u]=d;var g=e&&e.apply(r,c);if(null!=g){\"object\"!=typeof g&&(l[0]=g,g=l);for(var y=this.getRawIndex(d),v=0;v<g.length;v++){var m=o[v],_=g[v],x=p[m],b=s[m];b&&(b[y]=_),_<x[0]&&(x[0]=_),_>x[1]&&(x[1]=_)}}}return a},t.prototype.downSample=function(t,e,n,i){for(var r=g_(this,[t]),o=r._storage,a=[],s=x_(1/e),l=o[t],u=this.count(),h=r._rawExtent[t],c=new(s_(this))(u),p=0,d=0;d<u;d+=s){s>u-d&&(s=u-d,a.length=s);for(var f=0;f<s;f++){var g=this.getRawIndex(d+f);a[f]=l[g]}var y=n(a),v=this.getRawIndex(Math.min(d+i(a,y)||0,u-1));l[v]=y,y<h[0]&&(h[0]=y),y>h[1]&&(h[1]=y),c[p++]=v}return r._count=p,r._indices=c,r.getRawIndex=h_,r},t.prototype.lttbDownSample=function(t,e){var n,i,r,o=g_(this,[]),a=o._storage[t],s=this.count(),l=new(s_(this))(s),u=0,h=x_(1/e),c=this.getRawIndex(0);l[u++]=c;for(var p=1;p<s-1;p+=h){for(var d=Math.min(p+h,s-1),f=Math.min(p+2*h,s),g=(f+d)/2,y=0,v=d;v<f;v++){var m=a[S=this.getRawIndex(v)];isNaN(m)||(y+=m)}y/=f-d;var _=p,x=Math.min(p+h,s),b=p-1,w=a[c];n=-1,r=_;for(v=_;v<x;v++){var S;m=a[S=this.getRawIndex(v)];isNaN(m)||(i=Math.abs((b-g)*(m-w)-(b-v)*(y-w)))>n&&(n=i,r=S)}l[u++]=r,c=r}return l[u++]=this.getRawIndex(s-1),o._count=u,o._indices=l,o.getRawIndex=h_,o},t.prototype.getItemModel=function(t){var e=this.hostModel,n=this.getRawDataItem(t);return new Oh(n,e,e&&e.ecModel)},t.prototype.diff=function(t){var e=this;return new n_(t?t.getIndices():[],this.getIndices(),(function(e){return c_(t,e)}),(function(t){return c_(e,t)}))},t.prototype.getVisual=function(t){var e=this._visual;return e&&e[t]},t.prototype.setVisual=function(t,e){this._visual=this._visual||{},b_(t)?I(this._visual,t):this._visual[t]=e},t.prototype.getItemVisual=function(t,e){var n=this._itemVisuals[t],i=n&&n[e];return null==i?this.getVisual(e):i},t.prototype.hasItemVisual=function(){return this._itemVisuals.length>0},t.prototype.ensureUniqueItemVisual=function(t,e){var n=this._itemVisuals,i=n[t];i||(i=n[t]={});var r=i[e];return null==r&&(F(r=this.getVisual(e))?r=r.slice():b_(r)&&(r=I({},r)),i[e]=r),r},t.prototype.setItemVisual=function(t,e,n){var i=this._itemVisuals[t]||{};this._itemVisuals[t]=i,b_(e)?I(i,e):i[e]=n},t.prototype.clearAllVisual=function(){this._visual={},this._itemVisuals=[]},t.prototype.setLayout=function(t,e){if(b_(t))for(var n in t)t.hasOwnProperty(n)&&this.setLayout(n,t[n]);else this._layout[t]=e},t.prototype.getLayout=function(t){return this._layout[t]},t.prototype.getItemLayout=function(t){return this._itemLayouts[t]},t.prototype.setItemLayout=function(t,e,n){this._itemLayouts[t]=n?I(this._itemLayouts[t]||{},e):e},t.prototype.clearItemLayouts=function(){this._itemLayouts.length=0},t.prototype.setItemGraphicEl=function(t,e){var n=this.hostModel;if(e){var i=_s(e);i.dataIndex=t,i.dataType=this.dataType,i.seriesIndex=n&&n.seriesIndex,\"group\"===e.type&&e.traverse(v_,e)}this._graphicEls[t]=e},t.prototype.getItemGraphicEl=function(t){return this._graphicEls[t]},t.prototype.eachItemGraphicEl=function(t,e){P(this._graphicEls,(function(n,i){n&&t&&t.call(e,n,i)}))},t.prototype.cloneShallow=function(e){e||(e=new t(w_(this.dimensions,this.getDimensionInfo,this),this.hostModel));if(e._storage=this._storage,e._storageArr=this._storageArr,m_(e,this),this._indices){var n=this._indices.constructor;if(n===Array){var i=this._indices.length;e._indices=new n(i);for(var r=0;r<i;r++)e._indices[r]=this._indices[r]}else e._indices=new n(this._indices)}else e._indices=null;return e.getRawIndex=e._indices?h_:u_,e},t.prototype.wrapMethod=function(t,e){var n=this[t];\"function\"==typeof n&&(this.__wrappedMethods=this.__wrappedMethods||[],this.__wrappedMethods.push(t),this[t]=function(){var t=n.apply(this,arguments);return e.apply(this,[t].concat(nt(arguments)))})},t.internalField=function(){function e(t,e,n,i){return kd(t[i],this._dimensionInfos[e])}o_={arrayRows:e,objectRows:function(t,e,n,i){return kd(t[e],this._dimensionInfos[e])},keyedColumns:e,original:function(t,e,n,i){var r=t&&(null==t.value?t:t.value);return!this._rawData.pure&&function(t){return X(t)&&!(t instanceof Array)}(t)&&(this.hasItemOption=!0),kd(r instanceof Array?r[i]:r,this._dimensionInfos[e])},typedArray:function(t,e,n,i){return t[i]}},a_=function(t){var e=t._invertedIndicesMap;P(e,(function(n,i){var r=t._dimensionInfos[i].ordinalMeta;if(r){n=e[i]=new T_(r.categories.length);for(var o=0;o<n.length;o++)n[o]=-1;for(o=0;o<t._count;o++)n[t.get(i,o)]=o}}))},p_=function(t,e,n,i){var r,o=t._storageArr[e];return o&&(r=o[i],n&&n.categories.length&&(r=n.categories[r])),Cr(r,null)},s_=function(t){return t._rawCount>65535?I_:C_},l_=function(t,e,n,i){var r=M_[e.type],o=e.name;if(i){var a=t[o],s=a&&a.length;if(s!==n){for(var l=new r(n),u=0;u<s;u++)l[u]=a[u];t[o]=l}}else t[o]=new r(n)},u_=function(t){return t},h_=function(t){return t<this._count&&t>=0?this._indices[t]:-1},c_=function(t,e){var n=t._idList[e];return null==n&&null!=t._idDimIdx&&(n=p_(t,t._idDimIdx,t._idOrdinalMeta,e)),null==n&&(n=\"e\\0\\0\"+e),n},f_=function(t){return F(t)||(t=null!=t?[t]:[]),t},function(t,e){for(var n=0;n<e.length;n++)t._dimensionInfos[e[n]]||console.error(\"Unkown dimension \"+e[n])},g_=function(e,n){var i=e.dimensions,r=new t(w_(i,e.getDimensionInfo,e),e.hostModel);m_(r,e);for(var o,a,s=r._storage={},l=e._storage,u=r._storageArr=[],h=0;h<i.length;h++){var c=i[h];l[c]&&(D(n,c)>=0?(s[c]=(o=l[c],a=void 0,(a=o.constructor)===Array?o.slice():new a(o)),r._rawExtent[c]=y_(),r._extent[c]=null):s[c]=l[c],u.push(s[c]))}return r},y_=function(){return[1/0,-1/0]},v_=function(t){var e=_s(t),n=_s(this);e.seriesIndex=n.seriesIndex,e.dataIndex=n.dataIndex,e.dataType=n.dataType},m_=function(t,e){P(D_.concat(e.__wrappedMethods||[]),(function(n){e.hasOwnProperty(n)&&(t[n]=e[n])})),t.__wrappedMethods=e.__wrappedMethods,P(A_,(function(n){t[n]=w(e[n])})),t._calculationInfo=I({},e._calculationInfo)},d_=function(t,e){var n=t._nameList,i=t._idList,r=t._nameDimIdx,o=t._idDimIdx,a=n[e],s=i[e];if(null==a&&null!=r&&(n[e]=a=p_(t,r,t._nameOrdinalMeta,e)),null==s&&null!=o&&(i[e]=s=p_(t,o,t._idOrdinalMeta,e)),null==s&&null!=a){var l=t._nameRepeatCount,u=l[a]=(l[a]||0)+1;s=a,u>1&&(s+=\"__ec__\"+u),i[e]=s}}}(),t}();function k_(t,e,n){ad(e)||(e=ld(e)),n=n||{},t=(t||[]).slice();for(var i=(n.dimsDef||[]).slice(),r=ht(),o=ht(),a=[],s=function(t,e,n,i){var r=Math.max(t.dimensionsDetectedCount||1,e.length,n.length,i||0);return P(e,(function(t){var e;X(t)&&(e=t.dimsDef)&&(r=Math.max(r,e.length))})),r}(e,t,i,n.dimCount),l=0;l<s;l++){var u=i[l],h=i[l]=I({},X(u)?u:{name:u}),c=h.name,p=a[l]=new __;null!=c&&null==r.get(c)&&(p.name=p.displayName=c,r.set(c,l)),null!=h.type&&(p.type=h.type),null!=h.displayName&&(p.displayName=h.displayName)}var d=n.encodeDef;!d&&n.encodeDefaulter&&(d=n.encodeDefaulter(e,s));var f=ht(d);f.each((function(t,e){var n=xr(t).slice();if(1===n.length&&!H(n[0])&&n[0]<0)f.set(e,!1);else{var i=f.set(e,[]);P(n,(function(t,n){var o=H(t)?r.get(t):t;null!=o&&o<s&&(i[n]=o,y(a[o],e,n))}))}}));var g=0;function y(t,e,n){null!=qc.get(e)?t.otherDims[e]=n:(t.coordDim=e,t.coordDimIndex=n,o.set(e,!0))}P(t,(function(t){var e,n,i,r;if(H(t))e=t,r={};else{e=(r=t).name;var o=r.ordinalMeta;r.ordinalMeta=null,(r=w(r)).ordinalMeta=o,n=r.dimsDef,i=r.otherDims,r.name=r.coordDim=r.coordDimIndex=r.dimsDef=r.otherDims=null}var s=f.get(e);if(!1!==s){if(!(s=xr(s)).length)for(var l=0;l<(n&&n.length||1);l++){for(;g<a.length&&null!=a[g].coordDim;)g++;g<a.length&&s.push(g++)}P(s,(function(t,o){var s=a[t];if(y(T(s,r),e,o),null==s.name&&n){var l=n[o];!X(l)&&(l={name:l}),s.name=s.displayName=l.name,s.defaultTooltip=l.defaultTooltip}i&&T(s.otherDims,i)}))}}));var v=n.generateCoord,m=n.generateCoordCount,_=null!=m;m=v?m||1:0;for(var x=v||\"value\",b=0;b<s;b++){null==(p=a[b]=a[b]||new __).coordDim&&(p.coordDim=P_(x,o,_),p.coordDimIndex=0,(!v||m<=0)&&(p.isExtraCoord=!0),m--),null==p.name&&(p.name=P_(p.coordDim,r,!1)),null!=p.type||cp(e,b)!==rp&&(!p.isExtraCoord||null==p.otherDims.itemName&&null==p.otherDims.seriesName)||(p.type=\"ordinal\")}return a}function P_(t,e,n){if(n||null!=e.get(t)){for(var i=0;null!=e.get(t+i);)i++;t+=i}return e.set(t,!0),t}function O_(t,e){return k_((e=e||{}).coordDimensions||[],t,{dimsDef:e.dimensionsDefine||t.dimensionsDefine,encodeDef:e.encodeDefine||t.encodeDefine,dimCount:e.dimensionsCount,encodeDefaulter:e.encodeDefaulter,generateCoord:e.generateCoord,generateCoordCount:e.generateCoordCount})}var R_=function(t){this.coordSysDims=[],this.axisMap=ht(),this.categoryAxisMap=ht(),this.coordSysName=t};var N_={cartesian2d:function(t,e,n,i){var r=t.getReferringComponents(\"xAxis\",Nr).models[0],o=t.getReferringComponents(\"yAxis\",Nr).models[0];e.coordSysDims=[\"x\",\"y\"],n.set(\"x\",r),n.set(\"y\",o),z_(r)&&(i.set(\"x\",r),e.firstCategoryDimIndex=0),z_(o)&&(i.set(\"y\",o),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=1))},singleAxis:function(t,e,n,i){var r=t.getReferringComponents(\"singleAxis\",Nr).models[0];e.coordSysDims=[\"single\"],n.set(\"single\",r),z_(r)&&(i.set(\"single\",r),e.firstCategoryDimIndex=0)},polar:function(t,e,n,i){var r=t.getReferringComponents(\"polar\",Nr).models[0],o=r.findAxisModel(\"radiusAxis\"),a=r.findAxisModel(\"angleAxis\");e.coordSysDims=[\"radius\",\"angle\"],n.set(\"radius\",o),n.set(\"angle\",a),z_(o)&&(i.set(\"radius\",o),e.firstCategoryDimIndex=0),z_(a)&&(i.set(\"angle\",a),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=1))},geo:function(t,e,n,i){e.coordSysDims=[\"lng\",\"lat\"]},parallel:function(t,e,n,i){var r=t.ecModel,o=r.getComponent(\"parallel\",t.get(\"parallelIndex\")),a=e.coordSysDims=o.dimensions.slice();P(o.parallelAxisIndex,(function(t,o){var s=r.getComponent(\"parallelAxis\",t),l=a[o];n.set(l,s),z_(s)&&(i.set(l,s),null==e.firstCategoryDimIndex&&(e.firstCategoryDimIndex=o))}))}};function z_(t){return\"category\"===t.get(\"type\")}function E_(t,e,n){var i,r,o,a,s=(n=n||{}).byIndex,l=n.stackedCoordDimension,u=!(!t||!t.get(\"stack\"));if(P(e,(function(t,n){H(t)&&(e[n]=t={name:t}),u&&!t.isExtraCoord&&(s||i||!t.ordinalMeta||(i=t),r||\"ordinal\"===t.type||\"time\"===t.type||l&&l!==t.coordDim||(r=t))})),!r||s||i||(s=!0),r){o=\"__\\0ecstackresult\",a=\"__\\0ecstackedover\",i&&(i.createInvertedIndices=!0);var h=r.coordDim,c=r.type,p=0;P(e,(function(t){t.coordDim===h&&p++})),e.push({name:o,coordDim:h,coordDimIndex:p,type:c,isExtraCoord:!0,isCalculationCoord:!0}),p++,e.push({name:a,coordDim:a,coordDimIndex:p,type:c,isExtraCoord:!0,isCalculationCoord:!0})}return{stackedDimension:r&&r.name,stackedByDimension:i&&i.name,isStackedByIndex:s,stackedOverDimension:a,stackResultDimension:o}}function V_(t,e){return!!e&&e===t.getCalculationInfo(\"stackedDimension\")}function B_(t,e){return V_(t,e)?t.getCalculationInfo(\"stackResultDimension\"):e}function F_(t,e,n){n=n||{},ad(t)||(t=ld(t));var i,r=e.get(\"coordinateSystem\"),o=Ap.get(r),a=function(t){var e=t.get(\"coordinateSystem\"),n=new R_(e),i=N_[e];if(i)return i(t,n,n.axisMap,n.categoryAxisMap),n}(e);a&&a.coordSysDims&&(i=O(a.coordSysDims,(function(t){var e={name:t},n=a.axisMap.get(t);if(n){var i=n.get(\"type\");e.type=r_(i)}return e}))),i||(i=o&&(o.getDimensionsInfo?o.getDimensionsInfo():o.dimensions.slice())||[\"x\",\"y\"]);var s,l,u=n.useEncodeDefaulter,h=O_(t,{coordDimensions:i,generateCoord:n.generateCoord,encodeDefaulter:G(u)?u:u?B(lp,i,e):null});a&&P(h,(function(t,e){var i=t.coordDim,r=a.categoryAxisMap.get(i);r&&(null==s&&(s=e),t.ordinalMeta=r.getOrdinalMeta(),n.createInvertedIndices&&(t.createInvertedIndices=!0)),null!=t.otherDims.itemName&&(l=!0)})),l||null==s||(h[s].otherDims.itemName=0);var c=E_(e,h),p=new L_(h,e);p.setCalculationInfo(c);var d=null!=s&&function(t){if(t.sourceFormat===Kc){var e=function(t){var e=0;for(;e<t.length&&null==t[e];)e++;return t[e]}(t.data||[]);return null!=e&&!F(Sr(e))}}(t)?function(t,e,n,i){return i===s?n:this.defaultDimValueGetter(t,e,n,i)}:null;return p.hasItemOption=!1,p.initData(t,null,d),p}var G_=function(){function t(t){this._setting=t||{},this._extent=[1/0,-1/0]}return t.prototype.getSetting=function(t){return this._setting[t]},t.prototype.unionExtent=function(t){var e=this._extent;t[0]<e[0]&&(e[0]=t[0]),t[1]>e[1]&&(e[1]=t[1])},t.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},t.prototype.getExtent=function(){return this._extent.slice()},t.prototype.setExtent=function(t,e){var n=this._extent;isNaN(t)||(n[0]=t),isNaN(e)||(n[1]=e)},t.prototype.isInExtentRange=function(t){return this._extent[0]<=t&&this._extent[1]>=t},t.prototype.isBlank=function(){return this._isBlank},t.prototype.setBlank=function(t){this._isBlank=t},t}();Kr(G_);var H_=function(){function t(t){this.categories=t.categories||[],this._needCollect=t.needCollect,this._deduplication=t.deduplication}return t.createByAxisModel=function(e){var n=e.option,i=n.data,r=i&&O(i,W_);return new t({categories:r,needCollect:!r,deduplication:!1!==n.dedplication})},t.prototype.getOrdinal=function(t){return this._getOrCreateMap().get(t)},t.prototype.parseAndCollect=function(t){var e,n=this._needCollect;if(\"string\"!=typeof t&&!n)return t;if(n&&!this._deduplication)return e=this.categories.length,this.categories[e]=t,e;var i=this._getOrCreateMap();return null==(e=i.get(t))&&(n?(e=this.categories.length,this.categories[e]=t,i.set(t,e)):e=NaN),e},t.prototype._getOrCreateMap=function(){return this._map||(this._map=ht(this.categories))},t}();function W_(t){return X(t)&&null!=t.value?t.value:t+\"\"}var U_=ji;function X_(t,e,n,i){var r={},o=t[1]-t[0],a=r.interval=lr(o/e,!0);null!=n&&a<n&&(a=r.interval=n),null!=i&&a>i&&(a=r.interval=i);var s=r.intervalPrecision=Y_(a);return function(t,e){!isFinite(t[0])&&(t[0]=e[0]),!isFinite(t[1])&&(t[1]=e[1]),Z_(t,0,e),Z_(t,1,e),t[0]>t[1]&&(t[0]=t[1])}(r.niceTickExtent=[U_(Math.ceil(t[0]/a)*a,s),U_(Math.floor(t[1]/a)*a,s)],t),r}function Y_(t){return Ki(t)+2}function Z_(t,e,n){t[e]=Math.max(Math.min(t[e],n[1]),n[0])}function j_(t,e){return t>=e[0]&&t<=e[1]}function q_(t,e){return e[1]===e[0]?.5:(t-e[0])/(e[1]-e[0])}function K_(t,e){return t*(e[1]-e[0])+e[0]}var $_=function(t){function e(e){var n=t.call(this,e)||this;n.type=\"ordinal\";var i=n.getSetting(\"ordinalMeta\");return i||(i=new H_({})),F(i)&&(i=new H_({categories:O(i,(function(t){return X(t)?t.value:t}))})),n._ordinalMeta=i,n._extent=n.getSetting(\"extent\")||[0,i.categories.length-1],n}return n(e,t),e.prototype.parse=function(t){return\"string\"==typeof t?this._ordinalMeta.getOrdinal(t):Math.round(t)},e.prototype.contain=function(t){return j_(t=this.parse(t),this._extent)&&null!=this._ordinalMeta.categories[t]},e.prototype.normalize=function(t){return q_(t=this._getTickNumber(this.parse(t)),this._extent)},e.prototype.scale=function(t){return t=Math.round(K_(t,this._extent)),this.getRawOrdinalNumber(t)},e.prototype.getTicks=function(){for(var t=[],e=this._extent,n=e[0];n<=e[1];)t.push({value:n}),n++;return t},e.prototype.getMinorTicks=function(t){},e.prototype.setSortInfo=function(t){if(null!=t){for(var e=t.ordinalNumbers,n=this._ordinalNumbersByTick=[],i=this._ticksByOrdinalNumber=[],r=0,o=this._ordinalMeta.categories.length,a=Math.min(o,e.length);r<a;++r){var s=e[r];n[r]=s,i[s]=r}for(var l=0;r<o;++r){for(;null!=i[l];)l++;n.push(l),i[l]=r}}else this._ordinalNumbersByTick=this._ticksByOrdinalNumber=null},e.prototype._getTickNumber=function(t){var e=this._ticksByOrdinalNumber;return e&&t>=0&&t<e.length?e[t]:t},e.prototype.getRawOrdinalNumber=function(t){var e=this._ordinalNumbersByTick;return e&&t>=0&&t<e.length?e[t]:t},e.prototype.getLabel=function(t){if(!this.isBlank()){var e=this.getRawOrdinalNumber(t.value),n=this._ordinalMeta.categories[e];return null==n?\"\":n+\"\"}},e.prototype.count=function(){return this._extent[1]-this._extent[0]+1},e.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},e.prototype.isInExtentRange=function(t){return t=this._getTickNumber(t),this._extent[0]<=t&&this._extent[1]>=t},e.prototype.getOrdinalMeta=function(){return this._ordinalMeta},e.prototype.niceTicks=function(){},e.prototype.niceExtent=function(){},e.type=\"ordinal\",e}(G_);G_.registerClass($_);var J_=ji,Q_=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"interval\",e._interval=0,e._intervalPrecision=2,e}return n(e,t),e.prototype.parse=function(t){return t},e.prototype.contain=function(t){return j_(t,this._extent)},e.prototype.normalize=function(t){return q_(t,this._extent)},e.prototype.scale=function(t){return K_(t,this._extent)},e.prototype.setExtent=function(t,e){var n=this._extent;isNaN(t)||(n[0]=parseFloat(t)),isNaN(e)||(n[1]=parseFloat(e))},e.prototype.unionExtent=function(t){var e=this._extent;t[0]<e[0]&&(e[0]=t[0]),t[1]>e[1]&&(e[1]=t[1]),this.setExtent(e[0],e[1])},e.prototype.getInterval=function(){return this._interval},e.prototype.setInterval=function(t){this._interval=t,this._niceExtent=this._extent.slice(),this._intervalPrecision=Y_(t)},e.prototype.getTicks=function(t){var e=this._interval,n=this._extent,i=this._niceExtent,r=this._intervalPrecision,o=[];if(!e)return o;n[0]<i[0]&&(t?o.push({value:J_(i[0]-e,r)}):o.push({value:n[0]}));for(var a=i[0];a<=i[1]&&(o.push({value:a}),(a=J_(a+e,r))!==o[o.length-1].value);)if(o.length>1e4)return[];var s=o.length?o[o.length-1].value:i[1];return n[1]>s&&(t?o.push({value:J_(s+e,r)}):o.push({value:n[1]})),o},e.prototype.getMinorTicks=function(t){for(var e=this.getTicks(!0),n=[],i=this.getExtent(),r=1;r<e.length;r++){for(var o=e[r],a=e[r-1],s=0,l=[],u=(o.value-a.value)/t;s<t-1;){var h=J_(a.value+(s+1)*u);h>i[0]&&h<i[1]&&l.push(h),s++}n.push(l)}return n},e.prototype.getLabel=function(t,e){if(null==t)return\"\";var n=e&&e.precision;return null==n?n=Ki(t.value)||0:\"auto\"===n&&(n=this._intervalPrecision),xc(J_(t.value,n,!0))},e.prototype.niceTicks=function(t,e,n){t=t||5;var i=this._extent,r=i[1]-i[0];if(isFinite(r)){r<0&&(r=-r,i.reverse());var o=X_(i,t,e,n);this._intervalPrecision=o.intervalPrecision,this._interval=o.interval,this._niceExtent=o.niceTickExtent}},e.prototype.niceExtent=function(t){var e=this._extent;if(e[0]===e[1])if(0!==e[0]){var n=e[0];t.fixMax||(e[1]+=n/2),e[0]-=n/2}else e[1]=1;var i=e[1]-e[0];isFinite(i)||(e[0]=0,e[1]=1),this.niceTicks(t.splitNumber,t.minInterval,t.maxInterval);var r=this._interval;t.fixMin||(e[0]=J_(Math.floor(e[0]/r)*r)),t.fixMax||(e[1]=J_(Math.ceil(e[1]/r)*r))},e.type=\"interval\",e}(G_);G_.registerClass(Q_);var tx=\"__ec_stack_\",ex=\"undefined\"!=typeof Float32Array?Float32Array:Array;function nx(t){return t.get(\"stack\")||tx+t.seriesIndex}function ix(t){return t.dim+t.index}function rx(t,e){var n=[];return e.eachSeriesByType(t,(function(t){hx(t)&&!cx(t)&&n.push(t)})),n}function ox(t){var e=function(t){var e={};P(t,(function(t){var n=t.coordinateSystem.getBaseAxis();if(\"time\"===n.type||\"value\"===n.type)for(var i=t.getData(),r=n.dim+\"_\"+n.index,o=i.mapDimension(n.dim),a=0,s=i.count();a<s;++a){var l=i.get(o,a);e[r]?e[r].push(l):e[r]=[l]}}));var n={};for(var i in e)if(e.hasOwnProperty(i)){var r=e[i];if(r){r.sort((function(t,e){return t-e}));for(var o=null,a=1;a<r.length;++a){var s=r[a]-r[a-1];s>0&&(o=null===o?s:Math.min(o,s))}n[i]=o}}return n}(t),n=[];return P(t,(function(t){var i,r=t.coordinateSystem.getBaseAxis(),o=r.getExtent();if(\"category\"===r.type)i=r.getBandWidth();else if(\"value\"===r.type||\"time\"===r.type){var a=r.dim+\"_\"+r.index,s=e[a],l=Math.abs(o[1]-o[0]),u=r.scale.getExtent(),h=Math.abs(u[1]-u[0]);i=s?l/h*s:l}else{var c=t.getData();i=Math.abs(o[1]-o[0])/c.count()}var p=Zi(t.get(\"barWidth\"),i),d=Zi(t.get(\"barMaxWidth\"),i),f=Zi(t.get(\"barMinWidth\")||1,i),g=t.get(\"barGap\"),y=t.get(\"barCategoryGap\");n.push({bandWidth:i,barWidth:p,barMaxWidth:d,barMinWidth:f,barGap:g,barCategoryGap:y,axisKey:ix(r),stackId:nx(t)})})),ax(n)}function ax(t){var e={};P(t,(function(t,n){var i=t.axisKey,r=t.bandWidth,o=e[i]||{bandWidth:r,remainedWidth:r,autoWidthCount:0,categoryGap:null,gap:\"20%\",stacks:{}},a=o.stacks;e[i]=o;var s=t.stackId;a[s]||o.autoWidthCount++,a[s]=a[s]||{width:0,maxWidth:0};var l=t.barWidth;l&&!a[s].width&&(a[s].width=l,l=Math.min(o.remainedWidth,l),o.remainedWidth-=l);var u=t.barMaxWidth;u&&(a[s].maxWidth=u);var h=t.barMinWidth;h&&(a[s].minWidth=h);var c=t.barGap;null!=c&&(o.gap=c);var p=t.barCategoryGap;null!=p&&(o.categoryGap=p)}));var n={};return P(e,(function(t,e){n[e]={};var i=t.stacks,r=t.bandWidth,o=t.categoryGap;if(null==o){var a=E(i).length;o=Math.max(35-4*a,15)+\"%\"}var s=Zi(o,r),l=Zi(t.gap,1),u=t.remainedWidth,h=t.autoWidthCount,c=(u-s)/(h+(h-1)*l);c=Math.max(c,0),P(i,(function(t){var e=t.maxWidth,n=t.minWidth;if(t.width){i=t.width;e&&(i=Math.min(i,e)),n&&(i=Math.max(i,n)),t.width=i,u-=i+l*i,h--}else{var i=c;e&&e<i&&(i=Math.min(e,u)),n&&n>i&&(i=n),i!==c&&(t.width=i,u-=i+l*i,h--)}})),c=(u-s)/(h+(h-1)*l),c=Math.max(c,0);var p,d=0;P(i,(function(t,e){t.width||(t.width=c),p=t,d+=t.width*(1+l)})),p&&(d-=p.width*l);var f=-d/2;P(i,(function(t,i){n[e][i]=n[e][i]||{bandWidth:r,offset:f,width:t.width},f+=t.width*(1+l)}))})),n}function sx(t,e,n){if(t&&e){var i=t[ix(e)];return null!=i&&null!=n?i[nx(n)]:i}}function lx(t,e){var n=rx(t,e),i=ox(n),r={};P(n,(function(t){var e=t.getData(),n=t.coordinateSystem,o=n.getBaseAxis(),a=nx(t),s=i[ix(o)][a],l=s.offset,u=s.width,h=n.getOtherAxis(o),c=t.get(\"barMinHeight\")||0;r[a]=r[a]||[],e.setLayout({bandWidth:s.bandWidth,offset:l,size:u});for(var p=e.mapDimension(h.dim),d=e.mapDimension(o.dim),f=V_(e,p),g=h.isHorizontal(),y=px(o,h),v=0,m=e.count();v<m;v++){var _=e.get(p,v),x=e.get(d,v),b=_>=0?\"p\":\"n\",w=y;f&&(r[a][x]||(r[a][x]={p:y,n:y}),w=r[a][x][b]);var S,M=void 0,I=void 0,T=void 0,C=void 0;if(g)M=w,I=(S=n.dataToPoint([_,x]))[1]+l,T=S[0]-y,C=u,Math.abs(T)<c&&(T=(T<0?-1:1)*c),isNaN(T)||f&&(r[a][x][b]+=T);else M=(S=n.dataToPoint([x,_]))[0]+l,I=w,T=u,C=S[1]-y,Math.abs(C)<c&&(C=(C<=0?-1:1)*c),isNaN(C)||f&&(r[a][x][b]+=C);e.setItemLayout(v,{x:M,y:I,width:T,height:C})}}))}var ux={seriesType:\"bar\",plan:Sf(),reset:function(t){if(hx(t)&&cx(t)){var e=t.getData(),n=t.coordinateSystem,i=n.master.getRect(),r=n.getBaseAxis(),o=n.getOtherAxis(r),a=e.mapDimension(o.dim),s=e.mapDimension(r.dim),l=o.isHorizontal(),u=l?0:1,h=sx(ox([t]),r,t).width;return h>.5||(h=.5),{progress:function(t,e){for(var c,p=t.count,d=new ex(2*p),f=new ex(2*p),g=new ex(p),y=[],v=[],m=0,_=0;null!=(c=t.next());)v[u]=e.get(a,c),v[1-u]=e.get(s,c),y=n.dataToPoint(v,null),f[m]=l?i.x+i.width:y[0],d[m++]=y[0],f[m]=l?y[1]:i.y+i.height,d[m++]=y[1],g[_++]=c;e.setLayout({largePoints:d,largeDataIndices:g,largeBackgroundPoints:f,barWidth:h,valueAxisStart:px(r,o),backgroundStart:l?i.x:i.y,valueAxisHorizontal:l})}}}}};function hx(t){return t.coordinateSystem&&\"cartesian2d\"===t.coordinateSystem.type}function cx(t){return t.pipelineContext&&t.pipelineContext.large}function px(t,e,n){return e.toGlobalCoord(e.dataToCoord(\"log\"===e.type?1:0))}var dx=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"time\",n}return n(e,t),e.prototype.getLabel=function(t){var e=this.getSetting(\"useUTC\");return ic(t.value,$h[function(t){switch(t){case\"year\":case\"month\":return\"day\";case\"millisecond\":return\"millisecond\";default:return\"second\"}}(ec(this._minLevelUnit))]||$h.second,e,this.getSetting(\"locale\"))},e.prototype.getFormattedLabel=function(t,e,n){var i=this.getSetting(\"useUTC\");return function(t,e,n,i,r){var o=null;if(\"string\"==typeof n)o=n;else if(\"function\"==typeof n)o=n(t.value,e,{level:t.level});else{var a=I({},qh);if(t.level>0)for(var s=0;s<Jh.length;++s)a[Jh[s]]=\"{primary|\"+a[Jh[s]]+\"}\";var l=n?!1===n.inherit?n:T(n,a):a,u=rc(t.value,r);if(l[u])o=l[u];else if(l.inherit){for(s=Qh.indexOf(u)-1;s>=0;--s)if(l[u]){o=l[u];break}o=o||a.none}if(F(o)){var h=null==t.level?0:t.level>=0?t.level:o.length+t.level;o=o[h=Math.min(h,o.length-1)]}}return ic(new Date(t.value),o,r,i)}(t,e,n,this.getSetting(\"locale\"),i)},e.prototype.getTicks=function(t){var e=this._interval,n=this._extent,i=[];if(!e)return i;i.push({value:n[0],level:0});var r=this.getSetting(\"useUTC\"),o=function(t,e,n,i){var r=1e4,o=Qh,a=0;function s(t,e,n,r,o,a,s){for(var l=new Date(e),u=e,h=l[r]();u<n&&u<=i[1];)s.push({value:u}),h+=t,l[o](h),u=l.getTime();s.push({value:u,notAdd:!0})}function l(t,r,o){var a=[],l=!r.length;if(!function(t,e,n,i){var r=or(e),o=or(n),a=function(t){return oc(r,t,i)===oc(o,t,i)},s=function(){return a(\"year\")},l=function(){return s()&&a(\"month\")},u=function(){return l()&&a(\"day\")},h=function(){return u()&&a(\"hour\")},c=function(){return h()&&a(\"minute\")},p=function(){return c()&&a(\"second\")},d=function(){return p()&&a(\"millisecond\")};switch(t){case\"year\":return s();case\"month\":return l();case\"day\":return u();case\"hour\":return h();case\"minute\":return c();case\"second\":return p();case\"millisecond\":return d()}}(ec(t),i[0],i[1],n)){l&&(r=[{value:xx(new Date(i[0]),t,n)},{value:i[1]}]);for(var u=0;u<r.length-1;u++){var h=r[u].value,c=r[u+1].value;if(h!==c){var p=void 0,d=void 0,f=void 0,g=!1;switch(t){case\"year\":p=Math.max(1,Math.round(e/Zh/365)),d=ac(n),f=dc(n);break;case\"half-year\":case\"quarter\":case\"month\":p=yx(e),d=sc(n),f=fc(n);break;case\"week\":case\"half-week\":case\"day\":p=gx(e),d=lc(n),f=gc(n),g=!0;break;case\"half-day\":case\"quarter-day\":case\"hour\":p=vx(e),d=uc(n),f=yc(n);break;case\"minute\":p=mx(e,!0),d=hc(n),f=vc(n);break;case\"second\":p=mx(e,!1),d=cc(n),f=mc(n);break;case\"millisecond\":p=_x(e),d=pc(n),f=_c(n)}s(p,h,c,d,f,g,a),\"year\"===t&&o.length>1&&0===u&&o.unshift({value:o[0].value-p})}}for(u=0;u<a.length;u++)o.push(a[u]);return a}}for(var u=[],h=[],c=0,p=0,d=0;d<o.length&&a++<r;++d){var f=ec(o[d]);if(nc(o[d]))if(l(o[d],u[u.length-1]||[],h),f!==(o[d+1]?ec(o[d+1]):null)){if(h.length){p=c,h.sort((function(t,e){return t.value-e.value}));for(var g=[],y=0;y<h.length;++y){var v=h[y].value;0!==y&&h[y-1].value===v||(g.push(h[y]),v>=i[0]&&v<=i[1]&&c++)}var m=(i[1]-i[0])/e;if(c>1.5*m&&p>m/1.5)break;if(u.push(g),c>m||t===o[d])break}h=[]}}0;var _=N(O(u,(function(t){return N(t,(function(t){return t.value>=i[0]&&t.value<=i[1]&&!t.notAdd}))})),(function(t){return t.length>0})),x=[],b=_.length-1;for(d=0;d<_.length;++d)for(var w=_[d],S=0;S<w.length;++S)x.push({value:w[S].value,level:b-d});x.sort((function(t,e){return t.value-e.value}));var M=[];for(d=0;d<x.length;++d)0!==d&&x[d].value===x[d-1].value||M.push(x[d]);return M}(this._minLevelUnit,this._approxInterval,r,n);return(i=i.concat(o)).push({value:n[1],level:0}),i},e.prototype.niceExtent=function(t){var e=this._extent;if(e[0]===e[1]&&(e[0]-=Zh,e[1]+=Zh),e[1]===-1/0&&e[0]===1/0){var n=new Date;e[1]=+new Date(n.getFullYear(),n.getMonth(),n.getDate()),e[0]=e[1]-Zh}this.niceTicks(t.splitNumber,t.minInterval,t.maxInterval)},e.prototype.niceTicks=function(t,e,n){t=t||10;var i=this._extent,r=i[1]-i[0];this._approxInterval=r/t,null!=e&&this._approxInterval<e&&(this._approxInterval=e),null!=n&&this._approxInterval>n&&(this._approxInterval=n);var o=fx.length,a=Math.min(function(t,e,n,i){for(;n<i;){var r=n+i>>>1;t[r][1]<e?n=r+1:i=r}return n}(fx,this._approxInterval,0,o),o-1);this._interval=fx[a][1],this._minLevelUnit=fx[Math.max(a-1,0)][0]},e.prototype.parse=function(t){return\"number\"==typeof t?t:+or(t)},e.prototype.contain=function(t){return j_(this.parse(t),this._extent)},e.prototype.normalize=function(t){return q_(this.parse(t),this._extent)},e.prototype.scale=function(t){return K_(t,this._extent)},e.type=\"time\",e}(Q_),fx=[[\"second\",Uh],[\"minute\",Xh],[\"hour\",Yh],[\"quarter-day\",216e5],[\"half-day\",432e5],[\"day\",10368e4],[\"half-week\",3024e5],[\"week\",6048e5],[\"month\",26784e5],[\"quarter\",8208e6],[\"half-year\",jh/2],[\"year\",jh]];function gx(t,e){return(t/=Zh)>16?16:t>7.5?7:t>3.5?4:t>1.5?2:1}function yx(t){return(t/=2592e6)>6?6:t>3?3:t>2?2:1}function vx(t){return(t/=Yh)>12?12:t>6?6:t>3.5?4:t>2?2:1}function mx(t,e){return(t/=e?Xh:Uh)>30?30:t>20?20:t>15?15:t>10?10:t>5?5:t>2?2:1}function _x(t){return lr(t,!0)}function xx(t,e,n){var i=new Date(t);switch(ec(e)){case\"year\":case\"month\":i[fc(n)](0);case\"day\":i[gc(n)](1);case\"hour\":i[yc(n)](0);case\"minute\":i[vc(n)](0);case\"second\":i[mc(n)](0),i[_c(n)](0)}return i.getTime()}G_.registerClass(dx);var bx=G_.prototype,Sx=Q_.prototype,Mx=ji,Ix=Math.floor,Tx=Math.ceil,Cx=Math.pow,Dx=Math.log,Ax=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"log\",e.base=10,e._originalScale=new Q_,e._interval=0,e}return n(e,t),e.prototype.getTicks=function(t){var e=this._originalScale,n=this._extent,i=e.getExtent();return O(Sx.getTicks.call(this,t),(function(t){var e=t.value,r=ji(Cx(this.base,e));return r=e===n[0]&&this._fixMin?kx(r,i[0]):r,{value:r=e===n[1]&&this._fixMax?kx(r,i[1]):r}}),this)},e.prototype.setExtent=function(t,e){var n=this.base;t=Dx(t)/Dx(n),e=Dx(e)/Dx(n),Sx.setExtent.call(this,t,e)},e.prototype.getExtent=function(){var t=this.base,e=bx.getExtent.call(this);e[0]=Cx(t,e[0]),e[1]=Cx(t,e[1]);var n=this._originalScale.getExtent();return this._fixMin&&(e[0]=kx(e[0],n[0])),this._fixMax&&(e[1]=kx(e[1],n[1])),e},e.prototype.unionExtent=function(t){this._originalScale.unionExtent(t);var e=this.base;t[0]=Dx(t[0])/Dx(e),t[1]=Dx(t[1])/Dx(e),bx.unionExtent.call(this,t)},e.prototype.unionExtentFromData=function(t,e){this.unionExtent(t.getApproximateExtent(e))},e.prototype.niceTicks=function(t){t=t||10;var e=this._extent,n=e[1]-e[0];if(!(n===1/0||n<=0)){var i=ar(n);for(t/n*i<=.5&&(i*=10);!isNaN(i)&&Math.abs(i)<1&&Math.abs(i)>0;)i*=10;var r=[ji(Tx(e[0]/i)*i),ji(Ix(e[1]/i)*i)];this._interval=i,this._niceExtent=r}},e.prototype.niceExtent=function(t){Sx.niceExtent.call(this,t),this._fixMin=t.fixMin,this._fixMax=t.fixMax},e.prototype.parse=function(t){return t},e.prototype.contain=function(t){return j_(t=Dx(t)/Dx(this.base),this._extent)},e.prototype.normalize=function(t){return q_(t=Dx(t)/Dx(this.base),this._extent)},e.prototype.scale=function(t){return t=K_(t,this._extent),Cx(this.base,t)},e.type=\"log\",e}(G_),Lx=Ax.prototype;function kx(t,e){return Mx(t,Ki(e))}Lx.getMinorTicks=Sx.getMinorTicks,Lx.getLabel=Sx.getLabel,G_.registerClass(Ax);var Px=function(){function t(t,e,n){this._prepareParams(t,e,n)}return t.prototype._prepareParams=function(t,e,n){n[1]<n[0]&&(n=[NaN,NaN]),this._dataMin=n[0],this._dataMax=n[1];var i=this._isOrdinal=\"ordinal\"===t.type;this._needCrossZero=e.getNeedCrossZero&&e.getNeedCrossZero();var r=this._modelMinRaw=e.get(\"min\",!0);G(r)?this._modelMinNum=zx(t,r({min:n[0],max:n[1]})):\"dataMin\"!==r&&(this._modelMinNum=zx(t,r));var o=this._modelMaxRaw=e.get(\"max\",!0);if(G(o)?this._modelMaxNum=zx(t,o({min:n[0],max:n[1]})):\"dataMax\"!==o&&(this._modelMaxNum=zx(t,o)),i)this._axisDataLen=e.getCategories().length;else{var a=e.get(\"boundaryGap\"),s=F(a)?a:[a||0,a||0];\"boolean\"==typeof s[0]||\"boolean\"==typeof s[1]?this._boundaryGapInner=[0,0]:this._boundaryGapInner=[Ii(s[0],1),Ii(s[1],1)]}},t.prototype.calculate=function(){var t=this._isOrdinal,e=this._dataMin,n=this._dataMax,i=this._axisDataLen,r=this._boundaryGapInner,o=t?null:n-e||Math.abs(e),a=\"dataMin\"===this._modelMinRaw?e:this._modelMinNum,s=\"dataMax\"===this._modelMaxRaw?n:this._modelMaxNum,l=null!=a,u=null!=s;null==a&&(a=t?i?0:NaN:e-r[0]*o),null==s&&(s=t?i?i-1:NaN:n+r[1]*o),(null==a||!isFinite(a))&&(a=NaN),(null==s||!isFinite(s))&&(s=NaN),a>s&&(a=NaN,s=NaN);var h=J(a)||J(s)||t&&!i;this._needCrossZero&&(a>0&&s>0&&!l&&(a=0),a<0&&s<0&&!u&&(s=0));var c=this._determinedMin,p=this._determinedMax;return null!=c&&(a=c,l=!0),null!=p&&(s=p,u=!0),{min:a,max:s,minFixed:l,maxFixed:u,isBlank:h}},t.prototype.modifyDataMinMax=function(t,e){this[Rx[t]]=e},t.prototype.setDeterminedMinMax=function(t,e){var n=Ox[t];this[n]=e},t.prototype.freeze=function(){this.frozen=!0},t}(),Ox={min:\"_determinedMin\",max:\"_determinedMax\"},Rx={min:\"_dataMin\",max:\"_dataMax\"};function Nx(t,e,n){var i=t.rawExtentInfo;return i||(i=new Px(t,e,n),t.rawExtentInfo=i,i)}function zx(t,e){return null==e?null:J(e)?NaN:t.parse(e)}function Ex(t,e){var n=t.type,i=Nx(t,e,t.getExtent()).calculate();t.setBlank(i.isBlank);var r=i.min,o=i.max,a=e.ecModel;if(a&&\"time\"===n){var s=rx(\"bar\",a),l=!1;if(P(s,(function(t){l=l||t.getBaseAxis()===e.axis})),l){var u=ox(s),h=function(t,e,n,i){var r=n.axis.getExtent(),o=r[1]-r[0],a=sx(i,n.axis);if(void 0===a)return{min:t,max:e};var s=1/0;P(a,(function(t){s=Math.min(t.offset,s)}));var l=-1/0;P(a,(function(t){l=Math.max(t.offset+t.width,l)})),s=Math.abs(s),l=Math.abs(l);var u=s+l,h=e-t,c=h/(1-(s+l)/o)-h;return{min:t-=c*(s/u),max:e+=c*(l/u)}}(r,o,e,u);r=h.min,o=h.max}}return{extent:[r,o],fixMin:i.minFixed,fixMax:i.maxFixed}}function Vx(t,e){var n=Ex(t,e),i=n.extent,r=e.get(\"splitNumber\");t instanceof Ax&&(t.base=e.get(\"logBase\"));var o=t.type;t.setExtent(i[0],i[1]),t.niceExtent({splitNumber:r,fixMin:n.fixMin,fixMax:n.fixMax,minInterval:\"interval\"===o||\"time\"===o?e.get(\"minInterval\"):null,maxInterval:\"interval\"===o||\"time\"===o?e.get(\"maxInterval\"):null});var a=e.get(\"interval\");null!=a&&t.setInterval&&t.setInterval(a)}function Bx(t,e){if(e=e||t.get(\"type\"))switch(e){case\"category\":return new $_({ordinalMeta:t.getOrdinalMeta?t.getOrdinalMeta():t.getCategories(),extent:[1/0,-1/0]});case\"time\":return new dx({locale:t.ecModel.getLocaleModel(),useUTC:t.ecModel.get(\"useUTC\")});default:return new(G_.getClass(e)||Q_)}}function Fx(t){var e,n,i=t.getLabelModel().get(\"formatter\"),r=\"category\"===t.type?t.scale.getExtent()[0]:null;return\"time\"===t.scale.type?(n=i,function(e,i){return t.scale.getFormattedLabel(e,i,n)}):\"string\"==typeof i?function(e){return function(n){var i=t.scale.getLabel(n);return e.replace(\"{value}\",null!=i?i:\"\")}}(i):\"function\"==typeof i?(e=i,function(n,i){return null!=r&&(i=n.value-r),e(Gx(t,n),i,null!=n.level?{level:n.level}:null)}):function(e){return t.scale.getLabel(e)}}function Gx(t,e){return\"category\"===t.type?t.scale.getLabel(e):e.value}function Hx(t,e){var n=e*Math.PI/180,i=t.width,r=t.height,o=i*Math.abs(Math.cos(n))+Math.abs(r*Math.sin(n)),a=i*Math.abs(Math.sin(n))+Math.abs(r*Math.cos(n));return new gi(t.x,t.y,o,a)}function Wx(t){var e=t.get(\"interval\");return null==e?\"auto\":e}function Ux(t){return\"category\"===t.type&&0===Wx(t.getLabelModel())}function Xx(t,e){var n={};return P(t.mapDimensionsAll(e),(function(e){n[B_(t,e)]=!0})),E(n)}var Yx=function(){function t(){}return t.prototype.getNeedCrossZero=function(){return!this.option.scale},t.prototype.getCoordSysModel=function(){},t}();var Zx={isDimensionStacked:V_,enableDataStack:E_,getStackedDimension:B_};var jx=Object.freeze({__proto__:null,createList:function(t){return F_(t.getSource(),t)},getLayoutRect:Vc,dataStack:Zx,createScale:function(t,e){var n=e;e instanceof Oh||(n=new Oh(e));var i=Bx(n);return i.setExtent(t[0],t[1]),Vx(i,n),i},mixinAxisModelCommonMethods:function(t){L(t,Yx)},getECData:_s,createTextStyle:function(t,e){return ph(t,null,null,\"normal\"!==(e=e||{}).state)},createDimensions:O_,createSymbol:fy,enableHoverEmphasis:sl}),qx=Object.freeze({__proto__:null,linearMap:Yi,round:ji,asc:qi,getPrecision:Ki,getPrecisionSafe:$i,getPixelPrecision:Ji,getPercentWithPrecision:Qi,MAX_SAFE_INTEGER:er,remRadian:nr,isRadianAroundZero:ir,parseDate:or,quantity:ar,quantityExponent:sr,nice:lr,quantile:ur,reformIntervals:hr,isNumeric:pr,numericToNumber:cr}),Kx=Object.freeze({__proto__:null,parse:or,format:ic}),$x=Object.freeze({__proto__:null,extendShape:Lu,extendPath:Pu,makePath:Nu,makeImage:zu,mergePath:Vu,resizePath:Bu,createIcon:eh,updateProps:Hu,initProps:Wu,getTransform:ju,clipPointsByRect:Qu,clipRectByRect:th,registerShape:Ou,getShapeClass:Ru,Group:Ei,Image:es,Text:cs,Circle:Nl,Ellipse:El,Sector:Jl,Ring:tu,Polygon:ru,Polyline:au,Rect:ls,Line:uu,BezierCurve:du,Arc:gu,IncrementalDisplayable:Tu,CompoundPath:yu,LinearGradient:mu,RadialGradient:_u,BoundingRect:gi}),Jx=Object.freeze({__proto__:null,addCommas:xc,toCamelCase:bc,normalizeCssArray:wc,encodeHTML:Ic,formatTpl:Ac,getTooltipMarker:Lc,formatTime:function(t,e,n){\"week\"!==t&&\"month\"!==t&&\"quarter\"!==t&&\"half-year\"!==t&&\"year\"!==t||(t=\"MM-dd\\nyyyy\");var i=or(e),r=n?\"UTC\":\"\",o=i[\"get\"+r+\"FullYear\"](),a=i[\"get\"+r+\"Month\"]()+1,s=i[\"get\"+r+\"Date\"](),l=i[\"get\"+r+\"Hours\"](),u=i[\"get\"+r+\"Minutes\"](),h=i[\"get\"+r+\"Seconds\"](),c=i[\"get\"+r+\"Milliseconds\"]();return t=t.replace(\"MM\",tc(a,2)).replace(\"M\",a).replace(\"yyyy\",o).replace(\"yy\",o%100+\"\").replace(\"dd\",tc(s,2)).replace(\"d\",s).replace(\"hh\",tc(l,2)).replace(\"h\",l).replace(\"mm\",tc(u,2)).replace(\"m\",u).replace(\"ss\",tc(h,2)).replace(\"s\",h).replace(\"SSS\",tc(c,3))},capitalFirst:function(t){return t?t.charAt(0).toUpperCase()+t.substr(1):t},truncateText:ao,getTextRect:function(t,e,n,i,r,o,a,s){return yr(),new cs({style:{text:t,font:e,align:n,verticalAlign:i,padding:r,rich:o,overflow:a?\"truncate\":null,lineHeight:s}}).getBoundingRect()}}),Qx=Object.freeze({__proto__:null,map:O,each:P,indexOf:D,inherits:A,reduce:R,filter:N,bind:V,curry:B,isArray:F,isString:H,isObject:X,isFunction:G,extend:I,defaults:T,clone:w,merge:S}),tb=kr();function eb(t){return\"category\"===t.type?function(t){var e=t.getLabelModel(),n=ib(t,e);return!e.get(\"show\")||t.scale.isBlank()?{labels:[],labelCategoryInterval:n.labelCategoryInterval}:n}(t):function(t){var e=t.scale.getTicks(),n=Fx(t);return{labels:O(e,(function(e,i){return{formattedLabel:n(e,i),rawLabel:t.scale.getLabel(e),tickValue:e.value}}))}}(t)}function nb(t,e){return\"category\"===t.type?function(t,e){var n,i,r=rb(t,\"ticks\"),o=Wx(e),a=ob(r,o);if(a)return a;e.get(\"show\")&&!t.scale.isBlank()||(n=[]);if(G(o))n=lb(t,o,!0);else if(\"auto\"===o){var s=ib(t,t.getLabelModel());i=s.labelCategoryInterval,n=O(s.labels,(function(t){return t.tickValue}))}else n=sb(t,i=o,!0);return ab(r,o,{ticks:n,tickCategoryInterval:i})}(t,e):{ticks:O(t.scale.getTicks(),(function(t){return t.value}))}}function ib(t,e){var n,i,r=rb(t,\"labels\"),o=Wx(e),a=ob(r,o);return a||(G(o)?n=lb(t,o):(i=\"auto\"===o?function(t){var e=tb(t).autoInterval;return null!=e?e:tb(t).autoInterval=t.calculateCategoryInterval()}(t):o,n=sb(t,i)),ab(r,o,{labels:n,labelCategoryInterval:i}))}function rb(t,e){return tb(t)[e]||(tb(t)[e]=[])}function ob(t,e){for(var n=0;n<t.length;n++)if(t[n].key===e)return t[n].value}function ab(t,e,n){return t.push({key:e,value:n}),n}function sb(t,e,n){var i=Fx(t),r=t.scale,o=r.getExtent(),a=t.getLabelModel(),s=[],l=Math.max((e||0)+1,1),u=o[0],h=r.count();0!==u&&l>1&&h/l>2&&(u=Math.round(Math.ceil(u/l)*l));var c=Ux(t),p=a.get(\"showMinLabel\")||c,d=a.get(\"showMaxLabel\")||c;p&&u!==o[0]&&g(o[0]);for(var f=u;f<=o[1];f+=l)g(f);function g(t){var e={value:t};s.push(n?t:{formattedLabel:i(e),rawLabel:r.getLabel(e),tickValue:t})}return d&&f-l!==o[1]&&g(o[1]),s}function lb(t,e,n){var i=t.scale,r=Fx(t),o=[];return P(i.getTicks(),(function(t){var a=i.getLabel(t),s=t.value;e(t.value,a)&&o.push(n?s:{formattedLabel:r(t),rawLabel:a,tickValue:s})})),o}var ub=[0,1],hb=function(){function t(t,e,n){this.onBand=!1,this.inverse=!1,this.dim=t,this.scale=e,this._extent=n||[0,0]}return t.prototype.contain=function(t){var e=this._extent,n=Math.min(e[0],e[1]),i=Math.max(e[0],e[1]);return t>=n&&t<=i},t.prototype.containData=function(t){return this.scale.contain(t)},t.prototype.getExtent=function(){return this._extent.slice()},t.prototype.getPixelPrecision=function(t){return Ji(t||this.scale.getExtent(),this._extent)},t.prototype.setExtent=function(t,e){var n=this._extent;n[0]=t,n[1]=e},t.prototype.dataToCoord=function(t,e){var n=this._extent,i=this.scale;return t=i.normalize(t),this.onBand&&\"ordinal\"===i.type&&cb(n=n.slice(),i.count()),Yi(t,ub,n,e)},t.prototype.coordToData=function(t,e){var n=this._extent,i=this.scale;this.onBand&&\"ordinal\"===i.type&&cb(n=n.slice(),i.count());var r=Yi(t,n,ub,e);return this.scale.scale(r)},t.prototype.pointToData=function(t,e){},t.prototype.getTicksCoords=function(t){var e=(t=t||{}).tickModel||this.getTickModel(),n=O(nb(this,e).ticks,(function(t){return{coord:this.dataToCoord(\"ordinal\"===this.scale.type?this.scale.getRawOrdinalNumber(t):t),tickValue:t}}),this);return function(t,e,n,i){var r=e.length;if(!t.onBand||n||!r)return;var o,a,s=t.getExtent();if(1===r)e[0].coord=s[0],o=e[1]={coord:s[0]};else{var l=e[r-1].tickValue-e[0].tickValue,u=(e[r-1].coord-e[0].coord)/l;P(e,(function(t){t.coord-=u/2})),a=1+t.scale.getExtent()[1]-e[r-1].tickValue,o={coord:e[r-1].coord+u*a},e.push(o)}var h=s[0]>s[1];c(e[0].coord,s[0])&&(i?e[0].coord=s[0]:e.shift());i&&c(s[0],e[0].coord)&&e.unshift({coord:s[0]});c(s[1],o.coord)&&(i?o.coord=s[1]:e.pop());i&&c(o.coord,s[1])&&e.push({coord:s[1]});function c(t,e){return t=ji(t),e=ji(e),h?t>e:t<e}}(this,n,e.get(\"alignWithLabel\"),t.clamp),n},t.prototype.getMinorTicksCoords=function(){if(\"ordinal\"===this.scale.type)return[];var t=this.model.getModel(\"minorTick\").get(\"splitNumber\");return t>0&&t<100||(t=5),O(this.scale.getMinorTicks(t),(function(t){return O(t,(function(t){return{coord:this.dataToCoord(t),tickValue:t}}),this)}),this)},t.prototype.getViewLabels=function(){return eb(this).labels},t.prototype.getLabelModel=function(){return this.model.getModel(\"axisLabel\")},t.prototype.getTickModel=function(){return this.model.getModel(\"axisTick\")},t.prototype.getBandWidth=function(){var t=this._extent,e=this.scale.getExtent(),n=e[1]-e[0]+(this.onBand?1:0);0===n&&(n=1);var i=Math.abs(t[1]-t[0]);return Math.abs(i)/n},t.prototype.calculateCategoryInterval=function(){return function(t){var e=function(t){var e=t.getLabelModel();return{axisRotate:t.getRotate?t.getRotate():t.isHorizontal&&!t.isHorizontal()?90:0,labelRotate:e.get(\"rotate\")||0,font:e.getFont()}}(t),n=Fx(t),i=(e.axisRotate-e.labelRotate)/180*Math.PI,r=t.scale,o=r.getExtent(),a=r.count();if(o[1]-o[0]<1)return 0;var s=1;a>40&&(s=Math.max(1,Math.floor(a/40)));for(var l=o[0],u=t.dataToCoord(l+1)-t.dataToCoord(l),h=Math.abs(u*Math.cos(i)),c=Math.abs(u*Math.sin(i)),p=0,d=0;l<=o[1];l+=s){var f,g,y=bi(n({value:l}),e.font,\"center\",\"top\");f=1.3*y.width,g=1.3*y.height,p=Math.max(p,f,7),d=Math.max(d,g,7)}var v=p/h,m=d/c;isNaN(v)&&(v=1/0),isNaN(m)&&(m=1/0);var _=Math.max(0,Math.floor(Math.min(v,m))),x=tb(t.model),b=t.getExtent(),w=x.lastAutoInterval,S=x.lastTickCount;return null!=w&&null!=S&&Math.abs(w-_)<=1&&Math.abs(S-a)<=1&&w>_&&x.axisExtent0===b[0]&&x.axisExtent1===b[1]?_=w:(x.lastTickCount=a,x.lastAutoInterval=_,x.axisExtent0=b[0],x.axisExtent1=b[1]),_}(this)},t}();function cb(t,e){var n=(t[1]-t[0])/e/2;t[0]+=n,t[1]-=n}function pb(t){return document.createElementNS(\"http://www.w3.org/2000/svg\",t)}function db(t,e,n,i,r){for(var o=e.length,a=n.length,s=t.newPos,l=s-i,u=0;s+1<o&&l+1<a&&r(e[s+1],n[l+1]);)s++,l++,u++;return u&&t.components.push({count:u,added:!1,removed:!1,indices:[]}),t.newPos=s,l}function fb(t,e,n){var i=t[t.length-1];i&&i.added===e&&i.removed===n?t[t.length-1]={count:i.count+1,added:e,removed:n,indices:[]}:t.push({count:1,added:e,removed:n,indices:[]})}function gb(t){for(var e=0,n=t.length,i=0,r=0;e<n;e++){var o=t[e];if(o.removed){for(s=r;s<r+o.count;s++)o.indices.push(s);r+=o.count}else{for(var a=[],s=i;s<i+o.count;s++)a.push(s);o.indices=a,i+=o.count,o.added||(r+=o.count)}}return t}function yb(t,e,n){return function(t,e,n){n||(n=function(t,e){return t===e}),t=t.slice();var i=(e=e.slice()).length,r=t.length,o=1,a=i+r,s=[{newPos:-1,components:[]}],l=db(s[0],e,t,0,n);if(s[0].newPos+1>=i&&l+1>=r){for(var u=[],h=0;h<e.length;h++)u.push(h);return[{indices:u,count:e.length,added:!1,removed:!1}]}function c(){for(var a=-1*o;a<=o;a+=2){var l,u=s[a-1],h=s[a+1],c=(h?h.newPos:0)-a;u&&(s[a-1]=void 0);var p=u&&u.newPos+1<i,d=h&&0<=c&&c<r;if(p||d){if(!p||d&&u.newPos<h.newPos?fb((l={newPos:(f=h).newPos,components:f.components.slice(0)}).components,!1,!0):((l=u).newPos++,fb(l.components,!0,!1)),c=db(l,e,t,a,n),l.newPos+1>=i&&c+1>=r)return gb(l.components);s[a]=l}else s[a]=void 0}var f;o++}for(;o<=a;){var p=c();if(p)return p}}(t,e,n)}var vb=\"none\",mb=Math.round,_b=Math.sin,xb=Math.cos,bb=Math.PI,wb=2*Math.PI,Sb=180/bb,Mb=1e-4;function Ib(t){return mb(1e3*t)/1e3}function Tb(t){return mb(1e4*t)/1e4}function Cb(t){return t<Mb&&t>-1e-4}function Db(t,e){e&&Ab(t,\"transform\",\"matrix(\"+Ib(e[0])+\",\"+Ib(e[1])+\",\"+Ib(e[2])+\",\"+Ib(e[3])+\",\"+Tb(e[4])+\",\"+Tb(e[5])+\")\")}function Ab(t,e,n){(!n||\"linear\"!==n.type&&\"radial\"!==n.type)&&t.setAttribute(e,n)}function Lb(t,e,n){var i=null==e.opacity?1:e.opacity;if(n instanceof es)t.style.opacity=i+\"\";else{if(function(t){var e=t.fill;return null!=e&&e!==vb}(e)){var r=e.fill;Ab(t,\"fill\",r=\"transparent\"===r?vb:r),Ab(t,\"fill-opacity\",(null!=e.fillOpacity?e.fillOpacity*i:i)+\"\")}else Ab(t,\"fill\",vb);if(function(t){var e=t.stroke;return null!=e&&e!==vb}(e)){var o=e.stroke;Ab(t,\"stroke\",o=\"transparent\"===o?vb:o);var a=e.lineWidth,s=e.strokeNoScale?n.getLineScale():1;Ab(t,\"stroke-width\",(s?a/s:0)+\"\"),Ab(t,\"paint-order\",e.strokeFirst?\"stroke\":\"fill\"),Ab(t,\"stroke-opacity\",(null!=e.strokeOpacity?e.strokeOpacity*i:i)+\"\");var l=e.lineDash&&a>0&&vy(e.lineDash,a);if(l){var u=e.lineDashOffset;s&&1!==s&&(l=O(l,(function(t){return t/s})),u&&(u=mb(u/=s))),Ab(t,\"stroke-dasharray\",l.join(\",\")),Ab(t,\"stroke-dashoffset\",(u||0)+\"\")}else Ab(t,\"stroke-dasharray\",\"\");e.lineCap&&Ab(t,\"stroke-linecap\",e.lineCap),e.lineJoin&&Ab(t,\"stroke-linejoin\",e.lineJoin),e.miterLimit&&Ab(t,\"stroke-miterlimit\",e.miterLimit+\"\")}else Ab(t,\"stroke\",vb)}}var kb=function(){function t(){}return t.prototype.reset=function(){this._d=[],this._str=\"\"},t.prototype.moveTo=function(t,e){this._add(\"M\",t,e)},t.prototype.lineTo=function(t,e){this._add(\"L\",t,e)},t.prototype.bezierCurveTo=function(t,e,n,i,r,o){this._add(\"C\",t,e,n,i,r,o)},t.prototype.quadraticCurveTo=function(t,e,n,i){this._add(\"Q\",t,e,n,i)},t.prototype.arc=function(t,e,n,i,r,o){this.ellipse(t,e,n,n,0,i,r,o)},t.prototype.ellipse=function(t,e,n,i,r,o,a,s){var l=0===this._d.length,u=a-o,h=!s,c=Math.abs(u),p=Cb(c-wb)||(h?u>=wb:-u>=wb),d=u>0?u%wb:u%wb+wb,f=!1;f=!!p||!Cb(c)&&d>=bb==!!h;var g=Tb(t+n*xb(o)),y=Tb(e+i*_b(o));p&&(u=h?wb-1e-4:1e-4-wb,f=!0,l&&this._d.push(\"M\",g,y));var v=Tb(t+n*xb(o+u)),m=Tb(e+i*_b(o+u));if(isNaN(g)||isNaN(y)||isNaN(n)||isNaN(i)||isNaN(r)||isNaN(Sb)||isNaN(v)||isNaN(m))return\"\";this._d.push(\"A\",Tb(n),Tb(i),mb(r*Sb),+f,+h,v,m)},t.prototype.rect=function(t,e,n,i){this._add(\"M\",t,e),this._add(\"L\",t+n,e),this._add(\"L\",t+n,e+i),this._add(\"L\",t,e+i),this._add(\"L\",t,e)},t.prototype.closePath=function(){this._d.length>0&&this._add(\"Z\")},t.prototype._add=function(t,e,n,i,r,o,a,s,l){this._d.push(t);for(var u=1;u<arguments.length;u++){var h=arguments[u];if(isNaN(h))return void(this._invalid=!0);this._d.push(Tb(h))}},t.prototype.generateStr=function(){this._str=this._invalid?\"\":this._d.join(\" \"),this._d=[]},t.prototype.getStr=function(){return this._str},t}(),Pb={brush:function(t){var e=t.style,n=t.__svgEl;n||(n=pb(\"path\"),t.__svgEl=n),t.path||t.createPathProxy();var i=t.path;t.shapeChanged()&&(i.beginPath(),t.buildPath(i,t.shape),t.pathUpdated());var r=i.getVersion(),o=t,a=o.__svgPathBuilder;(o.__svgPathVersion!==r||!a||t.style.strokePercent<1)&&(a||(a=o.__svgPathBuilder=new kb),a.reset(),i.rebuildPath(a,t.style.strokePercent),a.generateStr(),o.__svgPathVersion=r),Ab(n,\"d\",a.getStr()),Lb(n,e,t),Db(n,t.transform)}},Ob={brush:function(t){var e=t.style,n=e.image;if(n instanceof HTMLImageElement?n=n.src:n instanceof HTMLCanvasElement&&(n=n.toDataURL()),n){var i=e.x||0,r=e.y||0,o=e.width,a=e.height,s=t.__svgEl;s||(s=pb(\"image\"),t.__svgEl=s),n!==t.__imageSrc&&(!function(t,e,n){t.setAttributeNS(\"http://www.w3.org/1999/xlink\",e,n)}(s,\"href\",n),t.__imageSrc=n),Ab(s,\"width\",o+\"\"),Ab(s,\"height\",a+\"\"),Ab(s,\"x\",i+\"\"),Ab(s,\"y\",r+\"\"),Lb(s,e,t),Db(s,t.transform)}}},Rb={left:\"start\",right:\"end\",center:\"middle\",middle:\"middle\"};var Nb={brush:function(t){var e=t.style,n=e.text;if(null!=n&&(n+=\"\"),n&&!isNaN(e.x)&&!isNaN(e.y)){var i=t.__svgEl;i||(function(t,e,n){t.setAttributeNS(\"http://www.w3.org/XML/1998/namespace\",e,n)}(i=pb(\"text\"),\"xml:space\",\"preserve\"),t.__svgEl=i);var r=e.font||vi;i.style.font=r,i.textContent=n,Lb(i,e,t),Db(i,t.transform);var o=e.x||0,a=function(t,e,n){return\"top\"===n?t+=e/2:\"bottom\"===n&&(t-=e/2),t}(e.y||0,Mi(r),e.textBaseline),s=Rb[e.textAlign]||e.textAlign;Ab(i,\"dominant-baseline\",\"central\"),Ab(i,\"text-anchor\",s),Ab(i,\"x\",o+\"\"),Ab(i,\"y\",a+\"\")}}},zb=function(){function t(t,e,n,i,r){this.nextId=0,this._domName=\"_dom\",this.createElement=pb,this._zrId=t,this._svgRoot=e,this._tagNames=\"string\"==typeof n?[n]:n,this._markLabel=i,r&&(this._domName=r)}return t.prototype.getDefs=function(t){var e=this._svgRoot,n=this._svgRoot.getElementsByTagName(\"defs\");if(0===n.length){if(t){var i=e.insertBefore(this.createElement(\"defs\"),e.firstChild);return i.contains||(i.contains=function(t){var e=i.children;if(!e)return!1;for(var n=e.length-1;n>=0;--n)if(e[n]===t)return!0;return!1}),i}return null}return n[0]},t.prototype.doUpdate=function(t,e){if(t){var n=this.getDefs(!1);if(t[this._domName]&&n.contains(t[this._domName]))\"function\"==typeof e&&e(t);else{var i=this.add(t);i&&(t[this._domName]=i)}}},t.prototype.add=function(t){return null},t.prototype.addDom=function(t){var e=this.getDefs(!0);t.parentNode!==e&&e.appendChild(t)},t.prototype.removeDom=function(t){var e=this.getDefs(!1);e&&t[this._domName]&&(e.removeChild(t[this._domName]),t[this._domName]=null)},t.prototype.getDoms=function(){var t=this.getDefs(!1);if(!t)return[];var e=[];return P(this._tagNames,(function(n){for(var i=t.getElementsByTagName(n),r=0;r<i.length;r++)e.push(i[r])})),e},t.prototype.markAllUnused=function(){var t=this.getDoms(),e=this;P(t,(function(t){t[e._markLabel]=\"0\"}))},t.prototype.markDomUsed=function(t){t&&(t[this._markLabel]=\"1\")},t.prototype.markDomUnused=function(t){t&&(t[this._markLabel]=\"0\")},t.prototype.isDomUnused=function(t){return t&&\"1\"!==t[this._markLabel]},t.prototype.removeUnused=function(){var t=this,e=this.getDefs(!1);e&&P(this.getDoms(),(function(n){t.isDomUnused(n)&&e.removeChild(n)}))},t.prototype.getSvgProxy=function(t){return t instanceof Ka?Pb:t instanceof es?Ob:t instanceof Ja?Nb:Pb},t.prototype.getSvgElement=function(t){return t.__svgEl},t}();function Eb(t){return\"linear\"===t.type}function Vb(t){return\"radial\"===t.type}function Bb(t){return t&&(\"linear\"===t.type||\"radial\"===t.type)}var Fb=function(t){function e(e,n){return t.call(this,e,n,[\"linearGradient\",\"radialGradient\"],\"__gradient_in_use__\")||this}return n(e,t),e.prototype.addWithoutUpdate=function(t,e){if(e&&e.style){var n=this;P([\"fill\",\"stroke\"],(function(i){var r=e.style[i];if(Bb(r)){var o=r,a=n.getDefs(!0),s=void 0;o.__dom?(s=o.__dom,a.contains(o.__dom)||n.addDom(s)):s=n.add(o),n.markUsed(e);var l=s.getAttribute(\"id\");t.setAttribute(i,\"url(#\"+l+\")\")}}))}},e.prototype.add=function(t){var e;if(Eb(t))e=this.createElement(\"linearGradient\");else{if(!Vb(t))return b(\"Illegal gradient type.\"),null;e=this.createElement(\"radialGradient\")}return t.id=t.id||this.nextId++,e.setAttribute(\"id\",\"zr\"+this._zrId+\"-gradient-\"+t.id),this.updateDom(t,e),this.addDom(e),e},e.prototype.update=function(t){if(Bb(t)){var e=this;this.doUpdate(t,(function(){var n=t.__dom;if(n){var i=n.tagName,r=t.type;\"linear\"===r&&\"linearGradient\"===i||\"radial\"===r&&\"radialGradient\"===i?e.updateDom(t,t.__dom):(e.removeDom(t),e.add(t))}}))}},e.prototype.updateDom=function(t,e){if(Eb(t))e.setAttribute(\"x1\",t.x+\"\"),e.setAttribute(\"y1\",t.y+\"\"),e.setAttribute(\"x2\",t.x2+\"\"),e.setAttribute(\"y2\",t.y2+\"\");else{if(!Vb(t))return void b(\"Illegal gradient type.\");e.setAttribute(\"cx\",t.x+\"\"),e.setAttribute(\"cy\",t.y+\"\"),e.setAttribute(\"r\",t.r+\"\")}t.global?e.setAttribute(\"gradientUnits\",\"userSpaceOnUse\"):e.setAttribute(\"gradientUnits\",\"objectBoundingBox\"),e.innerHTML=\"\";for(var n=t.colorStops,i=0,r=n.length;i<r;++i){var o=this.createElement(\"stop\");o.setAttribute(\"offset\",100*n[i].offset+\"%\");var a=n[i].color;if(a.indexOf(\"rgba\")>-1){var s=He(a)[3],l=Xe(a);o.setAttribute(\"stop-color\",\"#\"+l),o.setAttribute(\"stop-opacity\",s+\"\")}else o.setAttribute(\"stop-color\",n[i].color);e.appendChild(o)}t.__dom=e},e.prototype.markUsed=function(e){if(e.style){var n=e.style.fill;n&&n.__dom&&t.prototype.markDomUsed.call(this,n.__dom),(n=e.style.stroke)&&n.__dom&&t.prototype.markDomUsed.call(this,n.__dom)}},e}(zb);function Gb(t){return t&&(!!t.image||!!t.svgElement)}var Hb=new oy,Wb=function(t){function e(e,n){return t.call(this,e,n,[\"pattern\"],\"__pattern_in_use__\")||this}return n(e,t),e.prototype.addWithoutUpdate=function(t,e){if(e&&e.style){var n=this;P([\"fill\",\"stroke\"],(function(i){var r=e.style[i];if(Gb(r)){var o=n.getDefs(!0),a=Hb.get(r);a?o.contains(a)||n.addDom(a):a=n.add(r),n.markUsed(e);var s=a.getAttribute(\"id\");t.setAttribute(i,\"url(#\"+s+\")\")}}))}},e.prototype.add=function(t){if(Gb(t)){var e=this.createElement(\"pattern\");return t.id=null==t.id?this.nextId++:t.id,e.setAttribute(\"id\",\"zr\"+this._zrId+\"-pattern-\"+t.id),e.setAttribute(\"x\",\"0\"),e.setAttribute(\"y\",\"0\"),e.setAttribute(\"patternUnits\",\"userSpaceOnUse\"),this.updateDom(t,e),this.addDom(e),e}},e.prototype.update=function(t){if(Gb(t)){var e=this;this.doUpdate(t,(function(){var n=Hb.get(t);e.updateDom(t,n)}))}},e.prototype.updateDom=function(t,e){var n=t.svgElement;if(n instanceof SVGElement)n.parentNode!==e&&(e.innerHTML=\"\",e.appendChild(n),e.setAttribute(\"width\",t.svgWidth+\"\"),e.setAttribute(\"height\",t.svgHeight+\"\"));else{var i=void 0,r=e.getElementsByTagName(\"image\");if(r.length){if(!t.image)return void e.removeChild(r[0]);i=r[0]}else t.image&&(i=this.createElement(\"image\"));if(i){var o=void 0,a=t.image;if(\"string\"==typeof a?o=a:a instanceof HTMLImageElement?o=a.src:a instanceof HTMLCanvasElement&&(o=a.toDataURL()),o){i.setAttribute(\"href\",o),i.setAttribute(\"x\",\"0\"),i.setAttribute(\"y\",\"0\");var s=no(o,i,{dirty:function(){}},(function(t){e.setAttribute(\"width\",t.width+\"\"),e.setAttribute(\"height\",t.height+\"\")}));s&&s.width&&s.height&&(e.setAttribute(\"width\",s.width+\"\"),e.setAttribute(\"height\",s.height+\"\")),e.appendChild(i)}}}var l=\"translate(\"+(t.x||0)+\", \"+(t.y||0)+\") rotate(\"+(t.rotation||0)/Math.PI*180+\") scale(\"+(t.scaleX||1)+\", \"+(t.scaleY||1)+\")\";e.setAttribute(\"patternTransform\",l),Hb.set(t,e)},e.prototype.markUsed=function(e){e.style&&(Gb(e.style.fill)&&t.prototype.markDomUsed.call(this,Hb.get(e.style.fill)),Gb(e.style.stroke)&&t.prototype.markDomUsed.call(this,Hb.get(e.style.stroke)))},e}(zb);function Ub(t){var e=t.__clipPaths;return e&&e.length>0}var Xb=function(t){function e(e,n){var i=t.call(this,e,n,\"clipPath\",\"__clippath_in_use__\")||this;return i._refGroups={},i._keyDuplicateCount={},i}return n(e,t),e.prototype.markAllUnused=function(){for(var e in t.prototype.markAllUnused.call(this),this._refGroups)this.markDomUnused(this._refGroups[e]);this._keyDuplicateCount={}},e.prototype._getClipPathGroup=function(t,e){if(Ub(t)){var n=t.__clipPaths,i=this._keyDuplicateCount,r=function(t){var e=[];if(t)for(var n=0;n<t.length;n++){var i=t[n];e.push(i.id)}return e.join(\",\")}(n);return yy(n,e&&e.__clipPaths)&&(i[r]=i[r]||0,i[r]&&(r+=\"-\"+i[r]),i[r]++),this._refGroups[r]||(this._refGroups[r]=this.createElement(\"g\"))}},e.prototype.update=function(t,e){var n=this._getClipPathGroup(t,e);return n&&(this.markDomUsed(n),this.updateDom(n,t.__clipPaths)),n},e.prototype.updateDom=function(t,e){if(e&&e.length>0){var n=this.getDefs(!0),i=e[0],r=void 0,o=void 0;i._dom?(o=i._dom.getAttribute(\"id\"),r=i._dom,n.contains(r)||n.appendChild(r)):(o=\"zr\"+this._zrId+\"-clip-\"+this.nextId,++this.nextId,(r=this.createElement(\"clipPath\")).setAttribute(\"id\",o),n.appendChild(r),i._dom=r),this.getSvgProxy(i).brush(i);var a=this.getSvgElement(i);r.innerHTML=\"\",r.appendChild(a),t.setAttribute(\"clip-path\",\"url(#\"+o+\")\"),e.length>1&&this.updateDom(r,e.slice(1))}else t&&t.setAttribute(\"clip-path\",\"none\")},e.prototype.markUsed=function(e){var n=this;e.__clipPaths&&P(e.__clipPaths,(function(e){e._dom&&t.prototype.markDomUsed.call(n,e._dom)}))},e.prototype.removeUnused=function(){t.prototype.removeUnused.call(this);var e={};for(var n in this._refGroups){var i=this._refGroups[n];this.isDomUnused(i)?i.parentNode&&i.parentNode.removeChild(i):e[n]=i}this._refGroups=e},e}(zb),Yb=function(t){function e(e,n){var i=t.call(this,e,n,[\"filter\"],\"__filter_in_use__\",\"_shadowDom\")||this;return i._shadowDomMap={},i._shadowDomPool=[],i}return n(e,t),e.prototype._getFromPool=function(){var t=this._shadowDomPool.pop();if(!t){(t=this.createElement(\"filter\")).setAttribute(\"id\",\"zr\"+this._zrId+\"-shadow-\"+this.nextId++);var e=this.createElement(\"feDropShadow\");t.appendChild(e),this.addDom(t)}return t},e.prototype.update=function(t,e){if(function(t){return t&&(t.shadowBlur||t.shadowOffsetX||t.shadowOffsetY)}(e.style)){var n=function(t){var e=t.style,n=t.getGlobalScale();return[e.shadowColor,(e.shadowBlur||0).toFixed(2),(e.shadowOffsetX||0).toFixed(2),(e.shadowOffsetY||0).toFixed(2),n[0],n[1]].join(\",\")}(e),i=e._shadowDom=this._shadowDomMap[n];i||(i=this._getFromPool(),this._shadowDomMap[n]=i),this.updateDom(t,e,i)}else this.remove(t,e)},e.prototype.remove=function(t,e){null!=e._shadowDom&&(e._shadowDom=null,t.style.filter=\"\")},e.prototype.updateDom=function(t,e,n){var i=n.children[0],r=e.style,o=e.getGlobalScale(),a=o[0],s=o[1];if(a&&s){var l=r.shadowOffsetX||0,u=r.shadowOffsetY||0,h=r.shadowBlur,c=r.shadowColor;i.setAttribute(\"dx\",l/a+\"\"),i.setAttribute(\"dy\",u/s+\"\"),i.setAttribute(\"flood-color\",c);var p=h/2/a+\" \"+h/2/s;i.setAttribute(\"stdDeviation\",p),n.setAttribute(\"x\",\"-100%\"),n.setAttribute(\"y\",\"-100%\"),n.setAttribute(\"width\",\"300%\"),n.setAttribute(\"height\",\"300%\"),e._shadowDom=n;var d=n.getAttribute(\"id\");t.style.filter=\"url(#\"+d+\")\"}},e.prototype.removeUnused=function(){if(this.getDefs(!1)){var t=this._shadowDomPool;for(var e in this._shadowDomMap){var n=this._shadowDomMap[e];t.push(n)}this._shadowDomMap={}}},e}(zb);function Zb(t){return parseInt(t,10)}function jb(t){return t instanceof Ka?Pb:t instanceof es?Ob:t instanceof Ja?Nb:Pb}function qb(t,e){return e&&t&&e.parentNode!==t}function Kb(t,e,n){if(qb(t,e)&&n){var i=n.nextSibling;i?t.insertBefore(e,i):t.appendChild(e)}}function $b(t,e){if(qb(t,e)){var n=t.firstChild;n?t.insertBefore(e,n):t.appendChild(e)}}function Jb(t,e){e&&t&&e.parentNode===t&&t.removeChild(e)}function Qb(t){return t.__svgEl}var tw=function(){function t(t,e,n,i){this.type=\"svg\",this.refreshHover=ew(\"refreshHover\"),this.pathToImage=ew(\"pathToImage\"),this.configLayer=ew(\"configLayer\"),this.root=t,this.storage=e,this._opts=n=I({},n||{});var r=pb(\"svg\");r.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\"xmlns\",\"http://www.w3.org/2000/svg\"),r.setAttributeNS(\"http://www.w3.org/2000/xmlns/\",\"xmlns:xlink\",\"http://www.w3.org/1999/xlink\"),r.setAttribute(\"version\",\"1.1\"),r.setAttribute(\"baseProfile\",\"full\"),r.style.cssText=\"user-select:none;position:absolute;left:0;top:0;\";var o=pb(\"g\");r.appendChild(o);var a=pb(\"g\");r.appendChild(a),this._gradientManager=new Fb(i,a),this._patternManager=new Wb(i,a),this._clipPathManager=new Xb(i,a),this._shadowManager=new Yb(i,a);var s=document.createElement(\"div\");s.style.cssText=\"overflow:hidden;position:relative\",this._svgDom=r,this._svgRoot=a,this._backgroundRoot=o,this._viewport=s,t.appendChild(s),s.appendChild(r),this.resize(n.width,n.height),this._visibleList=[]}return t.prototype.getType=function(){return\"svg\"},t.prototype.getViewportRoot=function(){return this._viewport},t.prototype.getSvgDom=function(){return this._svgDom},t.prototype.getSvgRoot=function(){return this._svgRoot},t.prototype.getViewportRootOffset=function(){var t=this.getViewportRoot();if(t)return{offsetLeft:t.offsetLeft||0,offsetTop:t.offsetTop||0}},t.prototype.refresh=function(){var t=this.storage.getDisplayList(!0);this._paintList(t)},t.prototype.setBackgroundColor=function(t){this._backgroundRoot&&this._backgroundNode&&this._backgroundRoot.removeChild(this._backgroundNode);var e=pb(\"rect\");e.setAttribute(\"width\",this.getWidth()),e.setAttribute(\"height\",this.getHeight()),e.setAttribute(\"x\",0),e.setAttribute(\"y\",0),e.setAttribute(\"id\",0),e.style.fill=t,this._backgroundRoot.appendChild(e),this._backgroundNode=e},t.prototype.createSVGElement=function(t){return pb(t)},t.prototype.paintOne=function(t){var e=jb(t);return e&&e.brush(t),Qb(t)},t.prototype._paintList=function(t){var e=this._gradientManager,n=this._patternManager,i=this._clipPathManager,r=this._shadowManager;e.markAllUnused(),n.markAllUnused(),i.markAllUnused(),r.markAllUnused();for(var o=this._svgRoot,a=this._visibleList,s=t.length,l=[],u=0;u<s;u++){var h=jb(x=t[u]),c=Qb(x);x.invisible||(!x.__dirty&&c||(h&&h.brush(x),(c=Qb(x))&&x.style&&(e.update(x.style.fill),e.update(x.style.stroke),n.update(x.style.fill),n.update(x.style.stroke),r.update(c,x)),x.__dirty=0),c&&l.push(x))}var p,d,f,g,y,v=yb(a,l);for(u=0;u<v.length;u++){if((_=v[u]).removed)for(var m=0;m<_.count;m++){c=Qb(x=a[_.indices[m]]);Ub(x)?(f=c)&&f.parentNode&&f.parentNode.removeChild(f):Jb(o,c)}}for(u=0;u<v.length;u++){var _;if(!(_=v[u]).removed)for(m=0;m<_.count;m++){var x=l[_.indices[m]],b=i.update(x,g);b!==y&&(p=d,b&&(p?Kb(o,b,p):$b(o,b),d=b,p=null),y=b);c=Qb(x);p?Kb(y||o,c,p):$b(y||o,c),p=c||p,y||(d=p),e.markUsed(x),e.addWithoutUpdate(c,x),n.markUsed(x),n.addWithoutUpdate(c,x),i.markUsed(x),g=x}}e.removeUnused(),n.removeUnused(),i.removeUnused(),r.removeUnused(),this._visibleList=l},t.prototype.resize=function(t,e){var n=this._viewport;n.style.display=\"none\";var i=this._opts;if(null!=t&&(i.width=t),null!=e&&(i.height=e),t=this._getSize(0),e=this._getSize(1),n.style.display=\"\",this._width!==t||this._height!==e){this._width=t,this._height=e;var r=n.style;r.width=t+\"px\",r.height=e+\"px\";var o=this._svgDom;o.setAttribute(\"width\",t+\"\"),o.setAttribute(\"height\",e+\"\")}this._backgroundNode&&(this._backgroundNode.setAttribute(\"width\",t),this._backgroundNode.setAttribute(\"height\",e))},t.prototype.getWidth=function(){return this._width},t.prototype.getHeight=function(){return this._height},t.prototype._getSize=function(t){var e=this._opts,n=[\"width\",\"height\"][t],i=[\"clientWidth\",\"clientHeight\"][t],r=[\"paddingLeft\",\"paddingTop\"][t],o=[\"paddingRight\",\"paddingBottom\"][t];if(null!=e[n]&&\"auto\"!==e[n])return parseFloat(e[n]);var a=this.root,s=document.defaultView.getComputedStyle(a);return(a[i]||Zb(s[n])||Zb(a.style[n]))-(Zb(s[r])||0)-(Zb(s[o])||0)|0},t.prototype.dispose=function(){this.root.innerHTML=\"\",this._svgRoot=this._backgroundRoot=this._svgDom=this._backgroundNode=this._viewport=this.storage=null},t.prototype.clear=function(){var t=this._viewport;t&&t.parentNode&&t.parentNode.removeChild(t)},t.prototype.toDataURL=function(){this.refresh();var t=this._svgDom,e=t.outerHTML||(t.parentNode&&t.parentNode).innerHTML;return\"data:image/svg+xml;charset=UTF-8,\"+encodeURIComponent(e.replace(/></g,\">\\n\\r<\"))},t}();function ew(t){return function(){b('In SVG mode painter not support method \"'+t+'\"')}}function nw(){return!1}function iw(t,e,n){var i=C(),r=e.getWidth(),o=e.getHeight(),a=i.style;return a&&(a.position=\"absolute\",a.left=\"0\",a.top=\"0\",a.width=r+\"px\",a.height=o+\"px\",i.setAttribute(\"data-zr-dom-id\",t)),i.width=r*n,i.height=o*n,i}var rw=function(t){function e(e,n,i){var r,o=t.call(this)||this;o.motionBlur=!1,o.lastFrameAlpha=.7,o.dpr=1,o.virtual=!1,o.config={},o.incremental=!1,o.zlevel=0,o.maxRepaintRectCount=5,o.__dirty=!0,o.__firstTimePaint=!0,o.__used=!1,o.__drawIndex=0,o.__startIndex=0,o.__endIndex=0,o.__prevStartIndex=null,o.__prevEndIndex=null,i=i||En,\"string\"==typeof e?r=iw(e,n,i):X(e)&&(e=(r=e).id),o.id=e,o.dom=r;var a=r.style;return a&&(r.onselectstart=nw,a.webkitUserSelect=\"none\",a.userSelect=\"none\",a.webkitTapHighlightColor=\"rgba(0,0,0,0)\",a[\"-webkit-touch-callout\"]=\"none\",a.padding=\"0\",a.margin=\"0\",a.borderWidth=\"0\"),o.domBack=null,o.ctxBack=null,o.painter=n,o.config=null,o.dpr=i,o}return n(e,t),e.prototype.getElementCount=function(){return this.__endIndex-this.__startIndex},e.prototype.afterBrush=function(){this.__prevStartIndex=this.__startIndex,this.__prevEndIndex=this.__endIndex},e.prototype.initContext=function(){this.ctx=this.dom.getContext(\"2d\"),this.ctx.dpr=this.dpr},e.prototype.setUnpainted=function(){this.__firstTimePaint=!0},e.prototype.createBackBuffer=function(){var t=this.dpr;this.domBack=iw(\"back-\"+this.id,this.painter,t),this.ctxBack=this.domBack.getContext(\"2d\"),1!==t&&this.ctxBack.scale(t,t)},e.prototype.createRepaintRects=function(t,e,n,i){if(this.__firstTimePaint)return this.__firstTimePaint=!1,null;var r,o=[],a=this.maxRepaintRectCount,s=!1,l=new gi(0,0,0,0);function u(t){if(t.isFinite()&&!t.isZero())if(0===o.length){(e=new gi(0,0,0,0)).copy(t),o.push(e)}else{for(var e,n=!1,i=1/0,r=0,u=0;u<o.length;++u){var h=o[u];if(h.intersect(t)){var c=new gi(0,0,0,0);c.copy(h),c.union(t),o[u]=c,n=!0;break}if(s){l.copy(t),l.union(h);var p=t.width*t.height,d=h.width*h.height,f=l.width*l.height-p-d;f<i&&(i=f,r=u)}}if(s&&(o[r].union(t),n=!0),!n)(e=new gi(0,0,0,0)).copy(t),o.push(e);s||(s=o.length>=a)}}for(var h=this.__startIndex;h<this.__endIndex;++h){if(d=t[h]){var c=d.shouldBePainted(n,i,!0,!0);(f=d.__isRendered&&(1&d.__dirty||!c)?d.getPrevPaintRect():null)&&u(f);var p=c&&(1&d.__dirty||!d.__isRendered)?d.getPaintRect():null;p&&u(p)}}for(h=this.__prevStartIndex;h<this.__prevEndIndex;++h){var d,f;c=(d=e[h]).shouldBePainted(n,i,!0,!0);if(d&&(!c||!d.__zr)&&d.__isRendered)(f=d.getPrevPaintRect())&&u(f)}do{r=!1;for(h=0;h<o.length;)if(o[h].isZero())o.splice(h,1);else{for(var g=h+1;g<o.length;)o[h].intersect(o[g])?(r=!0,o[h].union(o[g]),o.splice(g,1)):g++;h++}}while(r);return this._paintRects=o,o},e.prototype.debugGetPaintRects=function(){return(this._paintRects||[]).slice()},e.prototype.resize=function(t,e){var n=this.dpr,i=this.dom,r=i.style,o=this.domBack;r&&(r.width=t+\"px\",r.height=e+\"px\"),i.width=t*n,i.height=e*n,o&&(o.width=t*n,o.height=e*n,1!==n&&this.ctxBack.scale(n,n))},e.prototype.clear=function(t,e,n){var i=this.dom,r=this.ctx,o=i.width,a=i.height;e=e||this.clearColor;var s=this.motionBlur&&!t,l=this.lastFrameAlpha,u=this.dpr,h=this;s&&(this.domBack||this.createBackBuffer(),this.ctxBack.globalCompositeOperation=\"copy\",this.ctxBack.drawImage(i,0,0,o/u,a/u));var c=this.domBack;function p(t,n,i,o){if(r.clearRect(t,n,i,o),e&&\"transparent\"!==e){var a=void 0;q(e)?(a=e.__canvasGradient||gy(r,e,{x:0,y:0,width:i,height:o}),e.__canvasGradient=a):K(e)&&(a=Sy(r,e,{dirty:function(){h.setUnpainted(),h.__painter.refresh()}})),r.save(),r.fillStyle=a||e,r.fillRect(t,n,i,o),r.restore()}s&&(r.save(),r.globalAlpha=l,r.drawImage(c,t,n,i,o),r.restore())}!n||s?p(0,0,o,a):n.length&&P(n,(function(t){p(t.x*u,t.y*u,t.width*u,t.height*u)}))},e}(Ft),ow=1e5,aw=314159,sw=.01;function lw(t){return parseInt(t,10)}var uw=function(){function t(t,e,n,i){this.type=\"canvas\",this._zlevelList=[],this._prevDisplayList=[],this._layers={},this._layerConfig={},this._needsManuallyCompositing=!1,this.type=\"canvas\";var r=!t.nodeName||\"CANVAS\"===t.nodeName.toUpperCase();this._opts=n=I({},n||{}),this.dpr=n.devicePixelRatio||En,this._singleCanvas=r,this.root=t;var o=t.style;o&&(o.webkitTapHighlightColor=\"transparent\",o.webkitUserSelect=\"none\",o.userSelect=\"none\",o[\"-webkit-touch-callout\"]=\"none\",t.innerHTML=\"\"),this.storage=e;var a=this._zlevelList;this._prevDisplayList=[];var s=this._layers;if(r){var l=t,u=l.width,h=l.height;null!=n.width&&(u=n.width),null!=n.height&&(h=n.height),this.dpr=n.devicePixelRatio||1,l.width=u*this.dpr,l.height=h*this.dpr,this._width=u,this._height=h;var c=new rw(l,this,this.dpr);c.__builtin__=!0,c.initContext(),s[314159]=c,c.zlevel=aw,a.push(aw),this._domRoot=t}else{this._width=this._getSize(0),this._height=this._getSize(1);var p=this._domRoot=function(t,e){var n=document.createElement(\"div\");return n.style.cssText=[\"position:relative\",\"width:\"+t+\"px\",\"height:\"+e+\"px\",\"padding:0\",\"margin:0\",\"border-width:0\"].join(\";\")+\";\",n}(this._width,this._height);t.appendChild(p)}}return t.prototype.getType=function(){return\"canvas\"},t.prototype.isSingleCanvas=function(){return this._singleCanvas},t.prototype.getViewportRoot=function(){return this._domRoot},t.prototype.getViewportRootOffset=function(){var t=this.getViewportRoot();if(t)return{offsetLeft:t.offsetLeft||0,offsetTop:t.offsetTop||0}},t.prototype.refresh=function(t){var e=this.storage.getDisplayList(!0),n=this._prevDisplayList,i=this._zlevelList;this._redrawId=Math.random(),this._paintList(e,n,t,this._redrawId);for(var r=0;r<i.length;r++){var o=i[r],a=this._layers[o];if(!a.__builtin__&&a.refresh){var s=0===r?this._backgroundColor:null;a.refresh(s)}}return this._opts.useDirtyRect&&(this._prevDisplayList=e.slice()),this},t.prototype.refreshHover=function(){this._paintHoverList(this.storage.getDisplayList(!1))},t.prototype._paintHoverList=function(t){var e=t.length,n=this._hoverlayer;if(n&&n.clear(),e){for(var i,r={inHover:!0,viewWidth:this._width,viewHeight:this._height},o=0;o<e;o++){var a=t[o];a.__inHover&&(n||(n=this._hoverlayer=this.getLayer(ow)),i||(i=n.ctx).save(),Py(i,a,r,o===e-1))}i&&i.restore()}},t.prototype.getHoverLayer=function(){return this.getLayer(ow)},t.prototype.paintOne=function(t,e){ky(t,e)},t.prototype._paintList=function(t,e,n,i){if(this._redrawId===i){n=n||!1,this._updateLayerStatus(t);var r=this._doPaintList(t,e,n),o=r.finished,a=r.needsRefreshHover;if(this._needsManuallyCompositing&&this._compositeManually(),a&&this._paintHoverList(t),o)this.eachLayer((function(t){t.afterBrush&&t.afterBrush()}));else{var s=this;Me((function(){s._paintList(t,e,n,i)}))}}},t.prototype._compositeManually=function(){var t=this.getLayer(aw).ctx,e=this._domRoot.width,n=this._domRoot.height;t.clearRect(0,0,e,n),this.eachBuiltinLayer((function(i){i.virtual&&t.drawImage(i.dom,0,0,e,n)}))},t.prototype._doPaintList=function(t,e,n){for(var i=this,r=[],o=this._opts.useDirtyRect,s=0;s<this._zlevelList.length;s++){var l=this._zlevelList[s],u=this._layers[l];u.__builtin__&&u!==this._hoverlayer&&(u.__dirty||n)&&r.push(u)}for(var h=!0,c=!1,p=function(a){var s,l=r[a],u=l.ctx,p=o&&l.createRepaintRects(t,e,d._width,d._height),f=n?l.__startIndex:l.__drawIndex,g=!n&&l.incremental&&Date.now,y=g&&Date.now(),v=l.zlevel===d._zlevelList[0]?d._backgroundColor:null;if(l.__startIndex===l.__endIndex)l.clear(!1,v,p);else if(f===l.__startIndex){var m=t[f];m.incremental&&m.notClear&&!n||l.clear(!1,v,p)}-1===f&&(console.error(\"For some unknown reason. drawIndex is -1\"),f=l.__startIndex);var _=function(e){var n={inHover:!1,allClipped:!1,prevEl:null,viewWidth:i._width,viewHeight:i._height};for(s=f;s<l.__endIndex;s++){var r=t[s];if(r.__inHover&&(c=!0),i._doPaintEl(r,l,o,e,n,s===l.__endIndex-1),g)if(Date.now()-y>15)break}n.prevElClipPaths&&u.restore()};if(p)if(0===p.length)s=l.__endIndex;else for(var x=d.dpr,b=0;b<p.length;++b){var w=p[b];u.save(),u.beginPath(),u.rect(w.x*x,w.y*x,w.width*x,w.height*x),u.clip(),_(w),u.restore()}else u.save(),_(),u.restore();l.__drawIndex=s,l.__drawIndex<l.__endIndex&&(h=!1)},d=this,f=0;f<r.length;f++)p(f);return a.wxa&&P(this._layers,(function(t){t&&t.ctx&&t.ctx.draw&&t.ctx.draw()})),{finished:h,needsRefreshHover:c}},t.prototype._doPaintEl=function(t,e,n,i,r,o){var a=e.ctx;if(n){var s=t.getPaintRect();(!i||s&&s.intersect(i))&&(Py(a,t,r,o),t.setPrevPaintRect(s))}else Py(a,t,r,o)},t.prototype.getLayer=function(t,e){this._singleCanvas&&!this._needsManuallyCompositing&&(t=aw);var n=this._layers[t];return n||((n=new rw(\"zr_\"+t,this,this.dpr)).zlevel=t,n.__builtin__=!0,this._layerConfig[t]?S(n,this._layerConfig[t],!0):this._layerConfig[t-sw]&&S(n,this._layerConfig[t-sw],!0),e&&(n.virtual=e),this.insertLayer(t,n),n.initContext()),n},t.prototype.insertLayer=function(t,e){var n=this._layers,i=this._zlevelList,r=i.length,o=this._domRoot,a=null,s=-1;if(n[t])b(\"ZLevel \"+t+\" has been used already\");else if(function(t){return!!t&&(!!t.__builtin__||\"function\"==typeof t.resize&&\"function\"==typeof t.refresh)}(e)){if(r>0&&t>i[0]){for(s=0;s<r-1&&!(i[s]<t&&i[s+1]>t);s++);a=n[i[s]]}if(i.splice(s+1,0,t),n[t]=e,!e.virtual)if(a){var l=a.dom;l.nextSibling?o.insertBefore(e.dom,l.nextSibling):o.appendChild(e.dom)}else o.firstChild?o.insertBefore(e.dom,o.firstChild):o.appendChild(e.dom);e.__painter=this}else b(\"Layer of zlevel \"+t+\" is not valid\")},t.prototype.eachLayer=function(t,e){for(var n=this._zlevelList,i=0;i<n.length;i++){var r=n[i];t.call(e,this._layers[r],r)}},t.prototype.eachBuiltinLayer=function(t,e){for(var n=this._zlevelList,i=0;i<n.length;i++){var r=n[i],o=this._layers[r];o.__builtin__&&t.call(e,o,r)}},t.prototype.eachOtherLayer=function(t,e){for(var n=this._zlevelList,i=0;i<n.length;i++){var r=n[i],o=this._layers[r];o.__builtin__||t.call(e,o,r)}},t.prototype.getLayers=function(){return this._layers},t.prototype._updateLayerStatus=function(t){function e(t){o&&(o.__endIndex!==t&&(o.__dirty=!0),o.__endIndex=t)}if(this.eachBuiltinLayer((function(t,e){t.__dirty=t.__used=!1})),this._singleCanvas)for(var n=1;n<t.length;n++){if((s=t[n]).zlevel!==t[n-1].zlevel||s.incremental){this._needsManuallyCompositing=!0;break}}var i,r,o=null,a=0;for(r=0;r<t.length;r++){var s,l=(s=t[r]).zlevel,u=void 0;i!==l&&(i=l,a=0),s.incremental?((u=this.getLayer(l+.001,this._needsManuallyCompositing)).incremental=!0,a=1):u=this.getLayer(l+(a>0?sw:0),this._needsManuallyCompositing),u.__builtin__||b(\"ZLevel \"+l+\" has been used by unkown layer \"+u.id),u!==o&&(u.__used=!0,u.__startIndex!==r&&(u.__dirty=!0),u.__startIndex=r,u.incremental?u.__drawIndex=-1:u.__drawIndex=r,e(r),o=u),1&s.__dirty&&!s.__inHover&&(u.__dirty=!0,u.incremental&&u.__drawIndex<0&&(u.__drawIndex=r))}e(r),this.eachBuiltinLayer((function(t,e){!t.__used&&t.getElementCount()>0&&(t.__dirty=!0,t.__startIndex=t.__endIndex=t.__drawIndex=0),t.__dirty&&t.__drawIndex<0&&(t.__drawIndex=t.__startIndex)}))},t.prototype.clear=function(){return this.eachBuiltinLayer(this._clearLayer),this},t.prototype._clearLayer=function(t){t.clear()},t.prototype.setBackgroundColor=function(t){this._backgroundColor=t,P(this._layers,(function(t){t.setUnpainted()}))},t.prototype.configLayer=function(t,e){if(e){var n=this._layerConfig;n[t]?S(n[t],e,!0):n[t]=e;for(var i=0;i<this._zlevelList.length;i++){var r=this._zlevelList[i];if(r===t||r===t+sw)S(this._layers[r],n[t],!0)}}},t.prototype.delLayer=function(t){var e=this._layers,n=this._zlevelList,i=e[t];i&&(i.dom.parentNode.removeChild(i.dom),delete e[t],n.splice(D(n,t),1))},t.prototype.resize=function(t,e){if(this._domRoot.style){var n=this._domRoot;n.style.display=\"none\";var i=this._opts;if(null!=t&&(i.width=t),null!=e&&(i.height=e),t=this._getSize(0),e=this._getSize(1),n.style.display=\"\",this._width!==t||e!==this._height){for(var r in n.style.width=t+\"px\",n.style.height=e+\"px\",this._layers)this._layers.hasOwnProperty(r)&&this._layers[r].resize(t,e);this.refresh(!0)}this._width=t,this._height=e}else{if(null==t||null==e)return;this._width=t,this._height=e,this.getLayer(aw).resize(t,e)}return this},t.prototype.clearLayer=function(t){var e=this._layers[t];e&&e.clear()},t.prototype.dispose=function(){this.root.innerHTML=\"\",this.root=this.storage=this._domRoot=this._layers=null},t.prototype.getRenderedCanvas=function(t){if(t=t||{},this._singleCanvas&&!this._compositeManually)return this._layers[314159].dom;var e=new rw(\"image\",this,t.pixelRatio||this.dpr);e.initContext(),e.clear(!1,t.backgroundColor||this._backgroundColor);var n=e.ctx;if(t.pixelRatio<=this.dpr){this.refresh();var i=e.dom.width,r=e.dom.height;this.eachLayer((function(t){t.__builtin__?n.drawImage(t.dom,0,0,i,r):t.renderToCanvas&&(n.save(),t.renderToCanvas(n),n.restore())}))}else for(var o={inHover:!1,viewWidth:this._width,viewHeight:this._height},a=this.storage.getDisplayList(!0),s=0,l=a.length;s<l;s++){var u=a[s];Py(n,u,o,s===l-1)}return e.dom},t.prototype.getWidth=function(){return this._width},t.prototype.getHeight=function(){return this._height},t.prototype._getSize=function(t){var e=this._opts,n=[\"width\",\"height\"][t],i=[\"clientWidth\",\"clientHeight\"][t],r=[\"paddingLeft\",\"paddingTop\"][t],o=[\"paddingRight\",\"paddingBottom\"][t];if(null!=e[n]&&\"auto\"!==e[n])return parseFloat(e[n]);var a=this.root,s=document.defaultView.getComputedStyle(a);return(a[i]||lw(s[n])||lw(a.style[n]))-(lw(s[r])||0)-(lw(s[o])||0)|0},t.prototype.pathToImage=function(t,e){e=e||this.dpr;var n=document.createElement(\"canvas\"),i=n.getContext(\"2d\"),r=t.getBoundingRect(),o=t.style,a=o.shadowBlur*e,s=o.shadowOffsetX*e,l=o.shadowOffsetY*e,u=t.hasStroke()?o.lineWidth:0,h=Math.max(u/2,-s+a),c=Math.max(u/2,s+a),p=Math.max(u/2,-l+a),d=Math.max(u/2,l+a),f=r.width+h+c,g=r.height+p+d;n.width=f*e,n.height=g*e,i.scale(e,e),i.clearRect(0,0,f,g),i.dpr=e;var y={x:t.x,y:t.y,scaleX:t.scaleX,scaleY:t.scaleY,rotation:t.rotation,originX:t.originX,originY:t.originY};t.x=h-r.x,t.y=p-r.y,t.rotation=0,t.scaleX=1,t.scaleY=1,t.updateTransform(),t&&Py(i,t,{inHover:!1,viewWidth:this._width,viewHeight:this._height},!0);var v=new es({style:{x:0,y:0,image:n}});return I(t,y),v},t}();var hw=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.getInitialData=function(t){return F_(this.getSource(),this,{useEncodeDefaulter:!0})},e.prototype.getLegendIcon=function(t){var e=new Ei,n=fy(\"line\",0,t.itemHeight/2,t.itemWidth,0,t.lineStyle.stroke,!1);e.add(n),n.setStyle(t.lineStyle);var i=this.getData().getVisual(\"symbol\"),r=this.getData().getVisual(\"symbolRotate\"),o=\"none\"===i?\"circle\":i,a=.8*t.itemHeight,s=fy(o,(t.itemWidth-a)/2,(t.itemHeight-a)/2,a,a,t.itemStyle.fill);e.add(s),s.setStyle(t.itemStyle);var l=\"inherit\"===t.iconRotate?r:t.iconRotate||0;return s.rotation=l*Math.PI/180,s.setOrigin([t.itemWidth/2,t.itemHeight/2]),o.indexOf(\"empty\")>-1&&(s.style.stroke=s.style.fill,s.style.fill=\"#fff\",s.style.lineWidth=2),e},e.type=\"series.line\",e.dependencies=[\"grid\",\"polar\"],e.defaultOption={zlevel:0,z:3,coordinateSystem:\"cartesian2d\",legendHoverLink:!0,clip:!0,label:{position:\"top\"},endLabel:{show:!1,valueAnimation:!0,distance:8},lineStyle:{width:2,type:\"solid\"},emphasis:{scale:!0,lineStyle:{width:\"bolder\"}},step:!1,smooth:!1,smoothMonotone:null,symbol:\"emptyCircle\",symbolSize:4,symbolRotate:null,showSymbol:!0,showAllSymbol:\"auto\",connectNulls:!1,sampling:\"none\",animationEasing:\"linear\",progressive:0,hoverLayerThreshold:1/0},e}(ff);function cw(t,e){var n=t.mapDimensionsAll(\"defaultedLabel\"),i=n.length;if(1===i){var r=Md(t,e,n[0]);return null!=r?r+\"\":null}if(i){for(var o=[],a=0;a<n.length;a++)o.push(Md(t,e,n[a]));return o.join(\" \")}}function pw(t,e){var n=t.mapDimensionsAll(\"defaultedLabel\");if(!F(e))return e+\"\";for(var i=[],r=0;r<n.length;r++){var o=t.getDimensionInfo(n[r]);o&&i.push(e[o.index])}return i.join(\" \")}var dw=function(t){function e(e,n,i,r){var o=t.call(this)||this;return o.updateData(e,n,i,r),o}return n(e,t),e.prototype._createSymbol=function(t,e,n,i,r){this.removeAll();var o=fy(t,-1,-1,2,2,null,r);o.attr({z2:100,culling:!0,scaleX:i[0]/2,scaleY:i[1]/2}),o.drift=fw,this._symbolType=t,this.add(o)},e.prototype.stopSymbolAnimation=function(t){this.childAt(0).stopAnimation(null,t)},e.prototype.getSymbolPath=function(){return this.childAt(0)},e.prototype.highlight=function(){js(this.childAt(0))},e.prototype.downplay=function(){qs(this.childAt(0))},e.prototype.setZ=function(t,e){var n=this.childAt(0);n.zlevel=t,n.z=e},e.prototype.setDraggable=function(t){var e=this.childAt(0);e.draggable=t,e.cursor=t?\"move\":e.cursor},e.prototype.updateData=function(t,n,i,r){this.silent=!1;var o=t.getItemVisual(n,\"symbol\")||\"circle\",a=t.hostModel,s=e.getSymbolSize(t,n),l=o!==this._symbolType,u=r&&r.disableAnimation;if(l){var h=t.getItemVisual(n,\"symbolKeepAspect\");this._createSymbol(o,t,n,s,h)}else{(p=this.childAt(0)).silent=!1;var c={scaleX:s[0]/2,scaleY:s[1]/2};u?p.attr(c):Hu(p,c,a,n)}if(this._updateCommon(t,n,s,i,r),l){var p=this.childAt(0);if(!u){c={scaleX:this._sizeX,scaleY:this._sizeY,style:{opacity:p.style.opacity}};p.scaleX=p.scaleY=0,p.style.opacity=0,Wu(p,c,a,n)}}u&&this.childAt(0).stopAnimation(\"remove\"),this._seriesModel=a},e.prototype._updateCommon=function(t,e,n,i,r){var o,a,s,l,u,h,c,p,d=this.childAt(0),f=t.hostModel;if(i&&(o=i.emphasisItemStyle,a=i.blurItemStyle,s=i.selectItemStyle,l=i.focus,u=i.blurScope,h=i.labelStatesModels,c=i.hoverScale,p=i.cursorStyle),!i||t.hasItemOption){var g=i&&i.itemModel?i.itemModel:t.getItemModel(e),y=g.getModel(\"emphasis\");o=y.getModel(\"itemStyle\").getItemStyle(),s=g.getModel([\"select\",\"itemStyle\"]).getItemStyle(),a=g.getModel([\"blur\",\"itemStyle\"]).getItemStyle(),l=y.get(\"focus\"),u=y.get(\"blurScope\"),h=ch(g),c=y.getShallow(\"scale\"),p=g.getShallow(\"cursor\")}var v=t.getItemVisual(e,\"symbolRotate\");d.attr(\"rotation\",(v||0)*Math.PI/180||0);var m=t.getItemVisual(e,\"symbolOffset\")||0;m&&(F(m)||(m=[m,m]),d.x=Zi(m[0],n[0]),d.y=Zi(tt(m[1],m[0])||0,n[1])),p&&d.attr(\"cursor\",p);var _=t.getItemVisual(e,\"style\"),x=_.fill;if(d instanceof es){var b=d.style;d.useStyle(I({image:b.image,x:b.x,y:b.y,width:b.width,height:b.height},_))}else d.__isEmptyBrush?d.useStyle(I({},_)):d.useStyle(_),d.style.decal=null,d.setColor(x,r&&r.symbolInnerColor),d.style.strokeNoScale=!0;var w=t.getItemVisual(e,\"liftZ\"),S=this._z2;null!=w?null==S&&(this._z2=d.z2,d.z2+=w):null!=S&&(d.z2=S,this._z2=null);var M=r&&r.useNameLabel;hh(d,h,{labelFetcher:f,labelDataIndex:e,defaultText:function(e){return M?t.getName(e):cw(t,e)},inheritColor:x,defaultOpacity:_.opacity}),this._sizeX=n[0]/2,this._sizeY=n[1]/2;var T=d.ensureState(\"emphasis\");if(T.style=o,d.ensureState(\"select\").style=s,d.ensureState(\"blur\").style=a,c){var C=Math.max(1.1,3/this._sizeY);T.scaleX=this._sizeX*C,T.scaleY=this._sizeY*C}this.setSymbolScale(1),sl(this,l,u)},e.prototype.setSymbolScale=function(t){this.scaleX=this.scaleY=t},e.prototype.fadeOut=function(t,e){var n=this.childAt(0),i=this._seriesModel,r=_s(this).dataIndex,o=e&&e.animation;if(this.silent=n.silent=!0,e&&e.fadeLabel){var a=n.getTextContent();a&&Uu(a,{style:{opacity:0}},i,{dataIndex:r,removeOpt:o,cb:function(){n.removeTextContent()}})}else n.removeTextContent();Uu(n,{style:{opacity:0},scaleX:0,scaleY:0},i,{dataIndex:r,cb:t,removeOpt:o})},e.getSymbolSize=function(t,e){var n=t.getItemVisual(e,\"symbolSize\");return F(n)?n.slice():[+n,+n]},e}(Ei);function fw(t,e){this.parent.drift(t,e)}function gw(t,e,n,i){return e&&!isNaN(e[0])&&!isNaN(e[1])&&!(i.isIgnore&&i.isIgnore(n))&&!(i.clipShape&&!i.clipShape.contain(e[0],e[1]))&&\"none\"!==t.getItemVisual(n,\"symbol\")}function yw(t){return null==t||X(t)||(t={isIgnore:t}),t||{}}function vw(t){var e=t.hostModel,n=e.getModel(\"emphasis\");return{emphasisItemStyle:n.getModel(\"itemStyle\").getItemStyle(),blurItemStyle:e.getModel([\"blur\",\"itemStyle\"]).getItemStyle(),selectItemStyle:e.getModel([\"select\",\"itemStyle\"]).getItemStyle(),focus:n.get(\"focus\"),blurScope:n.get(\"blurScope\"),hoverScale:n.get(\"scale\"),labelStatesModels:ch(e),cursorStyle:e.get(\"cursor\")}}var mw=function(){function t(t){this.group=new Ei,this._SymbolCtor=t||dw}return t.prototype.updateData=function(t,e){e=yw(e);var n=this.group,i=t.hostModel,r=this._data,o=this._SymbolCtor,a=e.disableAnimation,s=vw(t),l={disableAnimation:a},u=e.getSymbolPoint||function(e){return t.getItemLayout(e)};r||n.removeAll(),t.diff(r).add((function(i){var r=u(i);if(gw(t,r,i,e)){var a=new o(t,i,s,l);a.setPosition(r),t.setItemGraphicEl(i,a),n.add(a)}})).update((function(h,c){var p=r.getItemGraphicEl(c),d=u(h);if(gw(t,d,h,e)){if(p){p.updateData(t,h,s,l);var f={x:d[0],y:d[1]};a?p.attr(f):Hu(p,f,i)}else(p=new o(t,h)).setPosition(d);n.add(p),t.setItemGraphicEl(h,p)}else n.remove(p)})).remove((function(t){var e=r.getItemGraphicEl(t);e&&e.fadeOut((function(){n.remove(e)}))})).execute(),this._getSymbolPoint=u,this._data=t},t.prototype.isPersistent=function(){return!0},t.prototype.updateLayout=function(){var t=this,e=this._data;e&&e.eachItemGraphicEl((function(e,n){var i=t._getSymbolPoint(n);e.setPosition(i),e.markRedraw()}))},t.prototype.incrementalPrepareUpdate=function(t){this._seriesScope=vw(t),this._data=null,this.group.removeAll()},t.prototype.incrementalUpdate=function(t,e,n){function i(t){t.isGroup||(t.incremental=!0,t.ensureState(\"emphasis\").hoverLayer=!0)}n=yw(n);for(var r=t.start;r<t.end;r++){var o=e.getItemLayout(r);if(gw(e,o,r,n)){var a=new this._SymbolCtor(e,r,this._seriesScope);a.traverse(i),a.setPosition(o),this.group.add(a),e.setItemGraphicEl(r,a)}}},t.prototype.remove=function(t){var e=this.group,n=this._data;n&&t?n.eachItemGraphicEl((function(t){t.fadeOut((function(){e.remove(t)}))})):e.removeAll()},t}();function _w(t,e,n){var i=t.getBaseAxis(),r=t.getOtherAxis(i),o=function(t,e){var n=0,i=t.scale.getExtent();\"start\"===e?n=i[0]:\"end\"===e?n=i[1]:i[0]>0?n=i[0]:i[1]<0&&(n=i[1]);return n}(r,n),a=i.dim,s=r.dim,l=e.mapDimension(s),u=e.mapDimension(a),h=\"x\"===s||\"radius\"===s?1:0,c=O(t.dimensions,(function(t){return e.mapDimension(t)})),p=!1,d=e.getCalculationInfo(\"stackResultDimension\");return V_(e,c[0])&&(p=!0,c[0]=d),V_(e,c[1])&&(p=!0,c[1]=d),{dataDimsForPoint:c,valueStart:o,valueAxisDim:s,baseAxisDim:a,stacked:!!p,valueDim:l,baseDim:u,baseDataOffset:h,stackedOverDimension:e.getCalculationInfo(\"stackedOverDimension\")}}function xw(t,e,n,i){var r=NaN;t.stacked&&(r=n.get(n.getCalculationInfo(\"stackedOverDimension\"),i)),isNaN(r)&&(r=t.valueStart);var o=t.baseDataOffset,a=[];return a[o]=n.get(t.baseDim,i),a[1-o]=r,e.dataToPoint(a)}var bw=\"undefined\"!=typeof Float32Array,ww=bw?Float32Array:Array;function Sw(t){return F(t)?bw?new Float32Array(t):t:new ww(t)}var Mw=Math.min,Iw=Math.max;function Tw(t,e){return isNaN(t)||isNaN(e)}function Cw(t,e,n,i,r,o,a,s,l){for(var u,h,c,p,d,f,g=n,y=0;y<i;y++){var v=e[2*g],m=e[2*g+1];if(g>=r||g<0)break;if(Tw(v,m)){if(l){g+=o;continue}break}if(g===n)t[o>0?\"moveTo\":\"lineTo\"](v,m),c=v,p=m;else{var _=v-u,x=m-h;if(_*_+x*x<.5){g+=o;continue}if(a>0){var b=g+o,w=e[2*b],S=e[2*b+1],M=y+1;if(l)for(;Tw(w,S)&&M<i;)M++,w=e[2*(b+=o)],S=e[2*b+1];var I=.5,T=0,C=0,D=void 0,A=void 0;if(M>=i||Tw(w,S))d=v,f=m;else{T=w-u,C=S-h;var L=v-u,k=w-v,P=m-h,O=S-m,R=void 0,N=void 0;\"x\"===s?(R=Math.abs(L),N=Math.abs(k),d=v-R*a,f=m,D=v+R*a,A=m):\"y\"===s?(R=Math.abs(P),N=Math.abs(O),d=v,f=m-R*a,D=v,A=m+R*a):(R=Math.sqrt(L*L+P*P),d=v-T*a*(1-(I=(N=Math.sqrt(k*k+O*O))/(N+R))),f=m-C*a*(1-I),A=m+C*a*I,D=Mw(D=v+T*a*I,Iw(w,v)),A=Mw(A,Iw(S,m)),D=Iw(D,Mw(w,v)),f=m-(C=(A=Iw(A,Mw(S,m)))-m)*R/N,d=Mw(d=v-(T=D-v)*R/N,Iw(u,v)),f=Mw(f,Iw(h,m)),D=v+(T=v-(d=Iw(d,Mw(u,v))))*N/R,A=m+(C=m-(f=Iw(f,Mw(h,m))))*N/R)}t.bezierCurveTo(c,p,d,f,v,m),c=D,p=A}else t.lineTo(v,m)}u=v,h=m,g+=o}return y}var Dw=function(){this.smooth=0,this.smoothConstraint=!0},Aw=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"ec-polyline\",n}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new Dw},e.prototype.buildPath=function(t,e){var n=e.points,i=0,r=n.length/2;if(e.connectNulls){for(;r>0&&Tw(n[2*r-2],n[2*r-1]);r--);for(;i<r&&Tw(n[2*i],n[2*i+1]);i++);}for(;i<r;)i+=Cw(t,n,i,r,r,1,e.smooth,e.smoothMonotone,e.connectNulls)+1},e.prototype.getPointOn=function(t,e){this.path||(this.createPathProxy(),this.buildPath(this.path,this.shape));for(var n,i,r=this.path.data,o=La.CMD,a=\"x\"===e,s=[],l=0;l<r.length;){var u=void 0,h=void 0,c=void 0,p=void 0,d=void 0,f=void 0,g=void 0;switch(r[l++]){case o.M:n=r[l++],i=r[l++];break;case o.L:if(u=r[l++],h=r[l++],(g=a?(t-n)/(u-n):(t-i)/(h-i))<=1&&g>=0){var y=a?(h-i)*g+i:(u-n)*g+n;return a?[t,y]:[y,t]}n=u,i=h;break;case o.C:u=r[l++],h=r[l++],c=r[l++],p=r[l++],d=r[l++],f=r[l++];var v=a?Bo(n,u,c,d,t,s):Bo(i,h,p,f,t,s);if(v>0)for(var m=0;m<v;m++){var _=s[m];if(_<=1&&_>=0){y=a?Eo(i,h,p,f,_):Eo(n,u,c,d,_);return a?[t,y]:[y,t]}}n=d,i=f}}},e}(Ka),Lw=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e}(Dw),kw=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"ec-polygon\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new Lw},e.prototype.buildPath=function(t,e){var n=e.points,i=e.stackedOnPoints,r=0,o=n.length/2,a=e.smoothMonotone;if(e.connectNulls){for(;o>0&&Tw(n[2*o-2],n[2*o-1]);o--);for(;r<o&&Tw(n[2*r],n[2*r+1]);r++);}for(;r<o;){var s=Cw(t,n,r,o,o,1,e.smooth,a,e.connectNulls);Cw(t,i,r+s-1,s,o,-1,e.stackedOnSmooth,a,e.connectNulls),r+=s+1,t.closePath()}},e}(Ka);function Pw(t,e,n,i,r){var o=t.getArea(),a=o.x,s=o.y,l=o.width,u=o.height,h=n.get([\"lineStyle\",\"width\"])||2;a-=h/2,s-=h/2,l+=h,u+=h,a=Math.floor(a),l=Math.round(l);var c=new ls({shape:{x:a,y:s,width:l,height:u}});if(e){var p=t.getBaseAxis(),d=p.isHorizontal(),f=p.inverse;d?(f&&(c.shape.x+=l),c.shape.width=0):(f||(c.shape.y+=u),c.shape.height=0),Wu(c,{shape:{width:l,height:u,x:a,y:s}},n,null,i,\"function\"==typeof r?function(t){r(t,c)}:null)}return c}function Ow(t,e,n){var i=t.getArea(),r=ji(i.r0,1),o=ji(i.r,1),a=new Jl({shape:{cx:ji(t.cx,1),cy:ji(t.cy,1),r0:r,r:o,startAngle:i.startAngle,endAngle:i.endAngle,clockwise:i.clockwise}});e&&(\"angle\"===t.getBaseAxis().dim?a.shape.endAngle=i.startAngle:a.shape.r=r,Wu(a,{shape:{endAngle:i.endAngle,r:o}},n));return a}function Rw(t,e,n,i,r){return t?\"polar\"===t.type?Ow(t,e,n):\"cartesian2d\"===t.type?Pw(t,e,n,i,r):null:null}function Nw(t,e){return t.type===e}function zw(t,e){if(t.length===e.length){for(var n=0;n<t.length;n++)if(t[n]!==e[n])return;return!0}}function Ew(t){for(var e=1/0,n=1/0,i=-1/0,r=-1/0,o=0;o<t.length;){var a=t[o++],s=t[o++];isNaN(a)||(e=Math.min(a,e),i=Math.max(a,i)),isNaN(s)||(n=Math.min(s,n),r=Math.max(s,r))}return[[e,n],[i,r]]}function Vw(t,e){var n=Ew(t),i=n[0],r=n[1],o=Ew(e),a=o[0],s=o[1];return Math.max(Math.abs(i[0]-a[0]),Math.abs(i[1]-a[1]),Math.abs(r[0]-s[0]),Math.abs(r[1]-s[1]))}function Bw(t){return\"number\"==typeof t?t:t?.5:0}function Fw(t,e,n){for(var i=e.getBaseAxis(),r=\"x\"===i.dim||\"radius\"===i.dim?0:1,o=[],a=0,s=[],l=[],u=[];a<t.length-2;a+=2)switch(u[0]=t[a+2],u[1]=t[a+3],l[0]=t[a],l[1]=t[a+1],o.push(l[0],l[1]),n){case\"end\":s[r]=u[r],s[1-r]=l[1-r],o.push(s[0],s[1]);break;case\"middle\":var h=(l[r]+u[r])/2,c=[];s[r]=c[r]=h,s[1-r]=l[1-r],c[1-r]=u[1-r],o.push(s[0],s[1]),o.push(c[0],c[1]);break;default:s[r]=l[r],s[1-r]=u[1-r],o.push(s[0],s[1])}return o.push(t[a++],t[a++]),o}function Gw(t,e,n){var i=t.get(\"showAllSymbol\"),r=\"auto\"===i;if(!i||r){var o=n.getAxesByScale(\"ordinal\")[0];if(o&&(!r||!function(t,e){var n=t.getExtent(),i=Math.abs(n[1]-n[0])/t.scale.count();isNaN(i)&&(i=0);for(var r=e.count(),o=Math.max(1,Math.round(r/5)),a=0;a<r;a+=o)if(1.5*dw.getSymbolSize(e,a)[t.isHorizontal()?1:0]>i)return!1;return!0}(o,e))){var a=e.mapDimension(o.dim),s={};return P(o.getViewLabels(),(function(t){var e=o.scale.getRawOrdinalNumber(t.tickValue);s[e]=1})),function(t){return!s.hasOwnProperty(e.get(a,t))}}}}function Hw(t,e){return[t[2*e],t[2*e+1]]}function Ww(t,e,n,i){if(Nw(e,\"cartesian2d\")){var r=i.getModel(\"endLabel\"),o=r.get(\"show\"),a=r.get(\"valueAnimation\"),s=i.getData(),l={lastFrameIndex:0},u=o?function(n,i){t._endLabelOnDuring(n,i,s,l,a,r,e)}:null,h=e.getBaseAxis().isHorizontal(),c=Pw(e,n,i,(function(){var e=t._endLabel;e&&n&&null!=l.originalX&&e.attr({x:l.originalX,y:l.originalY})}),u);if(!i.get(\"clip\",!0)){var p=c.shape,d=Math.max(p.width,p.height);h?(p.y-=d,p.height+=2*d):(p.x-=d,p.width+=2*d)}return u&&u(1,c),c}return Ow(e,n,i)}var Uw=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.init=function(){var t=new Ei,e=new mw;this.group.add(e.group),this._symbolDraw=e,this._lineGroup=t},e.prototype.render=function(t,e,n){var i=this,r=t.coordinateSystem,o=this.group,a=t.getData(),s=t.getModel(\"lineStyle\"),l=t.getModel(\"areaStyle\"),u=a.getLayout(\"points\")||[],h=\"polar\"===r.type,c=this._coordSys,p=this._symbolDraw,d=this._polyline,f=this._polygon,g=this._lineGroup,y=t.get(\"animation\"),v=!l.isEmpty(),m=l.get(\"origin\"),_=_w(r,a,m),x=v&&function(t,e,n){if(!n.valueDim)return[];for(var i=e.count(),r=Sw(2*i),o=0;o<i;o++){var a=xw(n,t,e,o);r[2*o]=a[0],r[2*o+1]=a[1]}return r}(r,a,_),b=t.get(\"showSymbol\"),w=b&&!h&&Gw(t,a,r),S=this._data;S&&S.eachItemGraphicEl((function(t,e){t.__temp&&(o.remove(t),S.setItemGraphicEl(e,null))})),b||p.remove(),o.add(g);var M,I=!h&&t.get(\"step\");r&&r.getArea&&t.get(\"clip\",!0)&&(null!=(M=r.getArea()).width?(M.x-=.1,M.y-=.1,M.width+=.2,M.height+=.2):M.r0&&(M.r0-=.5,M.r+=.5)),this._clipShapeForSymbol=M;var C=function(t,e){var n=t.getVisual(\"visualMeta\");if(n&&n.length&&t.count()&&\"cartesian2d\"===e.type){for(var i,r,o=n.length-1;o>=0;o--){var a=n[o].dimension,s=t.dimensions[a],l=t.getDimensionInfo(s);if(\"x\"===(i=l&&l.coordDim)||\"y\"===i){r=n[o];break}}if(r){var u=e.getAxis(i),h=O(r.stops,(function(t){return{offset:0,coord:u.toGlobalCoord(u.dataToCoord(t.value,!0)),color:t.color}})),c=h.length,p=r.outerColors.slice();c&&h[0].coord>h[c-1].coord&&(h.reverse(),p.reverse());var d=h[0].coord-10,f=h[c-1].coord+10,g=f-d;if(g<.001)return\"transparent\";P(h,(function(t){t.offset=(t.coord-d)/g})),h.push({offset:c?h[c-1].offset:.5,color:p[1]||\"transparent\"}),h.unshift({offset:c?h[0].offset:.5,color:p[0]||\"transparent\"});var y=new mu(0,0,0,0,h,!0);return y[i]=d,y[i+\"2\"]=f,y}}}(a,r)||a.getVisual(\"style\")[a.getVisual(\"drawType\")];d&&c.type===r.type&&I===this._step?(v&&!f?f=this._newPolygon(u,x):f&&!v&&(g.remove(f),f=this._polygon=null),h||this._initOrUpdateEndLabel(t,r,kc(C)),g.setClipPath(Ww(this,r,!1,t)),b&&p.updateData(a,{isIgnore:w,clipShape:M,disableAnimation:!0,getSymbolPoint:function(t){return[u[2*t],u[2*t+1]]}}),zw(this._stackedOnPoints,x)&&zw(this._points,u)||(y?this._doUpdateAnimation(a,x,r,n,I,m):(I&&(u=Fw(u,r,I),x&&(x=Fw(x,r,I))),d.setShape({points:u}),f&&f.setShape({points:u,stackedOnPoints:x})))):(b&&p.updateData(a,{isIgnore:w,clipShape:M,disableAnimation:!0,getSymbolPoint:function(t){return[u[2*t],u[2*t+1]]}}),y&&this._initSymbolLabelAnimation(a,r,M),I&&(u=Fw(u,r,I),x&&(x=Fw(x,r,I))),d=this._newPolyline(u),v&&(f=this._newPolygon(u,x)),h||this._initOrUpdateEndLabel(t,r,kc(C)),g.setClipPath(Ww(this,r,!0,t)));var D=t.get([\"emphasis\",\"focus\"]),A=t.get([\"emphasis\",\"blurScope\"]);(d.useStyle(T(s.getLineStyle(),{fill:\"none\",stroke:C,lineJoin:\"bevel\"})),cl(d,t,\"lineStyle\"),d.style.lineWidth>0&&\"bolder\"===t.get([\"emphasis\",\"lineStyle\",\"width\"]))&&(d.getState(\"emphasis\").style.lineWidth=+d.style.lineWidth+1);_s(d).seriesIndex=t.seriesIndex,sl(d,D,A);var L=Bw(t.get(\"smooth\")),k=t.get(\"smoothMonotone\"),R=t.get(\"connectNulls\");if(d.setShape({smooth:L,smoothMonotone:k,connectNulls:R}),f){var N=a.getCalculationInfo(\"stackedOnSeries\"),z=0;f.useStyle(T(l.getAreaStyle(),{fill:C,opacity:.7,lineJoin:\"bevel\",decal:a.getVisual(\"style\").decal})),N&&(z=Bw(N.get(\"smooth\"))),f.setShape({smooth:L,stackedOnSmooth:z,smoothMonotone:k,connectNulls:R}),cl(f,t,\"areaStyle\"),_s(f).seriesIndex=t.seriesIndex,sl(f,D,A)}var E=function(t){i._changePolyState(t)};a.eachItemGraphicEl((function(t){t&&(t.onHoverStateChange=E)})),this._polyline.onHoverStateChange=E,this._data=a,this._coordSys=r,this._stackedOnPoints=x,this._points=u,this._step=I,this._valueOrigin=m},e.prototype.dispose=function(){},e.prototype.highlight=function(t,e,n,i){var r=t.getData(),o=Lr(r,i);if(this._changePolyState(\"emphasis\"),!(o instanceof Array)&&null!=o&&o>=0){var a=r.getLayout(\"points\"),s=r.getItemGraphicEl(o);if(!s){var l=a[2*o],u=a[2*o+1];if(isNaN(l)||isNaN(u))return;if(this._clipShapeForSymbol&&!this._clipShapeForSymbol.contain(l,u))return;var h=t.get(\"zlevel\"),c=t.get(\"z\");(s=new dw(r,o)).x=l,s.y=u,s.setZ(h,c);var p=s.getSymbolPath().getTextContent();p&&(p.zlevel=h,p.z=c,p.z2=this._polyline.z2+1),s.__temp=!0,r.setItemGraphicEl(o,s),s.stopSymbolAnimation(!0),this.group.add(s)}s.highlight()}else Tf.prototype.highlight.call(this,t,e,n,i)},e.prototype.downplay=function(t,e,n,i){var r=t.getData(),o=Lr(r,i);if(this._changePolyState(\"normal\"),null!=o&&o>=0){var a=r.getItemGraphicEl(o);a&&(a.__temp?(r.setItemGraphicEl(o,null),this.group.remove(a)):a.downplay())}else Tf.prototype.downplay.call(this,t,e,n,i)},e.prototype._changePolyState=function(t){var e=this._polygon;Ws(this._polyline,t),e&&Ws(e,t)},e.prototype._newPolyline=function(t){var e=this._polyline;return e&&this._lineGroup.remove(e),e=new Aw({shape:{points:t},segmentIgnoreThreshold:2,z2:10}),this._lineGroup.add(e),this._polyline=e,e},e.prototype._newPolygon=function(t,e){var n=this._polygon;return n&&this._lineGroup.remove(n),n=new kw({shape:{points:t,stackedOnPoints:e},segmentIgnoreThreshold:2}),this._lineGroup.add(n),this._polygon=n,n},e.prototype._initSymbolLabelAnimation=function(t,e,n){var i,r,o=e.getBaseAxis(),a=o.inverse;\"cartesian2d\"===e.type?(i=o.isHorizontal(),r=!1):\"polar\"===e.type&&(i=\"angle\"===o.dim,r=!0);var s=t.hostModel,l=s.get(\"animationDuration\");\"function\"==typeof l&&(l=l(null));var u=s.get(\"animationDelay\")||0,h=\"function\"==typeof u?u(null):u;t.eachItemGraphicEl((function(t,o){var s=t;if(s){var c=[t.x,t.y],p=void 0,d=void 0,f=void 0;if(n)if(r){var g=n,y=e.pointToCoord(c);i?(p=g.startAngle,d=g.endAngle,f=-y[1]/180*Math.PI):(p=g.r0,d=g.r,f=y[0])}else{var v=n;i?(p=v.x,d=v.x+v.width,f=t.x):(p=v.y+v.height,d=v.y,f=t.y)}var m=d===p?0:(f-p)/(d-p);a&&(m=1-m);var _=\"function\"==typeof u?u(o):l*m+h,x=s.getSymbolPath(),b=x.getTextContent();s.attr({scaleX:0,scaleY:0}),s.animateTo({scaleX:1,scaleY:1},{duration:200,delay:_}),b&&b.animateFrom({style:{opacity:0}},{duration:300,delay:_}),x.disableLabelAnimation=!0}}))},e.prototype._initOrUpdateEndLabel=function(t,e,n){var i=t.getModel(\"endLabel\");if(i.get(\"show\")){var r=t.getData(),o=this._polyline,a=this._endLabel;a||((a=this._endLabel=new cs({z2:200})).ignoreClip=!0,o.setTextContent(this._endLabel),o.disableLabelAnimation=!0);var s=function(t){for(var e,n,i=t.length/2;i>0&&(e=t[2*i-2],n=t[2*i-1],isNaN(e)||isNaN(n));i--);return i-1}(r.getLayout(\"points\"));s>=0&&(hh(o,ch(t,\"endLabel\"),{inheritColor:n,labelFetcher:t,labelDataIndex:s,defaultText:function(t,e,n){return null!=n?pw(r,n):cw(r,t)},enableTextSetter:!0},function(t,e){var n=e.getBaseAxis(),i=n.isHorizontal(),r=n.inverse,o=i?r?\"right\":\"left\":\"center\",a=i?\"middle\":r?\"top\":\"bottom\";return{normal:{align:t.get(\"align\")||o,verticalAlign:t.get(\"verticalAlign\")||a}}}(i,e)),o.textConfig.position=null)}else this._endLabel&&(this._polyline.removeTextContent(),this._endLabel=null)},e.prototype._endLabelOnDuring=function(t,e,n,i,r,o,a){var s=this._endLabel,l=this._polyline;if(s){t<1&&null==i.originalX&&(i.originalX=s.x,i.originalY=s.y);var u=n.getLayout(\"points\"),h=n.hostModel,c=h.get(\"connectNulls\"),p=o.get(\"precision\"),d=o.get(\"distance\")||0,f=a.getBaseAxis(),g=f.isHorizontal(),y=f.inverse,v=e.shape,m=y?g?v.x:v.y+v.height:g?v.x+v.width:v.y,_=(g?d:0)*(y?-1:1),x=(g?0:-d)*(y?-1:1),b=g?\"x\":\"y\",w=function(t,e,n){for(var i,r,o=t.length/2,a=\"x\"===n?0:1,s=0,l=-1,u=0;u<o;u++)if(r=t[2*u+a],!isNaN(r)&&!isNaN(t[2*u+1-a]))if(0!==u){if(i<=e&&r>=e||i>=e&&r<=e){l=u;break}s=u,i=r}else i=r;return{range:[s,l],t:(e-i)/(r-i)}}(u,m,b),S=w.range,M=S[1]-S[0],I=void 0;if(M>=1){if(M>1&&!c){var T=Hw(u,S[0]);s.attr({x:T[0]+_,y:T[1]+x}),r&&(I=h.getRawValue(S[0]))}else{(T=l.getPointOn(m,b))&&s.attr({x:T[0]+_,y:T[1]+x});var C=h.getRawValue(S[0]),D=h.getRawValue(S[1]);r&&(I=Fr(n,p,C,D,w.t))}i.lastFrameIndex=S[0]}else{var A=1===t||i.lastFrameIndex>0?S[0]:0;T=Hw(u,A);r&&(I=h.getRawValue(A)),s.attr({x:T[0]+_,y:T[1]+x})}r&&_h(s).setLabelText(I)}},e.prototype._doUpdateAnimation=function(t,e,n,i,r,o){var a=this._polyline,s=this._polygon,l=t.hostModel,u=function(t,e,n,i,r,o,a,s){for(var l=function(t,e){var n=[];return e.diff(t).add((function(t){n.push({cmd:\"+\",idx:t})})).update((function(t,e){n.push({cmd:\"=\",idx:e,idx1:t})})).remove((function(t){n.push({cmd:\"-\",idx:t})})).execute(),n}(t,e),u=[],h=[],c=[],p=[],d=[],f=[],g=[],y=_w(r,e,a),v=t.getLayout(\"points\")||[],m=e.getLayout(\"points\")||[],_=0;_<l.length;_++){var x=l[_],b=!0,w=void 0,S=void 0;switch(x.cmd){case\"=\":w=2*x.idx,S=2*x.idx1;var M=v[w],I=v[w+1],T=m[S],C=m[S+1];(isNaN(M)||isNaN(I))&&(M=T,I=C),u.push(M,I),h.push(T,C),c.push(n[w],n[w+1]),p.push(i[S],i[S+1]),g.push(e.getRawIndex(x.idx1));break;case\"+\":var D=x.idx,A=y.dataDimsForPoint,L=r.dataToPoint([e.get(A[0],D),e.get(A[1],D)]);S=2*D,u.push(L[0],L[1]),h.push(m[S],m[S+1]);var k=xw(y,r,e,D);c.push(k[0],k[1]),p.push(i[S],i[S+1]),g.push(e.getRawIndex(D));break;case\"-\":b=!1}b&&(d.push(x),f.push(f.length))}f.sort((function(t,e){return g[t]-g[e]}));var P=u.length,O=Sw(P),R=Sw(P),N=Sw(P),z=Sw(P),E=[];for(_=0;_<f.length;_++){var V=f[_],B=2*_,F=2*V;O[B]=u[F],O[B+1]=u[F+1],R[B]=h[F],R[B+1]=h[F+1],N[B]=c[F],N[B+1]=c[F+1],z[B]=p[F],z[B+1]=p[F+1],E[_]=d[V]}return{current:O,next:R,stackedOnCurrent:N,stackedOnNext:z,status:E}}(this._data,t,this._stackedOnPoints,e,this._coordSys,0,this._valueOrigin),h=u.current,c=u.stackedOnCurrent,p=u.next,d=u.stackedOnNext;if(r&&(h=Fw(u.current,n,r),c=Fw(u.stackedOnCurrent,n,r),p=Fw(u.next,n,r),d=Fw(u.stackedOnNext,n,r)),Vw(h,p)>3e3||s&&Vw(c,d)>3e3)return a.setShape({points:p}),void(s&&s.setShape({points:p,stackedOnPoints:d}));a.shape.__points=u.current,a.shape.points=h;var f={shape:{points:p}};u.current!==h&&(f.shape.__points=u.next),a.stopAnimation(),Hu(a,f,l),s&&(s.setShape({points:h,stackedOnPoints:c}),s.stopAnimation(),Hu(s,{shape:{stackedOnPoints:d}},l),a.shape.points!==s.shape.points&&(s.shape.points=a.shape.points));for(var g=[],y=u.status,v=0;v<y.length;v++){if(\"=\"===y[v].cmd){var m=t.getItemGraphicEl(y[v].idx1);m&&g.push({el:m,ptIdx:v})}}a.animators&&a.animators.length&&a.animators[0].during((function(){s&&s.dirtyShape();for(var t=a.shape.__points,e=0;e<g.length;e++){var n=g[e].el,i=2*g[e].ptIdx;n.x=t[i],n.y=t[i+1],n.markRedraw()}}))},e.prototype.remove=function(t){var e=this.group,n=this._data;this._lineGroup.removeAll(),this._symbolDraw.remove(!0),n&&n.eachItemGraphicEl((function(t,i){t.__temp&&(e.remove(t),n.setItemGraphicEl(i,null))})),this._polyline=this._polygon=this._coordSys=this._points=this._stackedOnPoints=this._endLabel=this._data=null},e.type=\"line\",e}(Tf);function Xw(t,e){return{seriesType:t,plan:Sf(),reset:function(t){var n=t.getData(),i=t.coordinateSystem,r=t.pipelineContext,o=e||r.large;if(i){var a=O(i.dimensions,(function(t){return n.mapDimension(t)})).slice(0,2),s=a.length,l=n.getCalculationInfo(\"stackResultDimension\");V_(n,a[0])&&(a[0]=l),V_(n,a[1])&&(a[1]=l);var u=n.getDimensionInfo(a[0]),h=n.getDimensionInfo(a[1]),c=u&&u.index,p=h&&h.index;return s&&{progress:function(t,e){for(var n=t.end-t.start,r=o&&Sw(n*s),a=[],l=[],u=t.start,h=0;u<t.end;u++){var d=void 0;if(1===s){var f=e.getByDimIdx(c,u);d=i.dataToPoint(f,null,l)}else a[0]=e.getByDimIdx(c,u),a[1]=e.getByDimIdx(p,u),d=i.dataToPoint(a,null,l);o?(r[h++]=d[0],r[h++]=d[1]):e.setItemLayout(u,d.slice())}o&&e.setLayout(\"points\",r)}}}}}}var Yw={average:function(t){for(var e=0,n=0,i=0;i<t.length;i++)isNaN(t[i])||(e+=t[i],n++);return 0===n?NaN:e/n},sum:function(t){for(var e=0,n=0;n<t.length;n++)e+=t[n]||0;return e},max:function(t){for(var e=-1/0,n=0;n<t.length;n++)t[n]>e&&(e=t[n]);return isFinite(e)?e:NaN},min:function(t){for(var e=1/0,n=0;n<t.length;n++)t[n]<e&&(e=t[n]);return isFinite(e)?e:NaN},nearest:function(t){return t[0]}},Zw=function(t){return Math.round(t.length/2)};function jw(t){return{seriesType:t,reset:function(t,e,n){var i=t.getData(),r=t.get(\"sampling\"),o=t.coordinateSystem,a=i.count();if(a>10&&\"cartesian2d\"===o.type&&r){var s=o.getBaseAxis(),l=o.getOtherAxis(s),u=s.getExtent(),h=n.getDevicePixelRatio(),c=Math.abs(u[1]-u[0])*(h||1),p=Math.round(a/c);if(p>1){\"lttb\"===r&&t.setData(i.lttbDownSample(i.mapDimension(l.dim),1/p));var d=void 0;\"string\"==typeof r?d=Yw[r]:\"function\"==typeof r&&(d=r),d&&t.setData(i.downSample(i.mapDimension(l.dim),1/p,d,Zw))}}}}}var qw=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(t,e){return F_(this.getSource(),this,{useEncodeDefaulter:!0})},e.prototype.getMarkerPosition=function(t){var e=this.coordinateSystem;if(e){var n=e.dataToPoint(e.clampData(t)),i=this.getData(),r=i.getLayout(\"offset\"),o=i.getLayout(\"size\");return n[e.getBaseAxis().isHorizontal()?0:1]+=r+o/2,n}return[NaN,NaN]},e.type=\"series.__base_bar__\",e.defaultOption={zlevel:0,z:2,coordinateSystem:\"cartesian2d\",legendHoverLink:!0,barMinHeight:0,barMinAngle:0,large:!1,largeThreshold:400,progressive:3e3,progressiveChunkMode:\"mod\"},e}(ff);ff.registerClass(qw);var Kw=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(){return F_(this.getSource(),this,{useEncodeDefaulter:!0,createInvertedIndices:!!this.get(\"realtimeSort\",!0)||null})},e.prototype.getProgressive=function(){return!!this.get(\"large\")&&this.get(\"progressive\")},e.prototype.getProgressiveThreshold=function(){var t=this.get(\"progressiveThreshold\"),e=this.get(\"largeThreshold\");return e>t&&(t=e),t},e.prototype.brushSelector=function(t,e,n){return n.rect(e.getItemLayout(t))},e.type=\"series.bar\",e.dependencies=[\"grid\",\"polar\"],e.defaultOption=zh(qw.defaultOption,{clip:!0,roundCap:!1,showBackground:!1,backgroundStyle:{color:\"rgba(180, 180, 180, 0.2)\",borderColor:null,borderWidth:0,borderType:\"solid\",borderRadius:0,shadowBlur:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,opacity:1},select:{itemStyle:{borderColor:\"#212121\"}},realtimeSort:!1}),e}(qw),$w=function(){this.cx=0,this.cy=0,this.r0=0,this.r=0,this.startAngle=0,this.endAngle=2*Math.PI,this.clockwise=!0},Jw=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"sausage\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new $w},e.prototype.buildPath=function(t,e){var n=e.cx,i=e.cy,r=Math.max(e.r0||0,0),o=Math.max(e.r,0),a=.5*(o-r),s=r+a,l=e.startAngle,u=e.endAngle,h=e.clockwise,c=Math.cos(l),p=Math.sin(l),d=Math.cos(u),f=Math.sin(u);(h?u-l<2*Math.PI:l-u<2*Math.PI)&&(t.moveTo(c*r+n,p*r+i),t.arc(c*s+n,p*s+i,a,-Math.PI+l,l,!h)),t.arc(n,i,o,l,u,!h),t.moveTo(d*o+n,f*o+i),t.arc(d*s+n,f*s+i,a,u-2*Math.PI,u-Math.PI,!h),0!==r&&(t.arc(n,i,r,u,l,h),t.moveTo(c*r+n,f*r+i)),t.closePath()},e}(Ka),Qw=[0,0],tS=Math.max,eS=Math.min;var nS=function(t){function e(){var n=t.call(this)||this;return n.type=e.type,n._isFirstFrame=!0,n}return n(e,t),e.prototype.render=function(t,e,n,i){this._model=t,this._removeOnRenderedListener(n),this._updateDrawMode(t);var r=t.get(\"coordinateSystem\");(\"cartesian2d\"===r||\"polar\"===r)&&(this._isLargeDraw?this._renderLarge(t,e,n):this._renderNormal(t,e,n,i))},e.prototype.incrementalPrepareRender=function(t){this._clear(),this._updateDrawMode(t),this._updateLargeClip(t)},e.prototype.incrementalRender=function(t,e){this._incrementalRenderLarge(t,e)},e.prototype._updateDrawMode=function(t){var e=t.pipelineContext.large;null!=this._isLargeDraw&&e===this._isLargeDraw||(this._isLargeDraw=e,this._clear())},e.prototype._renderNormal=function(t,e,n,i){var r,o=this.group,a=t.getData(),s=this._data,l=t.coordinateSystem,u=l.getBaseAxis();\"cartesian2d\"===l.type?r=u.isHorizontal():\"polar\"===l.type&&(r=\"angle\"===u.dim);var h=t.isAnimationEnabled()?t:null,c=function(t,e){var n=t.get(\"realtimeSort\",!0),i=e.getBaseAxis();0;if(n&&\"category\"===i.type&&\"cartesian2d\"===e.type)return{baseAxis:i,otherAxis:e.getOtherAxis(i)}}(t,l);c&&this._enableRealtimeSort(c,a,n);var p=t.get(\"clip\",!0)||c,d=function(t,e){var n=t.getArea&&t.getArea();if(Nw(t,\"cartesian2d\")){var i=t.getBaseAxis();if(\"category\"!==i.type||!i.onBand){var r=e.getLayout(\"bandWidth\");i.isHorizontal()?(n.x-=r,n.width+=2*r):(n.y-=r,n.height+=2*r)}}return n}(l,a);o.removeClipPath();var f=t.get(\"roundCap\",!0),g=t.get(\"showBackground\",!0),y=t.getModel(\"backgroundStyle\"),v=y.get(\"borderRadius\")||0,m=[],_=this._backgroundEls,x=i&&i.isInitSort,b=i&&\"changeAxisOrder\"===i.type;function w(t){var e=aS[l.type](a,t),n=function(t,e,n){return new(\"polar\"===t.type?Jl:ls)({shape:pS(e,n,t),silent:!0,z2:0})}(l,r,e);return n.useStyle(y.getItemStyle()),\"cartesian2d\"===l.type&&n.setShape(\"r\",v),m[t]=n,n}a.diff(s).add((function(e){var n=a.getItemModel(e),i=aS[l.type](a,e,n);if(g&&w(e),a.hasValue(e)){var s=!1;p&&(s=iS[l.type](d,i));var y=rS[l.type](t,a,e,i,r,h,u.model,!1,f);sS(y,a,e,n,i,t,r,\"polar\"===l.type),x?y.attr({shape:i}):c?oS(c,h,y,i,e,r,!1,!1):Wu(y,{shape:i},t,e),a.setItemGraphicEl(e,y),o.add(y),y.ignore=s}})).update((function(e,n){var i=a.getItemModel(e),S=aS[l.type](a,e,i);if(g){var M=void 0;0===_.length?M=w(n):((M=_[n]).useStyle(y.getItemStyle()),\"cartesian2d\"===l.type&&M.setShape(\"r\",v),m[e]=M);var I=aS[l.type](a,e);Hu(M,{shape:pS(r,I,l)},h,e)}var T=s.getItemGraphicEl(n);if(!a.hasValue(e))return o.remove(T),void(T=null);var C=!1;p&&(C=iS[l.type](d,S))&&o.remove(T),T||(T=rS[l.type](t,a,e,S,r,h,u.model,!!T,f)),b||sS(T,a,e,i,S,t,r,\"polar\"===l.type),x?T.attr({shape:S}):c?oS(c,h,T,S,e,r,!0,b):Hu(T,{shape:S},t,e,null),a.setItemGraphicEl(e,T),T.ignore=C,o.add(T)})).remove((function(e){var n=s.getItemGraphicEl(e);n&&Yu(n,t,e)})).execute();var S=this._backgroundGroup||(this._backgroundGroup=new Ei);S.removeAll();for(var M=0;M<m.length;++M)S.add(m[M]);o.add(S),this._backgroundEls=m,this._data=a},e.prototype._renderLarge=function(t,e,n){this._clear(),hS(t,this.group),this._updateLargeClip(t)},e.prototype._incrementalRenderLarge=function(t,e){this._removeBackground(),hS(e,this.group,!0)},e.prototype._updateLargeClip=function(t){var e=t.get(\"clip\",!0)?Rw(t.coordinateSystem,!1,t):null;e?this.group.setClipPath(e):this.group.removeClipPath()},e.prototype._enableRealtimeSort=function(t,e,n){var i=this;if(e.count()){var r=t.baseAxis;if(this._isFirstFrame)this._dispatchInitSort(e,t,n),this._isFirstFrame=!1;else{var o=function(t){var n=e.getItemGraphicEl(t);if(n){var i=n.shape;return(r.isHorizontal()?Math.abs(i.height):Math.abs(i.width))||0}return 0};this._onRendered=function(){i._updateSortWithinSameData(e,o,r,n)},n.getZr().on(\"rendered\",this._onRendered)}}},e.prototype._dataSort=function(t,e,n){var i=[];return t.each(t.mapDimension(e.dim),(function(t,e){var r=n(e);r=null==r?NaN:r,i.push({dataIndex:e,mappedValue:r,ordinalNumber:t})})),i.sort((function(t,e){return e.mappedValue-t.mappedValue})),{ordinalNumbers:O(i,(function(t){return t.ordinalNumber}))}},e.prototype._isOrderChangedWithinSameData=function(t,e,n){for(var i=n.scale,r=t.mapDimension(n.dim),o=Number.MAX_VALUE,a=0,s=i.getOrdinalMeta().categories.length;a<s;++a){var l=t.rawIndexOf(r,i.getRawOrdinalNumber(a)),u=l<0?Number.MIN_VALUE:e(t.indexOfRawIndex(l));if(u>o)return!0;o=u}return!1},e.prototype._isOrderDifferentInView=function(t,e){for(var n=e.scale,i=n.getExtent(),r=Math.max(0,i[0]),o=Math.min(i[1],n.getOrdinalMeta().categories.length-1);r<=o;++r)if(t.ordinalNumbers[r]!==n.getRawOrdinalNumber(r))return!0},e.prototype._updateSortWithinSameData=function(t,e,n,i){if(this._isOrderChangedWithinSameData(t,e,n)){var r=this._dataSort(t,n,e);this._isOrderDifferentInView(r,n)&&(this._removeOnRenderedListener(i),i.dispatchAction({type:\"changeAxisOrder\",componentType:n.dim+\"Axis\",axisId:n.index,sortInfo:r}))}},e.prototype._dispatchInitSort=function(t,e,n){var i=e.baseAxis,r=this._dataSort(t,i,(function(n){return t.get(t.mapDimension(e.otherAxis.dim),n)}));n.dispatchAction({type:\"changeAxisOrder\",componentType:i.dim+\"Axis\",isInitSort:!0,axisId:i.index,sortInfo:r,animation:{duration:0}})},e.prototype.remove=function(t,e){this._clear(this._model),this._removeOnRenderedListener(e)},e.prototype.dispose=function(t,e){this._removeOnRenderedListener(e)},e.prototype._removeOnRenderedListener=function(t){this._onRendered&&(t.getZr().off(\"rendered\",this._onRendered),this._onRendered=null)},e.prototype._clear=function(t){var e=this.group,n=this._data;t&&t.isAnimationEnabled()&&n&&!this._isLargeDraw?(this._removeBackground(),this._backgroundEls=[],n.eachItemGraphicEl((function(e){Yu(e,t,_s(e).dataIndex)}))):e.removeAll(),this._data=null,this._isFirstFrame=!0},e.prototype._removeBackground=function(){this.group.remove(this._backgroundGroup),this._backgroundGroup=null},e.type=\"bar\",e}(Tf),iS={cartesian2d:function(t,e){var n=e.width<0?-1:1,i=e.height<0?-1:1;n<0&&(e.x+=e.width,e.width=-e.width),i<0&&(e.y+=e.height,e.height=-e.height);var r=t.x+t.width,o=t.y+t.height,a=tS(e.x,t.x),s=eS(e.x+e.width,r),l=tS(e.y,t.y),u=eS(e.y+e.height,o),h=s<a,c=u<l;return e.x=h&&a>r?s:a,e.y=c&&l>o?u:l,e.width=h?0:s-a,e.height=c?0:u-l,n<0&&(e.x+=e.width,e.width=-e.width),i<0&&(e.y+=e.height,e.height=-e.height),h||c},polar:function(t,e){var n=e.r0<=e.r?1:-1;if(n<0){var i=e.r;e.r=e.r0,e.r0=i}var r=eS(e.r,t.r),o=tS(e.r0,t.r0);e.r=r,e.r0=o;var a=r-o<0;if(n<0){i=e.r;e.r=e.r0,e.r0=i}return a}},rS={cartesian2d:function(t,e,n,i,r,o,a,s,l){var u=new ls({shape:I({},i),z2:1});(u.__dataIndex=n,u.name=\"item\",o)&&(u.shape[r?\"height\":\"width\"]=0);return u},polar:function(t,e,n,i,r,o,a,s,l){var u=i.startAngle<i.endAngle,h=new(!r&&l?Jw:Jl)({shape:T({clockwise:u},i),z2:1});if(h.name=\"item\",o){var c=r?\"r\":\"endAngle\",p={};h.shape[c]=r?0:i.startAngle,p[c]=i[c],(s?Hu:Wu)(h,{shape:p},o)}return h}};function oS(t,e,n,i,r,o,a,s){var l,u;o?(u={x:i.x,width:i.width},l={y:i.y,height:i.height}):(u={y:i.y,height:i.height},l={x:i.x,width:i.width}),s||(a?Hu:Wu)(n,{shape:l},e,r,null),(a?Hu:Wu)(n,{shape:u},e?t.baseAxis.model:null,r)}var aS={cartesian2d:function(t,e,n){var i=t.getItemLayout(e),r=n?function(t,e){var n=t.get([\"itemStyle\",\"borderColor\"]);if(!n||\"none\"===n)return 0;var i=t.get([\"itemStyle\",\"borderWidth\"])||0,r=isNaN(e.width)?Number.MAX_VALUE:Math.abs(e.width),o=isNaN(e.height)?Number.MAX_VALUE:Math.abs(e.height);return Math.min(i,r,o)}(n,i):0,o=i.width>0?1:-1,a=i.height>0?1:-1;return{x:i.x+o*r/2,y:i.y+a*r/2,width:i.width-o*r,height:i.height-a*r}},polar:function(t,e,n){var i=t.getItemLayout(e);return{cx:i.cx,cy:i.cy,r0:i.r0,r:i.r,startAngle:i.startAngle,endAngle:i.endAngle}}};function sS(t,e,n,i,r,o,a,s){var l=e.getItemVisual(n,\"style\");s||t.setShape(\"r\",i.get([\"itemStyle\",\"borderRadius\"])||0),t.useStyle(l);var u=i.getShallow(\"cursor\");if(u&&t.attr(\"cursor\",u),!s){var h=a?r.height>0?\"bottom\":\"top\":r.width>0?\"left\":\"right\",c=ch(i);hh(t,c,{labelFetcher:o,labelDataIndex:n,defaultText:cw(o.getData(),n),inheritColor:l.fill,defaultOpacity:l.opacity,defaultOutsidePosition:h}),xh(t.getTextContent(),c,o.getRawValue(n),(function(t){return pw(e,t)}))}var p=i.getModel([\"emphasis\"]);sl(t,p.get(\"focus\"),p.get(\"blurScope\")),cl(t,i),function(t){return null!=t.startAngle&&null!=t.endAngle&&t.startAngle===t.endAngle}(r)&&(t.style.fill=\"none\",t.style.stroke=\"none\",P(t.states,(function(t){t.style&&(t.style.fill=t.style.stroke=\"none\")})))}var lS=function(){},uS=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"largeBar\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new lS},e.prototype.buildPath=function(t,e){for(var n=e.points,i=this.__startPoint,r=this.__baseDimIdx,o=0;o<n.length;o+=2)i[r]=n[o+r],t.moveTo(i[0],i[1]),t.lineTo(n[o],n[o+1])},e}(Ka);function hS(t,e,n){var i=t.getData(),r=[],o=i.getLayout(\"valueAxisHorizontal\")?1:0;r[1-o]=i.getLayout(\"valueAxisStart\");var a=i.getLayout(\"largeDataIndices\"),s=i.getLayout(\"barWidth\"),l=t.getModel(\"backgroundStyle\");if(t.get(\"showBackground\",!0)){var u=i.getLayout(\"largeBackgroundPoints\"),h=[];h[1-o]=i.getLayout(\"backgroundStart\");var c=new uS({shape:{points:u},incremental:!!n,silent:!0,z2:0});c.__startPoint=h,c.__baseDimIdx=o,c.__largeDataIndices=a,c.__barWidth=s,function(t,e,n){var i=e.get(\"borderColor\")||e.get(\"color\"),r=e.getItemStyle();t.useStyle(r),t.style.fill=null,t.style.stroke=i,t.style.lineWidth=n.getLayout(\"barWidth\")}(c,l,i),e.add(c)}var p=new uS({shape:{points:i.getLayout(\"largePoints\")},incremental:!!n});p.__startPoint=r,p.__baseDimIdx=o,p.__largeDataIndices=a,p.__barWidth=s,e.add(p),function(t,e,n){var i=n.getVisual(\"style\");t.useStyle(I({},i)),t.style.fill=null,t.style.stroke=i.fill,t.style.lineWidth=n.getLayout(\"barWidth\")}(p,0,i),_s(p).seriesIndex=t.seriesIndex,t.get(\"silent\")||(p.on(\"mousedown\",cS),p.on(\"mousemove\",cS))}var cS=Nf((function(t){var e=function(t,e,n){var i=t.__baseDimIdx,r=1-i,o=t.shape.points,a=t.__largeDataIndices,s=Math.abs(t.__barWidth/2),l=t.__startPoint[r];Qw[0]=e,Qw[1]=n;for(var u=Qw[i],h=Qw[1-i],c=u-s,p=u+s,d=0,f=o.length/2;d<f;d++){var g=2*d,y=o[g+i],v=o[g+r];if(y>=c&&y<=p&&(l<=v?h>=l&&h<=v:h>=v&&h<=l))return a[d]}return-1}(this,t.offsetX,t.offsetY);_s(this).dataIndex=e>=0?e:null}),30,!1);function pS(t,e,n){if(Nw(n,\"cartesian2d\")){var i=e,r=n.getArea();return{x:t?i.x:r.x,y:t?r.y:i.y,width:t?i.width:r.width,height:t?r.height:i.height}}var o=e;return{cx:(r=n.getArea()).cx,cy:r.cy,r0:t?r.r0:o.r0,r:t?r.r:o.r,startAngle:t?o.startAngle:0,endAngle:t?o.endAngle:2*Math.PI}}var dS=2*Math.PI,fS=Math.PI/180;function gS(t,e,n){e.eachSeriesByType(t,(function(t){var e=t.getData(),i=e.mapDimension(\"value\"),r=function(t,e){return Vc(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,n),o=t.get(\"center\"),a=t.get(\"radius\");F(a)||(a=[0,a]),F(o)||(o=[o,o]);var s=Zi(r.width,n.getWidth()),l=Zi(r.height,n.getHeight()),u=Math.min(s,l),h=Zi(o[0],s)+r.x,c=Zi(o[1],l)+r.y,p=Zi(a[0],u/2),d=Zi(a[1],u/2),f=-t.get(\"startAngle\")*fS,g=t.get(\"minAngle\")*fS,y=0;e.each(i,(function(t){!isNaN(t)&&y++}));var v=e.getSum(i),m=Math.PI/(v||y)*2,_=t.get(\"clockwise\"),x=t.get(\"roseType\"),b=t.get(\"stillShowZeroSum\"),w=e.getDataExtent(i);w[0]=0;var S=dS,M=0,I=f,T=_?1:-1;if(e.setLayout({viewRect:r,r:d}),e.each(i,(function(t,n){var i;if(isNaN(t))e.setItemLayout(n,{angle:NaN,startAngle:NaN,endAngle:NaN,clockwise:_,cx:h,cy:c,r0:p,r:x?NaN:d});else{(i=\"area\"!==x?0===v&&b?m:t*m:dS/y)<g?(i=g,S-=g):M+=t;var r=I+T*i;e.setItemLayout(n,{angle:i,startAngle:I,endAngle:r,clockwise:_,cx:h,cy:c,r0:p,r:x?Yi(t,w,[p,d]):d}),I=r}})),S<dS&&y)if(S<=.001){var C=dS/y;e.each(i,(function(t,n){if(!isNaN(t)){var i=e.getItemLayout(n);i.angle=C,i.startAngle=f+T*n*C,i.endAngle=f+T*(n+1)*C}}))}else m=S/M,I=f,e.each(i,(function(t,n){if(!isNaN(t)){var i=e.getItemLayout(n),r=i.angle===g?g:t*m;i.startAngle=I,i.endAngle=I+T*r,I+=T*r}}))}))}function yS(t){return{seriesType:t,reset:function(t,e){var n=e.findComponents({mainType:\"legend\"});if(n&&n.length){var i=t.getData();i.filterSelf((function(t){for(var e=i.getName(t),r=0;r<n.length;r++)if(!n[r].isSelected(e))return!1;return!0}))}}}}var vS=Math.PI/180;function mS(t,e,n,i,r,o,a,s,l,u){if(!(t.length<2)){for(var h=t.length,c=0;c<h;c++)if(\"outer\"===t[c].position&&\"labelLine\"===t[c].labelAlignTo){var p=t[c].label.x-u;t[c].linePoints[1][0]+=p,t[c].label.x=u}Xg(t,l,l+a)&&function(t){for(var o={list:[],maxY:0},a={list:[],maxY:0},s=0;s<t.length;s++)if(\"none\"===t[s].labelAlignTo){var l=t[s],u=l.label.y>n?a:o,h=Math.abs(l.label.y-n);if(h>u.maxY){var c=l.label.x-e-l.len2*r,p=i+l.len,f=Math.abs(c)<p?Math.sqrt(h*h/(1-c*c/p/p)):p;u.rB=f,u.maxY=h}u.list.push(l)}d(o),d(a)}(t)}function d(t){for(var o=t.rB,a=o*o,s=0;s<t.list.length;s++){var l=t.list[s],u=Math.abs(l.label.y-n),h=i+l.len,c=h*h,p=Math.sqrt((1-Math.abs(u*u/a))*c);l.label.x=e+(p+l.len2)*r}}}function _S(t){return\"center\"===t.position}function xS(t){var e,n,i=t.getData(),r=[],o=!1,a=(t.get(\"minShowLabelAngle\")||0)*vS,s=i.getLayout(\"viewRect\"),l=i.getLayout(\"r\"),u=s.width,h=s.x,c=s.y,p=s.height;function d(t){t.ignore=!0}i.each((function(t){var s=i.getItemGraphicEl(t),c=s.shape,p=s.getTextContent(),f=s.getTextGuideLine(),g=i.getItemModel(t),y=g.getModel(\"label\"),v=y.get(\"position\")||g.get([\"emphasis\",\"label\",\"position\"]),m=y.get(\"distanceToLabelLine\"),_=y.get(\"alignTo\"),x=Zi(y.get(\"edgeDistance\"),u),b=y.get(\"bleedMargin\"),w=g.getModel(\"labelLine\"),S=w.get(\"length\");S=Zi(S,u);var M=w.get(\"length2\");if(M=Zi(M,u),Math.abs(c.endAngle-c.startAngle)<a)return P(p.states,d),void(p.ignore=!0);if(function(t){if(!t.ignore)return!0;for(var e in t.states)if(!1===t.states[e].ignore)return!0;return!1}(p)){var I,T,C,D,A=(c.startAngle+c.endAngle)/2,L=Math.cos(A),k=Math.sin(A);e=c.cx,n=c.cy;var O,R=\"inside\"===v||\"inner\"===v;if(\"center\"===v)I=c.cx,T=c.cy,D=\"center\";else{var N=(R?(c.r+c.r0)/2*L:c.r*L)+e,z=(R?(c.r+c.r0)/2*k:c.r*k)+n;if(I=N+3*L,T=z+3*k,!R){var E=N+L*(S+l-c.r),V=z+k*(S+l-c.r),B=E+(L<0?-1:1)*M;I=\"edge\"===_?L<0?h+x:h+u-x:B+(L<0?-m:m),T=V,C=[[N,z],[E,V],[B,V]]}D=R?\"center\":\"edge\"===_?L>0?\"right\":\"left\":L>0?\"left\":\"right\"}var F=y.get(\"rotate\");if(O=\"number\"==typeof F?F*(Math.PI/180):F?L<0?-A+Math.PI:-A:0,o=!!O,p.x=I,p.y=T,p.rotation=O,p.setStyle({verticalAlign:\"middle\"}),R){p.setStyle({align:D});var G=p.states.select;G&&(G.x+=p.x,G.y+=p.y)}else{var H=p.getBoundingRect().clone();H.applyTransform(p.getComputedTransform());var W=(p.style.margin||0)+2.1;H.y-=W/2,H.height+=W,r.push({label:p,labelLine:f,position:v,len:S,len2:M,minTurnAngle:w.get(\"minTurnAngle\"),maxSurfaceAngle:w.get(\"maxSurfaceAngle\"),surfaceNormal:new ai(L,k),linePoints:C,textAlign:D,labelDistance:m,labelAlignTo:_,edgeDistance:x,bleedMargin:b,rect:H})}s.setTextConfig({inside:R})}})),!o&&t.get(\"avoidLabelOverlap\")&&function(t,e,n,i,r,o,a,s){for(var l=[],u=[],h=Number.MAX_VALUE,c=-Number.MAX_VALUE,p=0;p<t.length;p++){var d=t[p].label;_S(t[p])||(d.x<e?(h=Math.min(h,d.x),l.push(t[p])):(c=Math.max(c,d.x),u.push(t[p])))}for(mS(u,e,n,i,1,0,o,0,s,c),mS(l,e,n,i,-1,0,o,0,s,h),p=0;p<t.length;p++){var f=t[p];if(d=f.label,!_S(f)){var g=f.linePoints;if(g){var y=\"edge\"===f.labelAlignTo,v=f.rect.width,m=void 0;(m=y?d.x<e?g[2][0]-f.labelDistance-a-f.edgeDistance:a+r-f.edgeDistance-g[2][0]-f.labelDistance:d.x<e?d.x-a-f.bleedMargin:a+r-d.x-f.bleedMargin)<f.rect.width&&(f.label.style.width=m,\"edge\"===f.labelAlignTo&&(v=m));var _=g[1][0]-g[2][0];y?d.x<e?g[2][0]=a+f.edgeDistance+v+f.labelDistance:g[2][0]=a+r-f.edgeDistance-v-f.labelDistance:(d.x<e?g[2][0]=d.x+f.labelDistance:g[2][0]=d.x-f.labelDistance,g[1][0]=g[2][0]+_),g[1][1]=g[2][1]=d.y}}}}(r,e,n,l,u,p,h,c);for(var f=0;f<r.length;f++){var g=r[f],y=g.label,v=g.labelLine,m=isNaN(y.x)||isNaN(y.y);if(y){y.setStyle({align:g.textAlign}),m&&(P(y.states,d),y.ignore=!0);var _=y.states.select;_&&(_.x+=y.x,_.y+=y.y)}if(v){var x=g.linePoints;m||!x?(P(v.states,d),v.ignore=!0):(Vg(x,g.minTurnAngle),Bg(x,g.surfaceNormal,g.maxSurfaceAngle),v.setShape({points:x}),y.__hostTarget.textGuideLineConfig={anchor:new ai(x[0][0],x[0][1])})}}}function bS(t,e){var n=t.get(\"borderRadius\");return null==n?null:(F(n)||(n=[n,n]),{innerCornerRadius:Ii(n[0],e.r0),cornerRadius:Ii(n[1],e.r)})}var wS=function(t){function e(e,n,i){var r=t.call(this)||this;r.z2=2;var o=new cs;return r.setTextContent(o),r.updateData(e,n,i,!0),r}return n(e,t),e.prototype.updateData=function(t,e,n,r){var o=this,a=t.hostModel,s=t.getItemModel(e),l=s.getModel(\"emphasis\"),u=t.getItemLayout(e),h=I(bS(s.getModel(\"itemStyle\"),u)||{},u);if(isNaN(h.startAngle))o.setShape(h);else{if(r)o.setShape(h),\"scale\"===a.getShallow(\"animationType\")?(o.shape.r=u.r0,Wu(o,{shape:{r:u.r}},a,e)):null!=n?(o.setShape({startAngle:n,endAngle:n}),Wu(o,{shape:{startAngle:u.startAngle,endAngle:u.endAngle}},a,e)):(o.shape.endAngle=u.startAngle,Hu(o,{shape:{endAngle:u.endAngle}},a,e));else Hu(o,{shape:h},a,e);o.useStyle(t.getItemVisual(e,\"style\")),cl(o,s);var c=(u.startAngle+u.endAngle)/2,p=a.get(\"selectedOffset\"),d=Math.cos(c)*p,f=Math.sin(c)*p,g=s.getShallow(\"cursor\");g&&o.attr(\"cursor\",g),this._updateLabel(a,t,e),o.ensureState(\"emphasis\").shape=i({r:u.r+(l.get(\"scale\")&&l.get(\"scaleSize\")||0)},bS(l.getModel(\"itemStyle\"),u)),I(o.ensureState(\"select\"),{x:d,y:f,shape:bS(s.getModel([\"select\",\"itemStyle\"]),u)}),I(o.ensureState(\"blur\"),{shape:bS(s.getModel([\"blur\",\"itemStyle\"]),u)});var y=o.getTextGuideLine(),v=o.getTextContent();y&&I(y.ensureState(\"select\"),{x:d,y:f}),I(v.ensureState(\"select\"),{x:d,y:f}),sl(this,l.get(\"focus\"),l.get(\"blurScope\"))}},e.prototype._updateLabel=function(t,e,n){var i=this,r=e.getItemModel(n),o=r.getModel(\"labelLine\"),a=e.getItemVisual(n,\"style\"),s=a&&a.fill,l=a&&a.opacity;hh(i,ch(r),{labelFetcher:e.hostModel,labelDataIndex:n,inheritColor:s,defaultOpacity:l,defaultText:t.getFormattedLabel(n,\"normal\")||e.getName(n)});var u=i.getTextContent();i.setTextConfig({position:null,rotation:null}),u.attr({z2:10});var h=t.get([\"label\",\"position\"]);if(\"outside\"!==h&&\"outer\"!==h)i.removeTextGuideLine();else{var c=this.getTextGuideLine();c||(c=new au,this.setTextGuideLine(c)),Hg(this,Wg(r),{stroke:s,opacity:et(o.get([\"lineStyle\",\"opacity\"]),l,1)})}},e}(Jl),SS=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.ignoreLabelLineUpdate=!0,e}return n(e,t),e.prototype.init=function(){var t=new Ei;this._sectorGroup=t},e.prototype.render=function(t,e,n,i){var r,o=t.getData(),a=this._data,s=this.group;if(!a&&o.count()>0){for(var l=o.getItemLayout(0),u=1;isNaN(l&&l.startAngle)&&u<o.count();++u)l=o.getItemLayout(u);l&&(r=l.startAngle)}o.diff(a).add((function(t){var e=new wS(o,t,r);o.setItemGraphicEl(t,e),s.add(e)})).update((function(t,e){var n=a.getItemGraphicEl(e);n.updateData(o,t,r),n.off(\"click\"),s.add(n),o.setItemGraphicEl(t,n)})).remove((function(e){Yu(a.getItemGraphicEl(e),t,e)})).execute(),xS(t),\"expansion\"!==t.get(\"animationTypeUpdate\")&&(this._data=o)},e.prototype.dispose=function(){},e.prototype.containPoint=function(t,e){var n=e.getData().getItemLayout(0);if(n){var i=t[0]-n.cx,r=t[1]-n.cy,o=Math.sqrt(i*i+r*r);return o<=n.r&&o>=n.r0}},e.type=\"pie\",e}(Tf);function MS(t,e,n){e=F(e)&&{coordDimensions:e}||I({},e);var i=t.getSource(),r=O_(i,e),o=new L_(r,t);return o.initData(i,n),o}var IS=function(){function t(t,e){this._getDataWithEncodedVisual=t,this._getRawData=e}return t.prototype.getAllNames=function(){var t=this._getRawData();return t.mapArray(t.getName)},t.prototype.containName=function(t){return this._getRawData().indexOfName(t)>=0},t.prototype.indexOfName=function(t){return this._getDataWithEncodedVisual().indexOfName(t)},t.prototype.getItemVisual=function(t,e){return this._getDataWithEncodedVisual().getItemVisual(t,e)},t}(),TS=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.useColorPaletteOnData=!0,e}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new IS(V(this.getData,this),V(this.getRawData,this)),this._defaultLabelLine(e)},e.prototype.mergeOption=function(){t.prototype.mergeOption.apply(this,arguments)},e.prototype.getInitialData=function(){return MS(this,{coordDimensions:[\"value\"],encodeDefaulter:B(up,this)})},e.prototype.getDataParams=function(e){var n=this.getData(),i=t.prototype.getDataParams.call(this,e),r=[];return n.each(n.mapDimension(\"value\"),(function(t){r.push(t)})),i.percent=Qi(r,e,n.hostModel.get(\"percentPrecision\")),i.$vars.push(\"percent\"),i},e.prototype._defaultLabelLine=function(t){br(t,\"labelLine\",[\"show\"]);var e=t.labelLine,n=t.emphasis.labelLine;e.show=e.show&&t.label.show,n.show=n.show&&t.emphasis.label.show},e.type=\"series.pie\",e.defaultOption={zlevel:0,z:2,legendHoverLink:!0,center:[\"50%\",\"50%\"],radius:[0,\"75%\"],clockwise:!0,startAngle:90,minAngle:0,minShowLabelAngle:0,selectedOffset:10,percentPrecision:2,stillShowZeroSum:!0,left:0,top:0,right:0,bottom:0,width:null,height:null,label:{rotate:0,show:!0,overflow:\"truncate\",position:\"outer\",alignTo:\"none\",edgeDistance:\"25%\",bleedMargin:10,distanceToLabelLine:5},labelLine:{show:!0,length:15,length2:15,smooth:!1,minTurnAngle:90,maxSurfaceAngle:90,lineStyle:{width:1,type:\"solid\"}},itemStyle:{borderWidth:1},labelLayout:{hideOverlap:!0},emphasis:{scale:!0,scaleSize:5},avoidLabelOverlap:!0,animationType:\"expansion\",animationDuration:1e3,animationTypeUpdate:\"transition\",animationEasingUpdate:\"cubicInOut\",animationDurationUpdate:500,animationEasing:\"cubicInOut\"},e}(ff);var CS=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){return F_(this.getSource(),this,{useEncodeDefaulter:!0})},e.prototype.getProgressive=function(){var t=this.option.progressive;return null==t?this.option.large?5e3:this.get(\"progressive\"):t},e.prototype.getProgressiveThreshold=function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?1e4:this.get(\"progressiveThreshold\"):t},e.prototype.brushSelector=function(t,e,n){return n.point(e.getItemLayout(t))},e.type=\"series.scatter\",e.dependencies=[\"grid\",\"polar\",\"geo\",\"singleAxis\",\"calendar\"],e.defaultOption={coordinateSystem:\"cartesian2d\",zlevel:0,z:2,legendHoverLink:!0,symbolSize:10,large:!1,largeThreshold:2e3,itemStyle:{opacity:.8},emphasis:{scale:!0},clip:!0,select:{itemStyle:{borderColor:\"#212121\"}}},e}(ff),DS=function(){},AS=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new DS},e.prototype.buildPath=function(t,e){var n=e.points,i=e.size,r=this.symbolProxy,o=r.shape,a=t.getContext?t.getContext():t;if(a&&i[0]<4)this._ctx=a;else{this._ctx=null;for(var s=0;s<n.length;){var l=n[s++],u=n[s++];isNaN(l)||isNaN(u)||(this.softClipShape&&!this.softClipShape.contain(l,u)||(o.x=l-i[0]/2,o.y=u-i[1]/2,o.width=i[0],o.height=i[1],r.buildPath(t,o,!0)))}}},e.prototype.afterBrush=function(){var t=this.shape,e=t.points,n=t.size,i=this._ctx;if(i)for(var r=0;r<e.length;){var o=e[r++],a=e[r++];isNaN(o)||isNaN(a)||(this.softClipShape&&!this.softClipShape.contain(o,a)||i.fillRect(o-n[0]/2,a-n[1]/2,n[0],n[1]))}},e.prototype.findDataIndex=function(t,e){for(var n=this.shape,i=n.points,r=n.size,o=Math.max(r[0],4),a=Math.max(r[1],4),s=i.length/2-1;s>=0;s--){var l=2*s,u=i[l]-o/2,h=i[l+1]-a/2;if(t>=u&&e>=h&&t<=u+o&&e<=h+a)return s}return-1},e}(Ka),LS=function(){function t(){this.group=new Ei}return t.prototype.isPersistent=function(){return!this._incremental},t.prototype.updateData=function(t,e){this.group.removeAll();var n=new AS({rectHover:!0,cursor:\"default\"});n.setShape({points:t.getLayout(\"points\")}),this._setCommon(n,t,!1,e),this.group.add(n),this._incremental=null},t.prototype.updateLayout=function(t){if(!this._incremental){var e=t.getLayout(\"points\");this.group.eachChild((function(t){if(null!=t.startIndex){var n=2*(t.endIndex-t.startIndex),i=4*t.startIndex*2;e=new Float32Array(e.buffer,i,n)}t.setShape(\"points\",e)}))}},t.prototype.incrementalPrepareUpdate=function(t){this.group.removeAll(),this._clearIncremental(),t.count()>2e6?(this._incremental||(this._incremental=new Tu({silent:!0})),this.group.add(this._incremental)):this._incremental=null},t.prototype.incrementalUpdate=function(t,e,n){var i;this._incremental?(i=new AS,this._incremental.addDisplayable(i,!0)):((i=new AS({rectHover:!0,cursor:\"default\",startIndex:t.start,endIndex:t.end})).incremental=!0,this.group.add(i)),i.setShape({points:e.getLayout(\"points\")}),this._setCommon(i,e,!!this._incremental,n)},t.prototype._setCommon=function(t,e,n,i){var r=e.hostModel;i=i||{};var o=e.getVisual(\"symbolSize\");t.setShape(\"size\",o instanceof Array?o:[o,o]),t.softClipShape=i.clipShape||null,t.symbolProxy=fy(e.getVisual(\"symbol\"),0,0,0,0),t.setColor=t.symbolProxy.setColor;var a=t.shape.size[0]<4;t.useStyle(r.getModel(\"itemStyle\").getItemStyle(a?[\"color\",\"shadowBlur\",\"shadowColor\"]:[\"color\"]));var s=e.getVisual(\"style\"),l=s&&s.fill;if(l&&t.setColor(l),!n){var u=_s(t);u.seriesIndex=r.seriesIndex,t.on(\"mousemove\",(function(e){u.dataIndex=null;var n=t.findDataIndex(e.offsetX,e.offsetY);n>=0&&(u.dataIndex=n+(t.startIndex||0))}))}},t.prototype.remove=function(){this._clearIncremental(),this._incremental=null,this.group.removeAll()},t.prototype._clearIncremental=function(){var t=this._incremental;t&&t.clearDisplaybles()},t}(),kS=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData();this._updateSymbolDraw(i,t).updateData(i,{clipShape:this._getClipShape(t)}),this._finished=!0},e.prototype.incrementalPrepareRender=function(t,e,n){var i=t.getData();this._updateSymbolDraw(i,t).incrementalPrepareUpdate(i),this._finished=!1},e.prototype.incrementalRender=function(t,e,n){this._symbolDraw.incrementalUpdate(t,e.getData(),{clipShape:this._getClipShape(e)}),this._finished=t.end===e.getData().count()},e.prototype.updateTransform=function(t,e,n){var i=t.getData();if(this.group.dirty(),!this._finished||i.count()>1e4||!this._symbolDraw.isPersistent())return{update:!0};var r=Xw(\"\").reset(t,e,n);r.progress&&r.progress({start:0,end:i.count(),count:i.count()},i),this._symbolDraw.updateLayout(i)},e.prototype._getClipShape=function(t){var e=t.coordinateSystem,n=e&&e.getArea&&e.getArea();return t.get(\"clip\",!0)?n:null},e.prototype._updateSymbolDraw=function(t,e){var n=this._symbolDraw,i=e.pipelineContext.large;return n&&i===this._isLargeDraw||(n&&n.remove(),n=this._symbolDraw=i?new LS:new mw,this._isLargeDraw=i,this.group.removeAll()),this.group.add(n.group),n},e.prototype.remove=function(t,e){this._symbolDraw&&this._symbolDraw.remove(!0),this._symbolDraw=null},e.prototype.dispose=function(){},e.type=\"scatter\",e}(Tf),PS=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.type=\"grid\",e.dependencies=[\"xAxis\",\"yAxis\"],e.layoutMode=\"box\",e.defaultOption={show:!1,zlevel:0,z:0,left:\"10%\",top:60,right:\"10%\",bottom:70,containLabel:!1,backgroundColor:\"rgba(0,0,0,0)\",borderWidth:1,borderColor:\"#ccc\"},e}(Xc),OS=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getCoordSysModel=function(){return this.getReferringComponents(\"grid\",Nr).models[0]},e.type=\"cartesian2dAxis\",e}(Xc);L(OS,Yx);var RS={show:!0,zlevel:0,z:0,inverse:!1,name:\"\",nameLocation:\"end\",nameRotate:null,nameTruncate:{maxWidth:null,ellipsis:\"...\",placeholder:\".\"},nameTextStyle:{},nameGap:15,silent:!1,triggerEvent:!1,tooltip:{show:!1},axisPointer:{},axisLine:{show:!0,onZero:!0,onZeroAxisIndex:null,lineStyle:{color:\"#6E7079\",width:1,type:\"solid\"},symbol:[\"none\",\"none\"],symbolSize:[10,15]},axisTick:{show:!0,inside:!1,length:5,lineStyle:{width:1}},axisLabel:{show:!0,inside:!1,rotate:0,showMinLabel:null,showMaxLabel:null,margin:8,fontSize:12},splitLine:{show:!0,lineStyle:{color:[\"#E0E6F1\"],width:1,type:\"solid\"}},splitArea:{show:!1,areaStyle:{color:[\"rgba(250,250,250,0.2)\",\"rgba(210,219,238,0.2)\"]}}},NS=S({boundaryGap:!0,deduplication:null,splitLine:{show:!1},axisTick:{alignWithLabel:!1,interval:\"auto\"},axisLabel:{interval:\"auto\"}},RS),zS=S({boundaryGap:[0,0],axisLine:{show:\"auto\"},axisTick:{show:\"auto\"},splitNumber:5,minorTick:{show:!1,splitNumber:5,length:3,lineStyle:{}},minorSplitLine:{show:!1,lineStyle:{color:\"#F4F7FD\",width:1}}},RS),ES={category:NS,value:zS,time:S({scale:!0,splitNumber:6,axisLabel:{showMinLabel:!1,showMaxLabel:!1,rich:{primary:{fontWeight:\"bold\"}}},splitLine:{show:!1}},zS),log:T({scale:!0,logBase:10},zS)},VS={value:1,category:1,time:1,log:1};function BS(t,e,i,r){P(VS,(function(o,a){var s=S(S({},ES[a],!0),r,!0),l=function(t){function i(){for(var n=[],i=0;i<arguments.length;i++)n[i]=arguments[i];var r=t.apply(this,n)||this;return r.type=e+\"Axis.\"+a,r}return n(i,t),i.prototype.mergeDefaultAndTheme=function(t,e){var n=Fc(this),i=n?Hc(t):{};S(t,e.getTheme().get(a+\"Axis\")),S(t,this.getDefaultOption()),t.type=FS(t),n&&Gc(t,i,n)},i.prototype.optionUpdated=function(){\"category\"===this.option.type&&(this.__ordinalMeta=H_.createByAxisModel(this))},i.prototype.getCategories=function(t){var e=this.option;if(\"category\"===e.type)return t?e.data:this.__ordinalMeta.categories},i.prototype.getOrdinalMeta=function(){return this.__ordinalMeta},i.type=e+\"Axis.\"+a,i.defaultOption=s,i}(i);t.registerComponentModel(l)})),t.registerSubTypeDefaulter(e+\"Axis\",FS)}function FS(t){return t.type||(t.data?\"category\":\"value\")}var GS=function(){function t(t){this.type=\"cartesian\",this._dimList=[],this._axes={},this.name=t||\"\"}return t.prototype.getAxis=function(t){return this._axes[t]},t.prototype.getAxes=function(){return O(this._dimList,(function(t){return this._axes[t]}),this)},t.prototype.getAxesByScale=function(t){return t=t.toLowerCase(),N(this.getAxes(),(function(e){return e.scale.type===t}))},t.prototype.addAxis=function(t){var e=t.dim;this._axes[e]=t,this._dimList.push(e)},t}(),HS=[\"x\",\"y\"];function WS(t){return\"interval\"===t.type||\"time\"===t.type}var US=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"cartesian2d\",e.dimensions=HS,e}return n(e,t),e.prototype.calcAffineTransform=function(){this._transform=this._invTransform=null;var t=this.getAxis(\"x\").scale,e=this.getAxis(\"y\").scale;if(WS(t)&&WS(e)){var n=t.getExtent(),i=e.getExtent(),r=this.dataToPoint([n[0],i[0]]),o=this.dataToPoint([n[1],i[1]]),a=n[1]-n[0],s=i[1]-i[0];if(a&&s){var l=(o[0]-r[0])/a,u=(o[1]-r[1])/s,h=r[0]-n[0]*l,c=r[1]-i[0]*u,p=this._transform=[l,0,0,u,h,c];this._invTransform=Zn([],p)}}},e.prototype.getBaseAxis=function(){return this.getAxesByScale(\"ordinal\")[0]||this.getAxesByScale(\"time\")[0]||this.getAxis(\"x\")},e.prototype.containPoint=function(t){var e=this.getAxis(\"x\"),n=this.getAxis(\"y\");return e.contain(e.toLocalCoord(t[0]))&&n.contain(n.toLocalCoord(t[1]))},e.prototype.containData=function(t){return this.getAxis(\"x\").containData(t[0])&&this.getAxis(\"y\").containData(t[1])},e.prototype.dataToPoint=function(t,e,n){n=n||[];var i=t[0],r=t[1];if(this._transform&&null!=i&&isFinite(i)&&null!=r&&isFinite(r))return Rt(n,t,this._transform);var o=this.getAxis(\"x\"),a=this.getAxis(\"y\");return n[0]=o.toGlobalCoord(o.dataToCoord(i,e)),n[1]=a.toGlobalCoord(a.dataToCoord(r,e)),n},e.prototype.clampData=function(t,e){var n=this.getAxis(\"x\").scale,i=this.getAxis(\"y\").scale,r=n.getExtent(),o=i.getExtent(),a=n.parse(t[0]),s=i.parse(t[1]);return(e=e||[])[0]=Math.min(Math.max(Math.min(r[0],r[1]),a),Math.max(r[0],r[1])),e[1]=Math.min(Math.max(Math.min(o[0],o[1]),s),Math.max(o[0],o[1])),e},e.prototype.pointToData=function(t,e){var n=[];if(this._invTransform)return Rt(n,t,this._invTransform);var i=this.getAxis(\"x\"),r=this.getAxis(\"y\");return n[0]=i.coordToData(i.toLocalCoord(t[0]),e),n[1]=r.coordToData(r.toLocalCoord(t[1]),e),n},e.prototype.getOtherAxis=function(t){return this.getAxis(\"x\"===t.dim?\"y\":\"x\")},e.prototype.getArea=function(){var t=this.getAxis(\"x\").getGlobalExtent(),e=this.getAxis(\"y\").getGlobalExtent(),n=Math.min(t[0],t[1]),i=Math.min(e[0],e[1]),r=Math.max(t[0],t[1])-n,o=Math.max(e[0],e[1])-i;return new gi(n,i,r,o)},e}(GS),XS=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.index=0,a.type=r||\"value\",a.position=o||\"bottom\",a}return n(e,t),e.prototype.isHorizontal=function(){var t=this.position;return\"top\"===t||\"bottom\"===t},e.prototype.getGlobalExtent=function(t){var e=this.getExtent();return e[0]=this.toGlobalCoord(e[0]),e[1]=this.toGlobalCoord(e[1]),t&&e[0]>e[1]&&e.reverse(),e},e.prototype.pointToData=function(t,e){return this.coordToData(this.toLocalCoord(t[\"x\"===this.dim?0:1]),e)},e.prototype.setCategorySortInfo=function(t){if(\"category\"!==this.type)return!1;this.model.option.categorySortInfo=t,this.scale.setSortInfo(t)},e}(hb);function YS(t,e,n){n=n||{};var i=t.coordinateSystem,r=e.axis,o={},a=r.getAxesOnZeroOf()[0],s=r.position,l=a?\"onZero\":s,u=r.dim,h=i.getRect(),c=[h.x,h.x+h.width,h.y,h.y+h.height],p={left:0,right:1,top:0,bottom:1,onZero:2},d=e.get(\"offset\")||0,f=\"x\"===u?[c[2]-d,c[3]+d]:[c[0]-d,c[1]+d];if(a){var g=a.toGlobalCoord(a.dataToCoord(0));f[p.onZero]=Math.max(Math.min(g,f[1]),f[0])}o.position=[\"y\"===u?f[p[l]]:c[0],\"x\"===u?f[p[l]]:c[3]],o.rotation=Math.PI/2*(\"x\"===u?0:1);o.labelDirection=o.tickDirection=o.nameDirection={top:-1,bottom:1,left:-1,right:1}[s],o.labelOffset=a?f[p[s]]-f[p.onZero]:0,e.get([\"axisTick\",\"inside\"])&&(o.tickDirection=-o.tickDirection),Q(n.labelInside,e.get([\"axisLabel\",\"inside\"]))&&(o.labelDirection=-o.labelDirection);var y=e.get([\"axisLabel\",\"rotate\"]);return o.labelRotate=\"top\"===l?-y:y,o.z2=1,o}function ZS(t){return\"cartesian2d\"===t.get(\"coordinateSystem\")}function jS(t){var e={xAxisModel:null,yAxisModel:null};return P(e,(function(n,i){var r=i.replace(/Model$/,\"\"),o=t.getReferringComponents(r,Nr).models[0];e[i]=o})),e}var qS=function(){function t(t,e,n){this.type=\"grid\",this._coordsMap={},this._coordsList=[],this._axesMap={},this._axesList=[],this.axisPointerEnabled=!0,this.dimensions=HS,this._initCartesian(t,e,n),this.model=t}return t.prototype.getRect=function(){return this._rect},t.prototype.update=function(t,e){var n=this._axesMap;this._updateScale(t,this.model),P(n.x,(function(t){Vx(t.scale,t.model)})),P(n.y,(function(t){Vx(t.scale,t.model)}));var i={};P(n.x,(function(t){$S(n,\"y\",t,i)})),P(n.y,(function(t){$S(n,\"x\",t,i)})),this.resize(this.model,e)},t.prototype.resize=function(t,e,n){var i=t.getBoxLayoutParams(),r=!n&&t.get(\"containLabel\"),o=Vc(i,{width:e.getWidth(),height:e.getHeight()});this._rect=o;var a=this._axesList;function s(){P(a,(function(t){var e=t.isHorizontal(),n=e?[0,o.width]:[0,o.height],i=t.inverse?1:0;t.setExtent(n[i],n[1-i]),function(t,e){var n=t.getExtent(),i=n[0]+n[1];t.toGlobalCoord=\"x\"===t.dim?function(t){return t+e}:function(t){return i-t+e},t.toLocalCoord=\"x\"===t.dim?function(t){return t-e}:function(t){return i-t+e}}(t,e?o.x:o.y)}))}s(),r&&(P(a,(function(t){if(!t.model.get([\"axisLabel\",\"inside\"])){var e=function(t){var e=t.model,n=t.scale;if(e.get([\"axisLabel\",\"show\"])&&!n.isBlank()){var i,r,o=n.getExtent();r=n instanceof $_?n.count():(i=n.getTicks()).length;var a,s=t.getLabelModel(),l=Fx(t),u=1;r>40&&(u=Math.ceil(r/40));for(var h=0;h<r;h+=u){var c=l(i?i[h]:{value:o[0]+h},h),p=Hx(s.getTextRect(c),s.get(\"rotate\")||0);a?a.union(p):a=p}return a}}(t);if(e){var n=t.isHorizontal()?\"height\":\"width\",i=t.model.get([\"axisLabel\",\"margin\"]);o[n]-=e[n]+i,\"top\"===t.position?o.y+=e.height+i:\"left\"===t.position&&(o.x+=e.width+i)}}})),s()),P(this._coordsList,(function(t){t.calcAffineTransform()}))},t.prototype.getAxis=function(t,e){var n=this._axesMap[t];if(null!=n)return n[e||0]},t.prototype.getAxes=function(){return this._axesList.slice()},t.prototype.getCartesian=function(t,e){if(null!=t&&null!=e){var n=\"x\"+t+\"y\"+e;return this._coordsMap[n]}X(t)&&(e=t.yAxisIndex,t=t.xAxisIndex);for(var i=0,r=this._coordsList;i<r.length;i++)if(r[i].getAxis(\"x\").index===t||r[i].getAxis(\"y\").index===e)return r[i]},t.prototype.getCartesians=function(){return this._coordsList.slice()},t.prototype.convertToPixel=function(t,e,n){var i=this._findConvertTarget(e);return i.cartesian?i.cartesian.dataToPoint(n):i.axis?i.axis.toGlobalCoord(i.axis.dataToCoord(n)):null},t.prototype.convertFromPixel=function(t,e,n){var i=this._findConvertTarget(e);return i.cartesian?i.cartesian.pointToData(n):i.axis?i.axis.coordToData(i.axis.toLocalCoord(n)):null},t.prototype._findConvertTarget=function(t){var e,n,i=t.seriesModel,r=t.xAxisModel||i&&i.getReferringComponents(\"xAxis\",Nr).models[0],o=t.yAxisModel||i&&i.getReferringComponents(\"yAxis\",Nr).models[0],a=t.gridModel,s=this._coordsList;if(i)D(s,e=i.coordinateSystem)<0&&(e=null);else if(r&&o)e=this.getCartesian(r.componentIndex,o.componentIndex);else if(r)n=this.getAxis(\"x\",r.componentIndex);else if(o)n=this.getAxis(\"y\",o.componentIndex);else if(a){a.coordinateSystem===this&&(e=this._coordsList[0])}return{cartesian:e,axis:n}},t.prototype.containPoint=function(t){var e=this._coordsList[0];if(e)return e.containPoint(t)},t.prototype._initCartesian=function(t,e,n){var i=this,r=this,o={left:!1,right:!1,top:!1,bottom:!1},a={x:{},y:{}},s={x:0,y:0};if(e.eachComponent(\"xAxis\",l(\"x\"),this),e.eachComponent(\"yAxis\",l(\"y\"),this),!s.x||!s.y)return this._axesMap={},void(this._axesList=[]);function l(e){return function(n,i){if(KS(n,t)){var l=n.get(\"position\");\"x\"===e?\"top\"!==l&&\"bottom\"!==l&&(l=o.bottom?\"top\":\"bottom\"):\"left\"!==l&&\"right\"!==l&&(l=o.left?\"right\":\"left\"),o[l]=!0;var u=new XS(e,Bx(n),[0,0],n.get(\"type\"),l),h=\"category\"===u.type;u.onBand=h&&n.get(\"boundaryGap\"),u.inverse=n.get(\"inverse\"),n.axis=u,u.model=n,u.grid=r,u.index=i,r._axesList.push(u),a[e][i]=u,s[e]++}}}this._axesMap=a,P(a.x,(function(e,n){P(a.y,(function(r,o){var a=\"x\"+n+\"y\"+o,s=new US(a);s.master=i,s.model=t,i._coordsMap[a]=s,i._coordsList.push(s),s.addAxis(e),s.addAxis(r)}))}))},t.prototype._updateScale=function(t,e){function n(t,e){P(Xx(t,e.dim),(function(n){e.scale.unionExtentFromData(t,n)}))}P(this._axesList,(function(t){if(t.scale.setExtent(1/0,-1/0),\"category\"===t.type){var e=t.model.get(\"categorySortInfo\");t.scale.setSortInfo(e)}})),t.eachSeries((function(t){if(ZS(t)){var i=jS(t),r=i.xAxisModel,o=i.yAxisModel;if(!KS(r,e)||!KS(o,e))return;var a=this.getCartesian(r.componentIndex,o.componentIndex),s=t.getData(),l=a.getAxis(\"x\"),u=a.getAxis(\"y\");\"list\"===s.type&&(n(s,l),n(s,u))}}),this)},t.prototype.getTooltipAxes=function(t){var e=[],n=[];return P(this.getCartesians(),(function(i){var r=null!=t&&\"auto\"!==t?i.getAxis(t):i.getBaseAxis(),o=i.getOtherAxis(r);D(e,r)<0&&e.push(r),D(n,o)<0&&n.push(o)})),{baseAxes:e,otherAxes:n}},t.create=function(e,n){var i=[];return e.eachComponent(\"grid\",(function(r,o){var a=new t(r,e,n);a.name=\"grid_\"+o,a.resize(r,n,!0),r.coordinateSystem=a,i.push(a)})),e.eachSeries((function(t){if(ZS(t)){var e=jS(t),n=e.xAxisModel,i=e.yAxisModel,r=n.getCoordSysModel();0;var o=r.coordinateSystem;t.coordinateSystem=o.getCartesian(n.componentIndex,i.componentIndex)}})),i},t.dimensions=HS,t}();function KS(t,e){return t.getCoordSysModel()===e}function $S(t,e,n,i){n.getAxesOnZeroOf=function(){return r?[r]:[]};var r,o=t[e],a=n.model,s=a.get([\"axisLine\",\"onZero\"]),l=a.get([\"axisLine\",\"onZeroAxisIndex\"]);if(s){if(null!=l)JS(o[l])&&(r=o[l]);else for(var u in o)if(o.hasOwnProperty(u)&&JS(o[u])&&!i[h(o[u])]){r=o[u];break}r&&(i[h(r)]=!0)}function h(t){return t.dim+\"_\"+t.index}}function JS(t){return t&&\"category\"!==t.type&&\"time\"!==t.type&&function(t){var e=t.scale.getExtent(),n=e[0],i=e[1];return!(n>0&&i>0||n<0&&i<0)}(t)}var QS=Math.PI,tM=function(){function t(t,e){this.group=new Ei,this.opt=e,this.axisModel=t,T(e,{labelOffset:0,nameDirection:1,tickDirection:1,labelDirection:1,silent:!0,handleAutoShown:function(){return!0}});var n=new Ei({x:e.position[0],y:e.position[1],rotation:e.rotation});n.updateTransform(),this._transformGroup=n}return t.prototype.hasBuilder=function(t){return!!eM[t]},t.prototype.add=function(t){eM[t](this.opt,this.axisModel,this.group,this._transformGroup)},t.prototype.getGroup=function(){return this.group},t.innerTextLayout=function(t,e,n){var i,r,o=nr(e-t);return ir(o)?(r=n>0?\"top\":\"bottom\",i=\"center\"):ir(o-QS)?(r=n>0?\"bottom\":\"top\",i=\"center\"):(r=\"middle\",i=o>0&&o<QS?n>0?\"right\":\"left\":n>0?\"left\":\"right\"),{rotation:o,textAlign:i,textVerticalAlign:r}},t.makeAxisEventDataBase=function(t){var e={componentType:t.mainType,componentIndex:t.componentIndex};return e[t.mainType+\"Index\"]=t.componentIndex,e},t.isLabelSilent=function(t){var e=t.get(\"tooltip\");return t.get(\"silent\")||!(t.get(\"triggerEvent\")||e&&e.show)},t}(),eM={axisLine:function(t,e,n,i){var r=e.get([\"axisLine\",\"show\"]);if(\"auto\"===r&&t.handleAutoShown&&(r=t.handleAutoShown(\"axisLine\")),r){var o=e.axis.getExtent(),a=i.transform,s=[o[0],0],l=[o[1],0];a&&(Rt(s,s,a),Rt(l,l,a));var u=I({lineCap:\"round\"},e.getModel([\"axisLine\",\"lineStyle\"]).getLineStyle()),h=new uu({subPixelOptimize:!0,shape:{x1:s[0],y1:s[1],x2:l[0],y2:l[1]},style:u,strokeContainThreshold:t.strokeContainThreshold||5,silent:!0,z2:1});h.anid=\"line\",n.add(h);var c=e.get([\"axisLine\",\"symbol\"]),p=e.get([\"axisLine\",\"symbolSize\"]),d=e.get([\"axisLine\",\"symbolOffset\"])||0;if(\"number\"==typeof d&&(d=[d,d]),null!=c){\"string\"==typeof c&&(c=[c,c]),\"string\"!=typeof p&&\"number\"!=typeof p||(p=[p,p]);var f=p[0],g=p[1];P([{rotate:t.rotation+Math.PI/2,offset:d[0],r:0},{rotate:t.rotation-Math.PI/2,offset:d[1],r:Math.sqrt((s[0]-l[0])*(s[0]-l[0])+(s[1]-l[1])*(s[1]-l[1]))}],(function(e,i){if(\"none\"!==c[i]&&null!=c[i]){var r=fy(c[i],-f/2,-g/2,f,g,u.stroke,!0),o=e.r+e.offset;r.attr({rotation:e.rotate,x:s[0]+o*Math.cos(t.rotation),y:s[1]-o*Math.sin(t.rotation),silent:!0,z2:11}),n.add(r)}}))}}},axisTickLabel:function(t,e,n,i){var r=function(t,e,n,i){var r=n.axis,o=n.getModel(\"axisTick\"),a=o.get(\"show\");\"auto\"===a&&i.handleAutoShown&&(a=i.handleAutoShown(\"axisTick\"));if(!a||r.scale.isBlank())return;for(var s=o.getModel(\"lineStyle\"),l=i.tickDirection*o.get(\"length\"),u=oM(r.getTicksCoords(),e.transform,l,T(s.getLineStyle(),{stroke:n.get([\"axisLine\",\"lineStyle\",\"color\"])}),\"ticks\"),h=0;h<u.length;h++)t.add(u[h]);return u}(n,i,e,t),o=function(t,e,n,i){var r=n.axis;if(!Q(i.axisLabelShow,n.get([\"axisLabel\",\"show\"]))||r.scale.isBlank())return;var o=n.getModel(\"axisLabel\"),a=o.get(\"margin\"),s=r.getViewLabels(),l=(Q(i.labelRotate,o.get(\"rotate\"))||0)*QS/180,u=tM.innerTextLayout(i.rotation,l,i.labelDirection),h=n.getCategories&&n.getCategories(!0),c=[],p=tM.isLabelSilent(n),d=n.get(\"triggerEvent\");return P(s,(function(s,l){var f=\"ordinal\"===r.scale.type?r.scale.getRawOrdinalNumber(s.tickValue):s.tickValue,g=s.formattedLabel,y=s.rawLabel,v=o;if(h&&h[f]){var m=h[f];X(m)&&m.textStyle&&(v=new Oh(m.textStyle,o,n.ecModel))}var _=v.getTextColor()||n.get([\"axisLine\",\"lineStyle\",\"color\"]),x=r.dataToCoord(f),b=new cs({x:x,y:i.labelOffset+i.labelDirection*a,rotation:u.rotation,silent:p,z2:10,style:ph(v,{text:g,align:v.getShallow(\"align\",!0)||u.textAlign,verticalAlign:v.getShallow(\"verticalAlign\",!0)||v.getShallow(\"baseline\",!0)||u.textVerticalAlign,fill:\"function\"==typeof _?_(\"category\"===r.type?y:\"value\"===r.type?f+\"\":f,l):_})});if(b.anid=\"label_\"+f,d){var w=tM.makeAxisEventDataBase(n);w.targetType=\"axisLabel\",w.value=y,_s(b).eventData=w}e.add(b),b.updateTransform(),c.push(b),t.add(b),b.decomposeTransform()})),c}(n,i,e,t);!function(t,e,n){if(Ux(t.axis))return;var i=t.get([\"axisLabel\",\"showMinLabel\"]),r=t.get([\"axisLabel\",\"showMaxLabel\"]);n=n||[];var o=(e=e||[])[0],a=e[1],s=e[e.length-1],l=e[e.length-2],u=n[0],h=n[1],c=n[n.length-1],p=n[n.length-2];!1===i?(nM(o),nM(u)):iM(o,a)&&(i?(nM(a),nM(h)):(nM(o),nM(u)));!1===r?(nM(s),nM(c)):iM(l,s)&&(r?(nM(l),nM(p)):(nM(s),nM(c)))}(e,o,r),function(t,e,n,i){var r=n.axis,o=n.getModel(\"minorTick\");if(!o.get(\"show\")||r.scale.isBlank())return;var a=r.getMinorTicksCoords();if(!a.length)return;for(var s=o.getModel(\"lineStyle\"),l=i*o.get(\"length\"),u=T(s.getLineStyle(),T(n.getModel(\"axisTick\").getLineStyle(),{stroke:n.get([\"axisLine\",\"lineStyle\",\"color\"])})),h=0;h<a.length;h++)for(var c=oM(a[h],e.transform,l,u,\"minorticks_\"+h),p=0;p<c.length;p++)t.add(c[p])}(n,i,e,t.tickDirection)},axisName:function(t,e,n,i){var r=Q(t.axisName,e.get(\"name\"));if(r){var o,a,s=e.get(\"nameLocation\"),l=t.nameDirection,u=e.getModel(\"nameTextStyle\"),h=e.get(\"nameGap\")||0,c=e.axis.getExtent(),p=c[0]>c[1]?-1:1,d=[\"start\"===s?c[0]-p*h:\"end\"===s?c[1]+p*h:(c[0]+c[1])/2,rM(s)?t.labelOffset+l*h:0],f=e.get(\"nameRotate\");null!=f&&(f=f*QS/180),rM(s)?o=tM.innerTextLayout(t.rotation,null!=f?f:t.rotation,l):(o=function(t,e,n,i){var r,o,a=nr(n-t),s=i[0]>i[1],l=\"start\"===e&&!s||\"start\"!==e&&s;ir(a-QS/2)?(o=l?\"bottom\":\"top\",r=\"center\"):ir(a-1.5*QS)?(o=l?\"top\":\"bottom\",r=\"center\"):(o=\"middle\",r=a<1.5*QS&&a>QS/2?l?\"left\":\"right\":l?\"right\":\"left\");return{rotation:a,textAlign:r,textVerticalAlign:o}}(t.rotation,s,f||0,c),null!=(a=t.axisNameAvailableWidth)&&(a=Math.abs(a/Math.sin(o.rotation)),!isFinite(a)&&(a=null)));var g=u.getFont(),y=e.get(\"nameTruncate\",!0)||{},v=y.ellipsis,m=Q(t.nameTruncateMaxWidth,y.maxWidth,a),_=new cs({x:d[0],y:d[1],rotation:o.rotation,silent:tM.isLabelSilent(e),style:ph(u,{text:r,font:g,overflow:\"truncate\",width:m,ellipsis:v,fill:u.getTextColor()||e.get([\"axisLine\",\"lineStyle\",\"color\"]),align:u.get(\"align\")||o.textAlign,verticalAlign:u.get(\"verticalAlign\")||o.textVerticalAlign}),z2:1});if(oh({el:_,componentModel:e,itemName:r}),_.__fullText=r,_.anid=\"name\",e.get(\"triggerEvent\")){var x=tM.makeAxisEventDataBase(e);x.targetType=\"axisName\",x.name=r,_s(_).eventData=x}i.add(_),_.updateTransform(),n.add(_),_.decomposeTransform()}}};function nM(t){t&&(t.ignore=!0)}function iM(t,e){var n=t&&t.getBoundingRect().clone(),i=e&&e.getBoundingRect().clone();if(n&&i){var r=Gn([]);return Xn(r,r,-t.rotation),n.applyTransform(Wn([],r,t.getLocalTransform())),i.applyTransform(Wn([],r,e.getLocalTransform())),n.intersect(i)}}function rM(t){return\"middle\"===t||\"center\"===t}function oM(t,e,n,i,r){for(var o=[],a=[],s=[],l=0;l<t.length;l++){var u=t[l].coord;a[0]=u,a[1]=0,s[0]=u,s[1]=n,e&&(Rt(a,a,e),Rt(s,s,e));var h=new uu({subPixelOptimize:!0,shape:{x1:a[0],y1:a[1],x2:s[0],y2:s[1]},style:i,z2:2,autoBatch:!0,silent:!0});h.anid=r+\"_\"+t[l].tickValue,o.push(h)}return o}function aM(t,e){var n={axesInfo:{},seriesInvolved:!1,coordSysAxesInfo:{},coordSysMap:{}};return function(t,e,n){var i=e.getComponent(\"tooltip\"),r=e.getComponent(\"axisPointer\"),o=r.get(\"link\",!0)||[],a=[];P(n.getCoordinateSystems(),(function(n){if(n.axisPointerEnabled){var s=cM(n.model),l=t.coordSysAxesInfo[s]={};t.coordSysMap[s]=n;var u=n.model.getModel(\"tooltip\",i);if(P(n.getAxes(),B(d,!1,null)),n.getTooltipAxes&&i&&u.get(\"show\")){var h=\"axis\"===u.get(\"trigger\"),c=\"cross\"===u.get([\"axisPointer\",\"type\"]),p=n.getTooltipAxes(u.get([\"axisPointer\",\"axis\"]));(h||c)&&P(p.baseAxes,B(d,!c||\"cross\",h)),c&&P(p.otherAxes,B(d,\"cross\",!1))}}function d(i,s,h){var c=h.model.getModel(\"axisPointer\",r),p=c.get(\"show\");if(p&&(\"auto\"!==p||i||hM(c))){null==s&&(s=c.get(\"triggerTooltip\"));var d=(c=i?function(t,e,n,i,r,o){var a=e.getModel(\"axisPointer\"),s={};P([\"type\",\"snap\",\"lineStyle\",\"shadowStyle\",\"label\",\"animation\",\"animationDurationUpdate\",\"animationEasingUpdate\",\"z\"],(function(t){s[t]=w(a.get(t))})),s.snap=\"category\"!==t.type&&!!o,\"cross\"===a.get(\"type\")&&(s.type=\"line\");var l=s.label||(s.label={});if(null==l.show&&(l.show=!1),\"cross\"===r){var u=a.get([\"label\",\"show\"]);if(l.show=null==u||u,!o){var h=s.lineStyle=a.get(\"crossStyle\");h&&T(l,h.textStyle)}}return t.model.getModel(\"axisPointer\",new Oh(s,n,i))}(h,u,r,e,i,s):c).get(\"snap\"),f=cM(h.model),g=s||d||\"category\"===h.type,y=t.axesInfo[f]={key:f,axis:h,coordSys:n,axisPointerModel:c,triggerTooltip:s,involveSeries:g,snap:d,useHandle:hM(c),seriesModels:[],linkGroup:null};l[f]=y,t.seriesInvolved=t.seriesInvolved||g;var v=function(t,e){for(var n=e.model,i=e.dim,r=0;r<t.length;r++){var o=t[r]||{};if(sM(o[i+\"AxisId\"],n.id)||sM(o[i+\"AxisIndex\"],n.componentIndex)||sM(o[i+\"AxisName\"],n.name))return r}}(o,h);if(null!=v){var m=a[v]||(a[v]={axesInfo:{}});m.axesInfo[f]=y,m.mapper=o[v].mapper,y.linkGroup=m}}}}))}(n,t,e),n.seriesInvolved&&function(t,e){e.eachSeries((function(e){var n=e.coordinateSystem,i=e.get([\"tooltip\",\"trigger\"],!0),r=e.get([\"tooltip\",\"show\"],!0);n&&\"none\"!==i&&!1!==i&&\"item\"!==i&&!1!==r&&!1!==e.get([\"axisPointer\",\"show\"],!0)&&P(t.coordSysAxesInfo[cM(n.model)],(function(t){var i=t.axis;n.getAxis(i.dim)===i&&(t.seriesModels.push(e),null==t.seriesDataCount&&(t.seriesDataCount=0),t.seriesDataCount+=e.getData().count())}))}))}(n,t),n}function sM(t,e){return\"all\"===t||F(t)&&D(t,e)>=0||t===e}function lM(t){var e=uM(t);if(e){var n=e.axisPointerModel,i=e.axis.scale,r=n.option,o=n.get(\"status\"),a=n.get(\"value\");null!=a&&(a=i.parse(a));var s=hM(n);null==o&&(r.status=s?\"show\":\"hide\");var l=i.getExtent().slice();l[0]>l[1]&&l.reverse(),(null==a||a>l[1])&&(a=l[1]),a<l[0]&&(a=l[0]),r.value=a,s&&(r.status=e.axis.scale.isBlank()?\"hide\":\"show\")}}function uM(t){var e=(t.ecModel.getComponent(\"axisPointer\")||{}).coordSysAxesInfo;return e&&e.axesInfo[cM(t)]}function hM(t){return!!t.get([\"handle\",\"show\"])}function cM(t){return t.type+\"||\"+t.id}var pM={},dM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(e,n,i,r){this.axisPointerClass&&lM(e),t.prototype.render.apply(this,arguments),this._doUpdateAxisPointerClass(e,i,!0)},e.prototype.updateAxisPointer=function(t,e,n,i){this._doUpdateAxisPointerClass(t,n,!1)},e.prototype.remove=function(t,e){var n=this._axisPointer;n&&n.remove(e)},e.prototype.dispose=function(e,n){this._disposeAxisPointer(n),t.prototype.dispose.apply(this,arguments)},e.prototype._doUpdateAxisPointerClass=function(t,n,i){var r=e.getAxisPointerClass(this.axisPointerClass);if(r){var o=function(t){var e=uM(t);return e&&e.axisPointerModel}(t);o?(this._axisPointer||(this._axisPointer=new r)).render(t,o,n,i):this._disposeAxisPointer(n)}},e.prototype._disposeAxisPointer=function(t){this._axisPointer&&this._axisPointer.dispose(t),this._axisPointer=null},e.registerAxisPointerClass=function(t,e){pM[t]=e},e.getAxisPointerClass=function(t){return t&&pM[t]},e.type=\"axis\",e}(wf),fM=kr();function gM(t,e,n,i){var r=n.axis;if(!r.scale.isBlank()){var o=n.getModel(\"splitArea\"),a=o.getModel(\"areaStyle\"),s=a.get(\"color\"),l=i.coordinateSystem.getRect(),u=r.getTicksCoords({tickModel:o,clamp:!0});if(u.length){var h=s.length,c=fM(t).splitAreaColors,p=ht(),d=0;if(c)for(var f=0;f<u.length;f++){var g=c.get(u[f].tickValue);if(null!=g){d=(g+(h-1)*f)%h;break}}var y=r.toGlobalCoord(u[0].coord),v=a.getAreaStyle();s=F(s)?s:[s];for(f=1;f<u.length;f++){var m=r.toGlobalCoord(u[f].coord),_=void 0,x=void 0,b=void 0,w=void 0;r.isHorizontal()?(_=y,x=l.y,b=m-_,w=l.height,y=_+b):(_=l.x,x=y,b=l.width,y=x+(w=m-x));var S=u[f-1].tickValue;null!=S&&p.set(S,d),e.add(new ls({anid:null!=S?\"area_\"+S:null,shape:{x:_,y:x,width:b,height:w},style:T({fill:s[d]},v),autoBatch:!0,silent:!0})),d=(d+1)%h}fM(t).splitAreaColors=p}}}function yM(t){fM(t).splitAreaColors=null}var vM=[\"axisLine\",\"axisTickLabel\",\"axisName\"],mM=[\"splitArea\",\"splitLine\",\"minorSplitLine\"],_M=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass=\"CartesianAxisPointer\",n}return n(e,t),e.prototype.render=function(e,n,i,r){this.group.removeAll();var o=this._axisGroup;if(this._axisGroup=new Ei,this.group.add(this._axisGroup),e.get(\"show\")){var a=e.getCoordSysModel(),s=YS(a,e),l=new tM(e,I({handleAutoShown:function(t){for(var n=a.coordinateSystem.getCartesians(),i=0;i<n.length;i++){var r=n[i].getOtherAxis(e.axis).type;if(\"value\"===r||\"log\"===r)return!0}return!1}},s));P(vM,l.add,l),this._axisGroup.add(l.getGroup()),P(mM,(function(t){e.get([t,\"show\"])&&xM[t](this,this._axisGroup,e,a)}),this),Ju(o,this._axisGroup,e),t.prototype.render.call(this,e,n,i,r)}},e.prototype.remove=function(){yM(this)},e.type=\"cartesianAxis\",e}(dM),xM={splitLine:function(t,e,n,i){var r=n.axis;if(!r.scale.isBlank()){var o=n.getModel(\"splitLine\"),a=o.getModel(\"lineStyle\"),s=a.get(\"color\");s=F(s)?s:[s];for(var l=i.coordinateSystem.getRect(),u=r.isHorizontal(),h=0,c=r.getTicksCoords({tickModel:o}),p=[],d=[],f=a.getLineStyle(),g=0;g<c.length;g++){var y=r.toGlobalCoord(c[g].coord);u?(p[0]=y,p[1]=l.y,d[0]=y,d[1]=l.y+l.height):(p[0]=l.x,p[1]=y,d[0]=l.x+l.width,d[1]=y);var v=h++%s.length,m=c[g].tickValue;e.add(new uu({anid:null!=m?\"line_\"+c[g].tickValue:null,subPixelOptimize:!0,autoBatch:!0,shape:{x1:p[0],y1:p[1],x2:d[0],y2:d[1]},style:T({stroke:s[v]},f),silent:!0}))}}},minorSplitLine:function(t,e,n,i){var r=n.axis,o=n.getModel(\"minorSplitLine\").getModel(\"lineStyle\"),a=i.coordinateSystem.getRect(),s=r.isHorizontal(),l=r.getMinorTicksCoords();if(l.length)for(var u=[],h=[],c=o.getLineStyle(),p=0;p<l.length;p++)for(var d=0;d<l[p].length;d++){var f=r.toGlobalCoord(l[p][d].coord);s?(u[0]=f,u[1]=a.y,h[0]=f,h[1]=a.y+a.height):(u[0]=a.x,u[1]=f,h[0]=a.x+a.width,h[1]=f),e.add(new uu({anid:\"minor_line_\"+l[p][d].tickValue,subPixelOptimize:!0,autoBatch:!0,shape:{x1:u[0],y1:u[1],x2:h[0],y2:h[1]},style:c,silent:!0}))}},splitArea:function(t,e,n,i){gM(t,e,n,i)}},bM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"xAxis\",e}(_M),wM=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=bM.type,e}return n(e,t),e.type=\"yAxis\",e}(_M),SM=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"grid\",e}return n(e,t),e.prototype.render=function(t,e){this.group.removeAll(),t.get(\"show\")&&this.group.add(new ls({shape:t.coordinateSystem.getRect(),style:T({fill:t.get(\"backgroundColor\")},t.getItemStyle()),silent:!0,z2:-1}))},e.type=\"grid\",e}(wf),MM={offset:0};function IM(t){t.registerComponentView(SM),t.registerComponentModel(PS),t.registerCoordinateSystem(\"cartesian2d\",qS),BS(t,\"x\",OS,MM),BS(t,\"y\",OS,MM),t.registerComponentView(bM),t.registerComponentView(wM),t.registerPreprocessor((function(t){t.xAxis&&t.yAxis&&!t.grid&&(t.grid={})}))}function TM(t){t.eachSeriesByType(\"radar\",(function(t){var e=t.getData(),n=[],i=t.coordinateSystem;if(i){var r=i.getIndicatorAxes();P(r,(function(t,o){e.each(e.mapDimension(r[o].dim),(function(t,e){n[e]=n[e]||[];var r=i.dataToPoint(t,o);n[e][o]=CM(r)?r:DM(i)}))})),e.each((function(t){var r=z(n[t],(function(t){return CM(t)}))||DM(i);n[t].push(r.slice()),e.setItemLayout(t,n[t])}))}}))}function CM(t){return!isNaN(t[0])&&!isNaN(t[1])}function DM(t){return[t.cx,t.cy]}function AM(t){var e=t.polar;if(e){F(e)||(e=[e]);var n=[];P(e,(function(e,i){e.indicator?(e.type&&!e.shape&&(e.shape=e.type),t.radar=t.radar||[],F(t.radar)||(t.radar=[t.radar]),t.radar.push(e)):n.push(e)})),t.polar=n}P(t.series,(function(t){t&&\"radar\"===t.type&&t.polarIndex&&(t.radarIndex=t.polarIndex)}))}var LM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.coordinateSystem,r=this.group,o=t.getData(),a=this._data;function s(t,e){var n=t.getItemVisual(e,\"symbol\")||\"circle\";if(\"none\"!==n){var i=function(t){return F(t)||(t=[+t,+t]),t}(t.getItemVisual(e,\"symbolSize\")),r=fy(n,-1,-1,2,2),o=t.getItemVisual(e,\"symbolRotate\")||0;return r.attr({style:{strokeNoScale:!0},z2:100,scaleX:i[0]/2,scaleY:i[1]/2,rotation:o*Math.PI/180||0}),r}}function l(e,n,i,r,o,a){i.removeAll();for(var l=0;l<n.length-1;l++){var u=s(r,o);u&&(u.__dimIdx=l,e[l]?(u.setPosition(e[l]),ah[a?\"initProps\":\"updateProps\"](u,{x:n[l][0],y:n[l][1]},t,o)):u.setPosition(n[l]),i.add(u))}}function u(t){return O(t,(function(t){return[i.cx,i.cy]}))}o.diff(a).add((function(e){var n=o.getItemLayout(e);if(n){var i=new ru,r=new au,a={shape:{points:n}};i.shape.points=u(n),r.shape.points=u(n),Wu(i,a,t,e),Wu(r,a,t,e);var s=new Ei,h=new Ei;s.add(r),s.add(i),s.add(h),l(r.shape.points,n,h,o,e,!0),o.setItemGraphicEl(e,s)}})).update((function(e,n){var i=a.getItemGraphicEl(n),r=i.childAt(0),s=i.childAt(1),u=i.childAt(2),h={shape:{points:o.getItemLayout(e)}};h.shape.points&&(l(r.shape.points,h.shape.points,u,o,e,!1),Hu(r,h,t),Hu(s,h,t),o.setItemGraphicEl(e,i))})).remove((function(t){r.remove(a.getItemGraphicEl(t))})).execute(),o.eachItemGraphicEl((function(t,e){var n=o.getItemModel(e),i=t.childAt(0),a=t.childAt(1),s=t.childAt(2),l=o.getItemVisual(e,\"style\"),u=l.fill;r.add(t),i.useStyle(T(n.getModel(\"lineStyle\").getLineStyle(),{fill:\"none\",stroke:u})),cl(i,n,\"lineStyle\"),cl(a,n,\"areaStyle\");var h=n.getModel(\"areaStyle\"),c=h.isEmpty()&&h.parentModel.isEmpty();a.ignore=c,P([\"emphasis\",\"select\",\"blur\"],(function(t){var e=n.getModel([t,\"areaStyle\"]),i=e.isEmpty()&&e.parentModel.isEmpty();a.ensureState(t).ignore=i&&c})),a.useStyle(T(h.getAreaStyle(),{fill:u,opacity:.7,decal:l.decal}));var p=n.getModel(\"emphasis\"),d=p.getModel(\"itemStyle\").getItemStyle();s.eachChild((function(t){if(t instanceof es){var i=t.style;t.useStyle(I({image:i.image,x:i.x,y:i.y,width:i.width,height:i.height},l))}else t.useStyle(l),t.setColor(u);t.ensureState(\"emphasis\").style=w(d);var r=o.get(o.dimensions[t.__dimIdx],e);(null==r||isNaN(r))&&(r=\"\"),hh(t,ch(n),{labelFetcher:o.hostModel,labelDataIndex:e,labelDimIndex:t.__dimIdx,defaultText:r,inheritColor:u,defaultOpacity:l.opacity})})),sl(t,p.get(\"focus\"),p.get(\"blurScope\"))})),this._data=o},e.prototype.remove=function(){this.group.removeAll(),this._data=null},e.type=\"radar\",e}(Tf),kM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.useColorPaletteOnData=!0,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new IS(V(this.getData,this),V(this.getRawData,this))},e.prototype.getInitialData=function(t,e){return MS(this,{generateCoord:\"indicator_\",generateCoordCount:1/0})},e.prototype.formatTooltip=function(t,e,n){var i=this.getData(),r=this.coordinateSystem.getIndicatorAxes(),o=this.getData().getName(t),a=\"\"===o?this.name:o,s=lf(this,t);return tf(\"section\",{header:a,sortBlocks:!0,blocks:O(r,(function(e){var n=i.get(i.mapDimension(e.dim),t);return tf(\"nameValue\",{markerType:\"subItem\",markerColor:s,name:e.name,value:n,sortParam:n})}))})},e.prototype.getTooltipPosition=function(t){if(null!=t)for(var e=this.getData(),n=this.coordinateSystem,i=e.getValues(O(n.dimensions,(function(t){return e.mapDimension(t)})),t),r=0,o=i.length;r<o;r++)if(!isNaN(i[r])){var a=n.getIndicatorAxes();return n.coordToPoint(a[r].dataToCoord(i[r]),r)}},e.type=\"series.radar\",e.dependencies=[\"radar\"],e.defaultOption={zlevel:0,z:2,coordinateSystem:\"radar\",legendHoverLink:!0,radarIndex:0,lineStyle:{width:2,type:\"solid\"},label:{position:\"top\"},symbolSize:8},e}(ff),PM=ES.value;function OM(t,e){return T({show:e},t)}var RM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){var t=this.get(\"boundaryGap\"),e=this.get(\"splitNumber\"),n=this.get(\"scale\"),i=this.get(\"axisLine\"),r=this.get(\"axisTick\"),o=this.get(\"axisLabel\"),a=this.get(\"axisName\"),s=this.get([\"axisName\",\"show\"]),l=this.get([\"axisName\",\"formatter\"]),u=this.get(\"axisNameGap\"),h=this.get(\"triggerEvent\"),c=O(this.get(\"indicator\")||[],(function(c){null!=c.max&&c.max>0&&!c.min?c.min=0:null!=c.min&&c.min<0&&!c.max&&(c.max=0);var p=a;null!=c.color&&(p=T({color:c.color},a));var d=S(w(c),{boundaryGap:t,splitNumber:e,scale:n,axisLine:i,axisTick:r,axisLabel:o,name:c.text,nameLocation:\"end\",nameGap:u,nameTextStyle:p,triggerEvent:h},!1);if(s||(d.name=\"\"),\"string\"==typeof l){var f=d.name;d.name=l.replace(\"{value}\",null!=f?f:\"\")}else\"function\"==typeof l&&(d.name=l(d.name,d));var g=new Oh(d,null,this.ecModel);return L(g,Yx.prototype),g.mainType=\"radar\",g.componentIndex=this.componentIndex,g}),this);this._indicatorModels=c},e.prototype.getIndicatorModels=function(){return this._indicatorModels},e.type=\"radar\",e.defaultOption={zlevel:0,z:0,center:[\"50%\",\"50%\"],radius:\"75%\",startAngle:90,axisName:{show:!0},boundaryGap:[0,0],splitNumber:5,axisNameGap:15,scale:!1,shape:\"polygon\",axisLine:S({lineStyle:{color:\"#bbb\"}},PM.axisLine),axisLabel:OM(PM.axisLabel,!1),axisTick:OM(PM.axisTick,!1),splitLine:OM(PM.splitLine,!0),splitArea:OM(PM.splitArea,!0),indicator:[]},e}(Xc),NM=[\"axisLine\",\"axisTickLabel\",\"axisName\"],zM=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeAll(),this._buildAxes(t),this._buildSplitLineAndArea(t)},e.prototype._buildAxes=function(t){var e=t.coordinateSystem;P(O(e.getIndicatorAxes(),(function(t){return new tM(t.model,{position:[e.cx,e.cy],rotation:t.angle,labelDirection:-1,tickDirection:-1,nameDirection:1})})),(function(t){P(NM,t.add,t),this.group.add(t.getGroup())}),this)},e.prototype._buildSplitLineAndArea=function(t){var e=t.coordinateSystem,n=e.getIndicatorAxes();if(n.length){var i=t.get(\"shape\"),r=t.getModel(\"splitLine\"),o=t.getModel(\"splitArea\"),a=r.getModel(\"lineStyle\"),s=o.getModel(\"areaStyle\"),l=r.get(\"show\"),u=o.get(\"show\"),h=a.get(\"color\"),c=s.get(\"color\"),p=F(h)?h:[h],d=F(c)?c:[c],f=[],g=[];if(\"circle\"===i)for(var y=n[0].getTicksCoords(),v=e.cx,m=e.cy,_=0;_<y.length;_++){if(l)f[D(f,p,_)].push(new Nl({shape:{cx:v,cy:m,r:y[_].coord}}));if(u&&_<y.length-1)g[D(g,d,_)].push(new tu({shape:{cx:v,cy:m,r0:y[_].coord,r:y[_+1].coord}}))}else{var x,b=O(n,(function(t,n){var i=t.getTicksCoords();return x=null==x?i.length-1:Math.min(i.length-1,x),O(i,(function(t){return e.coordToPoint(t.coord,n)}))})),w=[];for(_=0;_<=x;_++){for(var S=[],M=0;M<n.length;M++)S.push(b[M][_]);if(S[0]&&S.push(S[0].slice()),l)f[D(f,p,_)].push(new au({shape:{points:S}}));if(u&&w)g[D(g,d,_-1)].push(new ru({shape:{points:S.concat(w)}}));w=S.slice().reverse()}}var I=a.getLineStyle(),C=s.getAreaStyle();P(g,(function(t,e){this.group.add(Vu(t,{style:T({stroke:\"none\",fill:d[e%d.length]},C),silent:!0}))}),this),P(f,(function(t,e){this.group.add(Vu(t,{style:T({fill:\"none\",stroke:p[e%p.length]},I),silent:!0}))}),this)}function D(t,e,n){var i=n%e.length;return t[i]=t[i]||[],i}},e.type=\"radar\",e}(wf),EM=function(t){function e(e,n,i){var r=t.call(this,e,n,i)||this;return r.type=\"value\",r.angle=0,r.name=\"\",r}return n(e,t),e}(hb),VM=function(){function t(t,e,n){this.dimensions=[],this._model=t,this._indicatorAxes=O(t.getIndicatorModels(),(function(t,e){var n=\"indicator_\"+e,i=new EM(n,new Q_);return i.name=t.get(\"name\"),i.model=t,t.axis=i,this.dimensions.push(n),i}),this),this.resize(t,n)}return t.prototype.getIndicatorAxes=function(){return this._indicatorAxes},t.prototype.dataToPoint=function(t,e){var n=this._indicatorAxes[e];return this.coordToPoint(n.dataToCoord(t),e)},t.prototype.coordToPoint=function(t,e){var n=this._indicatorAxes[e].angle;return[this.cx+t*Math.cos(n),this.cy-t*Math.sin(n)]},t.prototype.pointToData=function(t){var e=t[0]-this.cx,n=t[1]-this.cy,i=Math.sqrt(e*e+n*n);e/=i,n/=i;for(var r,o=Math.atan2(-n,e),a=1/0,s=-1,l=0;l<this._indicatorAxes.length;l++){var u=this._indicatorAxes[l],h=Math.abs(o-u.angle);h<a&&(r=u,s=l,a=h)}return[s,+(r&&r.coordToData(i))]},t.prototype.resize=function(t,e){var n=t.get(\"center\"),i=e.getWidth(),r=e.getHeight(),o=Math.min(i,r)/2;this.cx=Zi(n[0],i),this.cy=Zi(n[1],r),this.startAngle=t.get(\"startAngle\")*Math.PI/180;var a=t.get(\"radius\");\"string\"!=typeof a&&\"number\"!=typeof a||(a=[0,a]),this.r0=Zi(a[0],o),this.r=Zi(a[1],o),P(this._indicatorAxes,(function(t,e){t.setExtent(this.r0,this.r);var n=this.startAngle+e*Math.PI*2/this._indicatorAxes.length;n=Math.atan2(Math.sin(n),Math.cos(n)),t.angle=n}),this)},t.prototype.update=function(t,e){var n=this._indicatorAxes,i=this._model;P(n,(function(t){t.scale.setExtent(1/0,-1/0)})),t.eachSeriesByType(\"radar\",(function(e,r){if(\"radar\"===e.get(\"coordinateSystem\")&&t.getComponent(\"radar\",e.get(\"radarIndex\"))===i){var o=e.getData();P(n,(function(t){t.scale.unionExtentFromData(o,o.mapDimension(t.dim))}))}}),this);var r=i.get(\"splitNumber\");function o(t){var e=Math.pow(10,Math.floor(Math.log(t)/Math.LN10)),n=t/e;return 2===n?n=5:n*=2,n*e}P(n,(function(t,e){var n=Ex(t.scale,t.model).extent;Vx(t.scale,t.model);var i=t.model,a=t.scale,s=zx(a,i.get(\"min\",!0)),l=zx(a,i.get(\"max\",!0)),u=a.getInterval();if(null!=s&&null!=l)a.setExtent(+s,+l),a.setInterval((l-s)/r);else if(null!=s){var h=void 0;do{h=s+u*r,a.setExtent(+s,h),a.setInterval(u),u=o(u)}while(h<n[1]&&isFinite(h)&&isFinite(n[1]))}else if(null!=l){var c=void 0;do{c=l-u*r,a.setExtent(c,+l),a.setInterval(u),u=o(u)}while(c>n[0]&&isFinite(c)&&isFinite(n[0]))}else{a.getTicks().length-1>r&&(u=o(u));c=ji((h=Math.ceil(n[1]/u)*u)-u*r);a.setExtent(c,h),a.setInterval(u)}}))},t.prototype.convertToPixel=function(t,e,n){return console.warn(\"Not implemented.\"),null},t.prototype.convertFromPixel=function(t,e,n){return console.warn(\"Not implemented.\"),null},t.prototype.containPoint=function(t){return console.warn(\"Not implemented.\"),!1},t.create=function(e,n){var i=[];return e.eachComponent(\"radar\",(function(r){var o=new t(r,e,n);i.push(o),r.coordinateSystem=o})),e.eachSeriesByType(\"radar\",(function(t){\"radar\"===t.get(\"coordinateSystem\")&&(t.coordinateSystem=i[t.get(\"radarIndex\")||0])})),i},t.dimensions=[],t}();function BM(t){t.registerCoordinateSystem(\"radar\",VM),t.registerComponentModel(RM),t.registerComponentView(zM),t.registerVisual({seriesType:\"radar\",reset:function(t){var e=t.getData();e.each((function(t){e.setItemVisual(t,\"legendIcon\",\"roundRect\")})),e.setVisual(\"legendIcon\",\"roundRect\")}})}var FM=\"\\0_ec_interaction_mutex\";function GM(t,e){return!!HM(t)[e]}function HM(t){return t[FM]||(t[FM]={})}Hm({type:\"takeGlobalCursor\",event:\"globalCursorTaken\",update:\"update\"},(function(){}));var WM=function(t){function e(e){var n=t.call(this)||this;n._zr=e;var i=V(n._mousedownHandler,n),r=V(n._mousemoveHandler,n),o=V(n._mouseupHandler,n),a=V(n._mousewheelHandler,n),s=V(n._pinchHandler,n);return n.enable=function(t,n){this.disable(),this._opt=T(w(n)||{},{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}),null==t&&(t=!0),!0!==t&&\"move\"!==t&&\"pan\"!==t||(e.on(\"mousedown\",i),e.on(\"mousemove\",r),e.on(\"mouseup\",o)),!0!==t&&\"scale\"!==t&&\"zoom\"!==t||(e.on(\"mousewheel\",a),e.on(\"pinch\",s))},n.disable=function(){e.off(\"mousedown\",i),e.off(\"mousemove\",r),e.off(\"mouseup\",o),e.off(\"mousewheel\",a),e.off(\"pinch\",s)},n}return n(e,t),e.prototype.isDragging=function(){return this._dragging},e.prototype.isPinching=function(){return this._pinching},e.prototype.setPointerChecker=function(t){this.pointerChecker=t},e.prototype.dispose=function(){this.disable()},e.prototype._mousedownHandler=function(t){if(!(ne(t)||t.target&&t.target.draggable)){var e=t.offsetX,n=t.offsetY;this.pointerChecker&&this.pointerChecker(t,e,n)&&(this._x=e,this._y=n,this._dragging=!0)}},e.prototype._mousemoveHandler=function(t){if(this._dragging&&YM(\"moveOnMouseMove\",t,this._opt)&&\"pinch\"!==t.gestureEvent&&!GM(this._zr,\"globalPan\")){var e=t.offsetX,n=t.offsetY,i=this._x,r=this._y,o=e-i,a=n-r;this._x=e,this._y=n,this._opt.preventDefaultMouseMove&&ee(t.event),XM(this,\"pan\",\"moveOnMouseMove\",t,{dx:o,dy:a,oldX:i,oldY:r,newX:e,newY:n,isAvailableBehavior:null})}},e.prototype._mouseupHandler=function(t){ne(t)||(this._dragging=!1)},e.prototype._mousewheelHandler=function(t){var e=YM(\"zoomOnMouseWheel\",t,this._opt),n=YM(\"moveOnMouseWheel\",t,this._opt),i=t.wheelDelta,r=Math.abs(i),o=t.offsetX,a=t.offsetY;if(0!==i&&(e||n)){if(e){var s=r>3?1.4:r>1?1.2:1.1;UM(this,\"zoom\",\"zoomOnMouseWheel\",t,{scale:i>0?s:1/s,originX:o,originY:a,isAvailableBehavior:null})}if(n){var l=Math.abs(i);UM(this,\"scrollMove\",\"moveOnMouseWheel\",t,{scrollDelta:(i>0?1:-1)*(l>3?.4:l>1?.15:.05),originX:o,originY:a,isAvailableBehavior:null})}}},e.prototype._pinchHandler=function(t){GM(this._zr,\"globalPan\")||UM(this,\"zoom\",null,t,{scale:t.pinchScale>1?1.1:1/1.1,originX:t.pinchX,originY:t.pinchY,isAvailableBehavior:null})},e}(Ft);function UM(t,e,n,i,r){t.pointerChecker&&t.pointerChecker(i,r.originX,r.originY)&&(ee(i.event),XM(t,e,n,i,r))}function XM(t,e,n,i,r){r.isAvailableBehavior=V(YM,null,n,i),t.trigger(e,r)}function YM(t,e,n){var i=n[t];return!t||i&&(!H(i)||e.event[i+\"Key\"])}function ZM(t,e,n){var i=t.target;i.x+=e,i.y+=n,i.dirty()}function jM(t,e,n,i){var r=t.target,o=t.zoomLimit,a=t.zoom=t.zoom||1;if(a*=e,o){var s=o.min||0,l=o.max||1/0;a=Math.max(Math.min(l,a),s)}var u=a/t.zoom;t.zoom=a,r.x-=(n-r.x)*(u-1),r.y-=(i-r.y)*(u-1),r.scaleX*=u,r.scaleY*=u,r.dirty()}var qM={axisPointer:1,tooltip:1,brush:1};function KM(t,e,n){var i=e.getComponentByElement(t.topTarget),r=i&&i.coordinateSystem;return i&&i!==n&&!qM.hasOwnProperty(i.mainType)&&r&&r.model!==n}var $M=[\"rect\",\"circle\",\"line\",\"ellipse\",\"polygon\",\"polyline\",\"path\"],JM=ht($M),QM=ht($M.concat([\"g\"])),tI=ht($M.concat([\"g\"])),eI=kr();function nI(t){var e=t.getItemStyle(),n=t.get(\"areaColor\");return null!=n&&(e.fill=n),e}var iI=function(){function t(t){var e=new Ei;this.uid=Nh(\"ec_map_draw\"),this._controller=new WM(t.getZr()),this._controllerHost={target:e},this.group=e,e.add(this._regionsGroup=new Ei),e.add(this._svgGroup=new Ei)}return t.prototype.draw=function(t,e,n,i,r){var o=\"geo\"===t.mainType,a=t.getData&&t.getData();o&&e.eachComponent({mainType:\"series\",subType:\"map\"},(function(e){a||e.getHostGeoModel()!==t||(a=e.getData())}));var s=t.coordinateSystem,l=this._regionsGroup,u=this.group,h=s.getTransformInfo(),c=h.raw,p=h.roam;!l.childAt(0)||r?(u.x=p.x,u.y=p.y,u.scaleX=p.scaleX,u.scaleY=p.scaleY,u.dirty()):Hu(u,p,t);var d=a&&a.getVisual(\"visualMeta\")&&a.getVisual(\"visualMeta\").length>0,f={api:n,geo:s,mapOrGeoModel:t,data:a,isVisualEncodedByVisualMap:d,isGeo:o,transformInfoRaw:c};\"geoJSON\"===s.resourceType?this._buildGeoJSON(f):\"geoSVG\"===s.resourceType&&this._buildSVG(f),this._updateController(t,e,n),this._updateMapSelectHandler(t,l,n,i)},t.prototype._buildGeoJSON=function(t){var e=this._regionsGroupByName=ht(),n=ht(),i=this._regionsGroup,r=t.transformInfoRaw,o=t.mapOrGeoModel,a=t.data,s=function(t){return[t[0]*r.scaleX+r.x,t[1]*r.scaleY+r.y]};i.removeAll(),P(t.geo.regions,(function(r){var l=r.name,u=e.get(l),h=n.get(l)||{},c=h.dataIdx,p=h.regionModel;u||(u=e.set(l,new Ei),i.add(u),c=a?a.indexOfName(l):null,p=t.isGeo?o.getRegionModel(l):a?a.getItemModel(c):null,n.set(l,{dataIdx:c,regionModel:p}));var d=new yu({segmentIgnoreThreshold:1,shape:{paths:[]}});u.add(d),P(r.geometries,(function(t){if(\"polygon\"===t.type){for(var e=[],n=0;n<t.exterior.length;++n)e.push(s(t.exterior[n]));d.shape.paths.push(new ru({segmentIgnoreThreshold:1,shape:{points:e}}));for(n=0;n<(t.interiors?t.interiors.length:0);++n){for(var i=t.interiors[n],r=[],o=0;o<i.length;++o)r.push(s(i[o]));d.shape.paths.push(new ru({segmentIgnoreThreshold:1,shape:{points:r}}))}}})),rI(t,d,c,p),d instanceof So&&(d.culling=!0);var f=s(r.getCenter());oI(t,d,l,p,o,c,f)})),e.each((function(e,i){var r=n.get(i),a=r.dataIdx,s=r.regionModel;aI(t,e,i,s,o,a),sI(t,e,i,s,o),lI(t,e,i,s,o)}),this)},t.prototype._buildSVG=function(t){var e=t.geo.map,n=t.transformInfoRaw;this._svgGroup.x=n.x,this._svgGroup.y=n.y,this._svgGroup.scaleX=n.scaleX,this._svgGroup.scaleY=n.scaleY,this._svgResourceChanged(e)&&(this._freeSVG(),this._useSVG(e));var i=this._svgDispatcherMap=ht(),r=!1;P(this._svgGraphicRecord.named,(function(e){var n=e.name,o=t.mapOrGeoModel,a=t.data,s=e.svgNodeTagLower,l=e.el,u=a?a.indexOfName(n):null,h=o.getRegionModel(n);(null!=JM.get(s)&&l instanceof So&&rI(t,l,u,h),l instanceof So&&(l.culling=!0),l.z2EmphasisLift=0,e.namedFrom)||(null!=tI.get(s)&&oI(t,l,n,h,o,u,null),aI(t,l,n,h,o,u),sI(t,l,n,h,o),null!=QM.get(s)&&(\"self\"===lI(t,l,n,h,o)&&(r=!0),(i.get(n)||i.set(n,[])).push(l)))}),this),this._enableBlurEntireSVG(r,t)},t.prototype._enableBlurEntireSVG=function(t,e){if(t&&e.isGeo){var n=e.mapOrGeoModel.getModel([\"blur\",\"itemStyle\"]).getItemStyle().opacity;this._svgGraphicRecord.root.traverse((function(t){if(!t.isGroup){Xs(t);var e=t.ensureState(\"blur\").style||{};null==e.opacity&&null!=n&&(e.opacity=n),t.ensureState(\"emphasis\")}}))}},t.prototype.remove=function(){this._regionsGroup.removeAll(),this._regionsGroupByName=null,this._svgGroup.removeAll(),this._freeSVG(),this._controller.dispose(),this._controllerHost=null},t.prototype.findHighDownDispatchers=function(t,e){if(null==t)return[];var n=e.coordinateSystem;if(\"geoJSON\"===n.resourceType){var i=this._regionsGroupByName;if(i){var r=i.get(t);return r?[r]:[]}}else if(\"geoSVG\"===n.resourceType)return this._svgDispatcherMap&&this._svgDispatcherMap.get(t)||[]},t.prototype._svgResourceChanged=function(t){return this._svgMapName!==t},t.prototype._useSVG=function(t){var e=Lv(t);if(e&&\"geoSVG\"===e.type){var n=e.useGraphic(this.uid);this._svgGroup.add(n.root),this._svgGraphicRecord=n,this._svgMapName=t}},t.prototype._freeSVG=function(){var t=this._svgMapName;if(null!=t){var e=Lv(t);e&&\"geoSVG\"===e.type&&e.freeGraphic(this.uid),this._svgGraphicRecord=null,this._svgDispatcherMap=null,this._svgGroup.removeAll(),this._svgMapName=null}},t.prototype._updateController=function(t,e,n){var i=t.coordinateSystem,r=this._controller,o=this._controllerHost;o.zoomLimit=t.get(\"scaleLimit\"),o.zoom=i.getZoom(),r.enable(t.get(\"roam\")||!1);var a=t.mainType;function s(){var e={type:\"geoRoam\",componentType:a};return e[a+\"Id\"]=t.id,e}r.off(\"pan\").on(\"pan\",(function(t){this._mouseDownFlag=!1,ZM(o,t.dx,t.dy),n.dispatchAction(I(s(),{dx:t.dx,dy:t.dy}))}),this),r.off(\"zoom\").on(\"zoom\",(function(t){this._mouseDownFlag=!1,jM(o,t.scale,t.originX,t.originY),n.dispatchAction(I(s(),{zoom:t.scale,originX:t.originX,originY:t.originY}))}),this),r.setPointerChecker((function(e,r,o){return i.containPoint([r,o])&&!KM(e,n,t)}))},t.prototype.resetForLabelLayout=function(){this.group.traverse((function(t){var e=t.getTextContent();e&&(e.ignore=eI(e).ignore)}))},t.prototype._updateMapSelectHandler=function(t,e,n,i){var r=this;e.off(\"mousedown\"),e.off(\"click\"),t.get(\"selectedMode\")&&(e.on(\"mousedown\",(function(){r._mouseDownFlag=!0})),e.on(\"click\",(function(t){r._mouseDownFlag&&(r._mouseDownFlag=!1)})))},t}();function rI(t,e,n,i){var r=i.getModel(\"itemStyle\"),o=i.getModel([\"emphasis\",\"itemStyle\"]),a=i.getModel([\"blur\",\"itemStyle\"]),s=i.getModel([\"select\",\"itemStyle\"]),l=nI(r),u=nI(o),h=nI(s),c=nI(a),p=t.data;if(p){var d=p.getItemVisual(n,\"style\"),f=p.getItemVisual(n,\"decal\");t.isVisualEncodedByVisualMap&&d.fill&&(l.fill=d.fill),f&&(l.decal=Ey(f,t.api))}e.setStyle(l),e.style.strokeNoScale=!0,e.ensureState(\"emphasis\").style=u,e.ensureState(\"select\").style=h,e.ensureState(\"blur\").style=c,Xs(e)}function oI(t,e,n,i,r,o,a){var s=t.data,l=t.isGeo,u=s&&isNaN(s.get(s.mapDimension(\"value\"),o)),h=s&&s.getItemLayout(o);if(l||u||h&&h.showLabel){var c=l?n:o,p=void 0;(!s||o>=0)&&(p=r);var d=a?{normal:{align:\"center\",verticalAlign:\"middle\"}}:null;hh(e,ch(i),{labelFetcher:p,labelDataIndex:c,defaultText:n},d);var f=e.getTextContent();if(f&&(eI(f).ignore=f.ignore,e.textConfig&&a)){var g=e.getBoundingRect().clone();e.textConfig.layoutRect=g,e.textConfig.position=[(a[0]-g.x)/g.width*100+\"%\",(a[1]-g.y)/g.height*100+\"%\"]}e.disableLabelAnimation=!0}else e.removeTextContent(),e.removeTextConfig(),e.disableLabelAnimation=null}function aI(t,e,n,i,r,o){t.data?t.data.setItemGraphicEl(o,e):_s(e).eventData={componentType:\"geo\",componentIndex:r.componentIndex,geoIndex:r.componentIndex,name:n,region:i&&i.option||{}}}function sI(t,e,n,i,r){t.data||oh({el:e,componentModel:r,itemName:n,itemTooltipOption:i.get(\"tooltip\")})}function lI(t,e,n,i,r){e.highDownSilentOnTouch=!!r.get(\"selectedMode\");var o=i.getModel(\"emphasis\"),a=o.get(\"focus\");return sl(e,a,o.get(\"blurScope\")),t.isGeo&&function(t,e,n){var i=_s(t);i.componentMainType=e.mainType,i.componentIndex=e.componentIndex,i.componentHighDownName=n}(e,r,n),a}var uI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){if(!i||\"mapToggleSelect\"!==i.type||i.from!==this.uid){var r=this.group;if(r.removeAll(),!t.getHostGeoModel()){if(this._mapDraw&&i&&\"geoRoam\"===i.type&&this._mapDraw.resetForLabelLayout(),i&&\"geoRoam\"===i.type&&\"series\"===i.componentType&&i.seriesId===t.id)(o=this._mapDraw)&&r.add(o.group);else if(t.needsDrawMap){var o=this._mapDraw||new iI(n);r.add(o.group),o.draw(t,e,n,this,i),this._mapDraw=o}else this._mapDraw&&this._mapDraw.remove(),this._mapDraw=null;t.get(\"showLegendSymbol\")&&e.getComponent(\"legend\")&&this._renderSymbols(t,e,n)}}},e.prototype.remove=function(){this._mapDraw&&this._mapDraw.remove(),this._mapDraw=null,this.group.removeAll()},e.prototype.dispose=function(){this._mapDraw&&this._mapDraw.remove(),this._mapDraw=null},e.prototype._renderSymbols=function(t,e,n){var i=t.originalData,r=this.group;i.each(i.mapDimension(\"value\"),(function(e,n){if(!isNaN(e)){var o=i.getItemLayout(n);if(o&&o.point){var a=o.point,s=o.offset,l=new Nl({style:{fill:t.getData().getVisual(\"style\").fill},shape:{cx:a[0]+9*s,cy:a[1],r:3},silent:!0,z2:8+(s?0:11)});if(!s){var u=t.mainSeries.getData(),h=i.getName(n),c=u.indexOfName(h),p=i.getItemModel(n),d=p.getModel(\"label\"),f=u.getItemGraphicEl(c);hh(l,ch(p),{labelFetcher:{getFormattedLabel:function(e,n){return t.getFormattedLabel(c,n)}}}),l.disableLabelAnimation=!0,d.get(\"position\")||l.setTextConfig({position:\"bottom\"}),f.onHoverStateChange=function(t){Ws(l,t)}}r.add(l)}}}))},e.type=\"map\",e}(Tf),hI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.needsDrawMap=!1,n.seriesGroup=[],n.getTooltipPosition=function(t){if(null!=t){var e=this.getData().getName(t),n=this.coordinateSystem,i=n.getRegion(e);return i&&n.dataToPoint(i.getCenter())}},n}return n(e,t),e.prototype.getInitialData=function(t){for(var e=MS(this,{coordDimensions:[\"value\"],encodeDefaulter:B(up,this)}),n=ht(),i=[],r=0,o=e.count();r<o;r++){var a=e.getName(r);n.set(a,!0)}return P(Pv(this.getMapType(),this.option.nameMap,this.option.nameProperty).regions,(function(t){var e=t.name;n.get(e)||i.push(e)})),e.appendValues([],i),e},e.prototype.getHostGeoModel=function(){var t=this.option.geoIndex;return null!=t?this.ecModel.getComponent(\"geo\",t):null},e.prototype.getMapType=function(){return(this.getHostGeoModel()||this).option.map},e.prototype.getRawValue=function(t){var e=this.getData();return e.get(e.mapDimension(\"value\"),t)},e.prototype.getRegionModel=function(t){var e=this.getData();return e.getItemModel(e.indexOfName(t))},e.prototype.formatTooltip=function(t,e,n){for(var i=this.getData(),r=this.getRawValue(t),o=i.getName(t),a=this.seriesGroup,s=[],l=0;l<a.length;l++){var u=a[l].originalData.indexOfName(o),h=i.mapDimension(\"value\");isNaN(a[l].originalData.get(h,u))||s.push(a[l].name)}return tf(\"section\",{header:s.join(\", \"),noHeader:!s.length,blocks:[tf(\"nameValue\",{name:o,value:r})]})},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.getLegendIcon=function(t){var e=t.icon||\"roundRect\",n=fy(e,0,0,t.itemWidth,t.itemHeight,t.itemStyle.fill);return n.setStyle(t.itemStyle),n.style.stroke=\"none\",e.indexOf(\"empty\")>-1&&(n.style.stroke=n.style.fill,n.style.fill=\"#fff\",n.style.lineWidth=2),n},e.type=\"series.map\",e.dependencies=[\"geo\"],e.layoutMode=\"box\",e.defaultOption={zlevel:0,z:2,coordinateSystem:\"geo\",map:\"\",left:\"center\",top:\"center\",aspectScale:null,showLegendSymbol:!0,boundingCoords:null,center:null,zoom:1,scaleLimit:null,selectedMode:!0,label:{show:!1,color:\"#000\"},itemStyle:{borderWidth:.5,borderColor:\"#444\",areaColor:\"#eee\"},emphasis:{label:{show:!0,color:\"rgb(100,0,0)\"},itemStyle:{areaColor:\"rgba(255,215,0,0.8)\"}},select:{label:{show:!0,color:\"rgb(100,0,0)\"},itemStyle:{color:\"rgba(255,215,0,0.8)\"}},nameProperty:\"name\"},e}(ff);function cI(t){var e={};t.eachSeriesByType(\"map\",(function(t){var n=t.getHostGeoModel(),i=n?\"o\"+n.id:\"i\"+t.getMapType();(e[i]=e[i]||[]).push(t)})),P(e,(function(t,e){for(var n,i,r,o=(n=O(t,(function(t){return t.getData()})),i=t[0].get(\"mapValueCalculation\"),r={},P(n,(function(t){t.each(t.mapDimension(\"value\"),(function(e,n){var i=\"ec-\"+t.getName(n);r[i]=r[i]||[],isNaN(e)||r[i].push(e)}))})),n[0].map(n[0].mapDimension(\"value\"),(function(t,e){for(var o=\"ec-\"+n[0].getName(e),a=0,s=1/0,l=-1/0,u=r[o].length,h=0;h<u;h++)s=Math.min(s,r[o][h]),l=Math.max(l,r[o][h]),a+=r[o][h];return 0===u?NaN:\"min\"===i?s:\"max\"===i?l:\"average\"===i?a/u:a}))),a=0;a<t.length;a++)t[a].originalData=t[a].getData();for(a=0;a<t.length;a++)t[a].seriesGroup=t,t[a].needsDrawMap=0===a&&!t[a].getHostGeoModel(),t[a].setData(o.cloneShallow()),t[a].mainSeries=t[0]}))}function pI(t){var e={};t.eachSeriesByType(\"map\",(function(n){var i=n.getMapType();if(!n.getHostGeoModel()&&!e[i]){var r={};P(n.seriesGroup,(function(e){var n=e.coordinateSystem,i=e.originalData;e.get(\"showLegendSymbol\")&&t.getComponent(\"legend\")&&i.each(i.mapDimension(\"value\"),(function(t,e){var o=i.getName(e),a=n.getRegion(o);if(a&&!isNaN(t)){var s=r[o]||0,l=n.dataToPoint(a.getCenter());r[o]=s+1,i.setItemLayout(e,{point:l,offset:s})}}))}));var o=n.getData();o.each((function(t){var e=o.getName(t),n=o.getItemLayout(t)||{};n.showLabel=!r[e],o.setItemLayout(t,n)})),e[i]=!0}}))}var dI=Rt,fI=function(t){function e(e){var n=t.call(this)||this;return n.type=\"view\",n.dimensions=[\"x\",\"y\"],n._roamTransformable=new oi,n._rawTransformable=new oi,n.name=e,n}return n(e,t),e.prototype.setBoundingRect=function(t,e,n,i){return this._rect=new gi(t,e,n,i),this._rect},e.prototype.getBoundingRect=function(){return this._rect},e.prototype.setViewRect=function(t,e,n,i){this._transformTo(t,e,n,i),this._viewRect=new gi(t,e,n,i)},e.prototype._transformTo=function(t,e,n,i){var r=this.getBoundingRect(),o=this._rawTransformable;o.transform=r.calculateTransform(new gi(t,e,n,i));var a=o.parent;o.parent=null,o.decomposeTransform(),o.parent=a,this._updateTransform()},e.prototype.setCenter=function(t){t&&(this._center=t,this._updateCenterAndZoom())},e.prototype.setZoom=function(t){t=t||1;var e=this.zoomLimit;e&&(null!=e.max&&(t=Math.min(e.max,t)),null!=e.min&&(t=Math.max(e.min,t))),this._zoom=t,this._updateCenterAndZoom()},e.prototype.getDefaultCenter=function(){var t=this.getBoundingRect();return[t.x+t.width/2,t.y+t.height/2]},e.prototype.getCenter=function(){return this._center||this.getDefaultCenter()},e.prototype.getZoom=function(){return this._zoom||1},e.prototype.getRoamTransform=function(){return this._roamTransformable.getLocalTransform()},e.prototype._updateCenterAndZoom=function(){var t=this._rawTransformable.getLocalTransform(),e=this._roamTransformable,n=this.getDefaultCenter(),i=this.getCenter(),r=this.getZoom();i=Rt([],i,t),n=Rt([],n,t),e.originX=i[0],e.originY=i[1],e.x=n[0]-i[0],e.y=n[1]-i[1],e.scaleX=e.scaleY=r,this._updateTransform()},e.prototype._updateTransform=function(){var t=this._roamTransformable,e=this._rawTransformable;e.parent=t,t.updateTransform(),e.updateTransform(),Hn(this.transform||(this.transform=[]),e.transform||[1,0,0,1,0,0]),this._rawTransform=e.getLocalTransform(),this.invTransform=this.invTransform||[],Zn(this.invTransform,this.transform),this.decomposeTransform()},e.prototype.getTransformInfo=function(){var t=this._rawTransformable,e=this._roamTransformable,n=new oi;return n.transform=e.transform,n.decomposeTransform(),{roam:{x:n.x,y:n.y,scaleX:n.scaleX,scaleY:n.scaleY},raw:{x:t.x,y:t.y,scaleX:t.scaleX,scaleY:t.scaleY}}},e.prototype.getViewRect=function(){return this._viewRect},e.prototype.getViewRectAfterRoam=function(){var t=this.getBoundingRect().clone();return t.applyTransform(this.transform),t},e.prototype.dataToPoint=function(t,e,n){var i=e?this._rawTransform:this.transform;return n=n||[],i?dI(n,t,i):vt(n,t)},e.prototype.pointToData=function(t){var e=this.invTransform;return e?dI([],t,e):[t[0],t[1]]},e.prototype.convertToPixel=function(t,e,n){var i=gI(e);return i===this?i.dataToPoint(n):null},e.prototype.convertFromPixel=function(t,e,n){var i=gI(e);return i===this?i.pointToData(n):null},e.prototype.containPoint=function(t){return this.getViewRectAfterRoam().contain(t[0],t[1])},e.dimensions=[\"x\",\"y\"],e}(oi);function gI(t){var e=t.seriesModel;return e?e.coordinateSystem:null}var yI={geoJSON:{aspectScale:.75,invertLongitute:!0},geoSVG:{aspectScale:1,invertLongitute:!1}},vI=function(t){function e(e,n,i){var r=t.call(this,e)||this;r.dimensions=[\"lng\",\"lat\"],r.type=\"geo\",r._nameCoordMap=ht(),r.map=n;var o=Pv(n,i.nameMap,i.nameProperty),a=Lv(n);r.resourceType=a?a.type:null;var s=yI[a.type];r._regionsMap=o.regionsMap,r._invertLongitute=s.invertLongitute,r.regions=o.regions,r.aspectScale=tt(i.aspectScale,s.aspectScale);var l=o.boundingRect;return r.setBoundingRect(l.x,l.y,l.width,l.height),r}return n(e,t),e.prototype._transformTo=function(t,e,n,i){var r=this.getBoundingRect(),o=this._invertLongitute;r=r.clone(),o&&(r.y=-r.y-r.height);var a=this._rawTransformable;a.transform=r.calculateTransform(new gi(t,e,n,i));var s=a.parent;a.parent=null,a.decomposeTransform(),a.parent=s,o&&(a.scaleY=-a.scaleY),this._updateTransform()},e.prototype.getRegion=function(t){return this._regionsMap.get(t)},e.prototype.getRegionByCoord=function(t){for(var e=this.regions,n=0;n<e.length;n++){var i=e[n];if(\"geoJSON\"===i.type&&i.contain(t))return e[n]}},e.prototype.addGeoCoord=function(t,e){this._nameCoordMap.set(t,e)},e.prototype.getGeoCoord=function(t){var e=this._regionsMap.get(t);return this._nameCoordMap.get(t)||e&&e.getCenter()},e.prototype.dataToPoint=function(t,e,n){if(\"string\"==typeof t&&(t=this.getGeoCoord(t)),t)return fI.prototype.dataToPoint.call(this,t,e,n)},e.prototype.convertToPixel=function(t,e,n){var i=mI(e);return i===this?i.dataToPoint(n):null},e.prototype.convertFromPixel=function(t,e,n){var i=mI(e);return i===this?i.pointToData(n):null},e}(fI);function mI(t){var e=t.geoModel,n=t.seriesModel;return e?e.coordinateSystem:n?n.coordinateSystem||(n.getReferringComponents(\"geo\",Nr).models[0]||{}).coordinateSystem:null}function _I(t,e){var n=t.get(\"boundingCoords\");if(null!=n){var i=n[0],r=n[1];isNaN(i[0])||isNaN(i[1])||isNaN(r[0])||isNaN(r[1])||this.setBoundingRect(i[0],i[1],r[0]-i[0],r[1]-i[1])}var o,a,s,l=this.getBoundingRect(),u=t.get(\"layoutCenter\"),h=t.get(\"layoutSize\"),c=e.getWidth(),p=e.getHeight(),d=l.width/l.height*this.aspectScale,f=!1;if(u&&h&&(o=[Zi(u[0],c),Zi(u[1],p)],a=Zi(h,Math.min(c,p)),isNaN(o[0])||isNaN(o[1])||isNaN(a)||(f=!0)),f)s={},d>1?(s.width=a,s.height=a/d):(s.height=a,s.width=a*d),s.y=o[1]-s.height/2,s.x=o[0]-s.width/2;else{var g=t.getBoxLayoutParams();g.aspect=d,s=Vc(g,{width:c,height:p})}this.setViewRect(s.x,s.y,s.width,s.height),this.setCenter(t.get(\"center\")),this.setZoom(t.get(\"zoom\"))}L(vI,fI);var xI=new(function(){function t(){this.dimensions=vI.prototype.dimensions}return t.prototype.create=function(t,e){var n=[];t.eachComponent(\"geo\",(function(t,i){var r=t.get(\"map\"),o=new vI(r+i,r,{nameMap:t.get(\"nameMap\"),nameProperty:t.get(\"nameProperty\"),aspectScale:t.get(\"aspectScale\")});o.zoomLimit=t.get(\"scaleLimit\"),n.push(o),t.coordinateSystem=o,o.model=t,o.resize=_I,o.resize(t,e)})),t.eachSeries((function(t){if(\"geo\"===t.get(\"coordinateSystem\")){var e=t.get(\"geoIndex\")||0;t.coordinateSystem=n[e]}}));var i={};return t.eachSeriesByType(\"map\",(function(t){if(!t.getHostGeoModel()){var e=t.getMapType();i[e]=i[e]||[],i[e].push(t)}})),P(i,(function(t,i){var r=O(t,(function(t){return t.get(\"nameMap\")})),o=new vI(i,i,{nameMap:M(r),nameProperty:t[0].get(\"nameProperty\"),aspectScale:t[0].get(\"aspectScale\")});o.zoomLimit=Q.apply(null,O(t,(function(t){return t.get(\"scaleLimit\")}))),n.push(o),o.resize=_I,o.resize(t[0],e),P(t,(function(t){t.coordinateSystem=o,function(t,e){P(e.get(\"geoCoord\"),(function(e,n){t.addGeoCoord(n,e)}))}(o,t)}))})),n},t.prototype.getFilledRegions=function(t,e,n,i){for(var r=(t||[]).slice(),o=ht(),a=0;a<r.length;a++)o.set(r[a].name,r[a]);return P(Pv(e,n,i).regions,(function(t){var e=t.name;!o.get(e)&&r.push({name:e})})),r},t}()),bI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e,n){var i=Lv(t.map);if(i&&\"geoJSON\"===i.type){var r=t.itemStyle=t.itemStyle||{};\"color\"in r||(r.color=\"#eee\")}this.mergeDefaultAndTheme(t,n),br(t,\"label\",[\"show\"])},e.prototype.optionUpdated=function(){var t=this,e=this.option;e.regions=xI.getFilledRegions(e.regions,e.map,e.nameMap,e.nameProperty);var n={};this._optionModelMap=R(e.regions||[],(function(e,i){var r=i.name;return r&&(e.set(r,new Oh(i,t,t.ecModel)),i.selected&&(n[r]=!0)),e}),ht()),e.selectedMap||(e.selectedMap=n)},e.prototype.getRegionModel=function(t){return this._optionModelMap.get(t)||new Oh(null,this,this.ecModel)},e.prototype.getFormattedLabel=function(t,e){var n=this.getRegionModel(t),i=\"normal\"===e?n.get([\"label\",\"formatter\"]):n.get([\"emphasis\",\"label\",\"formatter\"]),r={name:t};return\"function\"==typeof i?(r.status=e,i(r)):\"string\"==typeof i?i.replace(\"{a}\",null!=t?t:\"\"):void 0},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.select=function(t){var e=this.option,n=e.selectedMode;n&&(\"multiple\"!==n&&(e.selectedMap=null),(e.selectedMap||(e.selectedMap={}))[t]=!0)},e.prototype.unSelect=function(t){var e=this.option.selectedMap;e&&(e[t]=!1)},e.prototype.toggleSelected=function(t){this[this.isSelected(t)?\"unSelect\":\"select\"](t)},e.prototype.isSelected=function(t){var e=this.option.selectedMap;return!(!e||!e[t])},e.type=\"geo\",e.layoutMode=\"box\",e.defaultOption={zlevel:0,z:0,show:!0,left:\"center\",top:\"center\",aspectScale:null,silent:!1,map:\"\",boundingCoords:null,center:null,zoom:1,scaleLimit:null,label:{show:!1,color:\"#000\"},itemStyle:{borderWidth:.5,borderColor:\"#444\"},emphasis:{label:{show:!0,color:\"rgb(100,0,0)\"},itemStyle:{color:\"rgba(255,215,0,0.8)\"}},select:{label:{show:!0,color:\"rgb(100,0,0)\"},itemStyle:{color:\"rgba(255,215,0,0.8)\"}},regions:[]},e}(Xc);function wI(t,e,n){var i=t.getZoom(),r=t.getCenter(),o=e.zoom,a=t.dataToPoint(r);if(null!=e.dx&&null!=e.dy&&(a[0]-=e.dx,a[1]-=e.dy,t.setCenter(t.pointToData(a))),null!=o){if(n){var s=n.min||0,l=n.max||1/0;o=Math.max(Math.min(i*o,l),s)/i}t.scaleX*=o,t.scaleY*=o;var u=(e.originX-t.x)*(o-1),h=(e.originY-t.y)*(o-1);t.x-=u,t.y-=h,t.updateTransform(),t.setCenter(t.pointToData(a)),t.setZoom(o*i)}return{center:t.getCenter(),zoom:t.getZoom()}}var SI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.focusBlurEnabled=!0,n}return n(e,t),e.prototype.init=function(t,e){var n=new iI(e);this._mapDraw=n,this.group.add(n.group),this._api=e},e.prototype.render=function(t,e,n,i){var r=this._mapDraw;t.get(\"show\")?r.draw(t,e,n,this,i):this._mapDraw.group.removeAll(),r.group.on(\"click\",this._handleRegionClick,this),r.group.silent=t.get(\"silent\"),this._model=t,this.updateSelectStatus(t,e,n)},e.prototype._handleRegionClick=function(t){var e;iy(t.target,(function(t){return null!=(e=_s(t).eventData)}),!0),e&&this._api.dispatchAction({type:\"geoToggleSelect\",geoId:this._model.id,name:e.name})},e.prototype.updateSelectStatus=function(t,e,n){var i=this;this._mapDraw.group.traverse((function(t){var e=_s(t).eventData;if(e)return i._model.isSelected(e.name)?n.enterSelect(t):n.leaveSelect(t),!0}))},e.prototype.findHighDownDispatchers=function(t){return this._mapDraw&&this._mapDraw.findHighDownDispatchers(t,this._model)},e.prototype.dispose=function(){this._mapDraw&&this._mapDraw.remove()},e.type=\"geo\",e}(wf);function MI(t){function e(e,n){n.update=\"geo:updateSelectStatus\",t.registerAction(n,(function(t,n){var i={},r=[];return n.eachComponent({mainType:\"geo\",query:t},(function(n){n[e](t.name),P(n.coordinateSystem.regions,(function(t){i[t.name]=n.isSelected(t.name)||!1}));var o=[];P(i,(function(t,e){i[e]&&o.push(e)})),r.push({geoIndex:n.componentIndex,name:o})})),{selected:i,allSelected:r,name:t.name}}))}t.registerCoordinateSystem(\"geo\",xI),t.registerComponentModel(bI),t.registerComponentView(SI),e(\"toggleSelected\",{type:\"geoToggleSelect\",event:\"geoselectchanged\"}),e(\"select\",{type:\"geoSelect\",event:\"geoselected\"}),e(\"unSelect\",{type:\"geoUnSelect\",event:\"geounselected\"}),t.registerAction({type:\"geoRoam\",event:\"geoRoam\",update:\"updateTransform\"},(function(t,e){var n=t.componentType||\"series\";e.eachComponent({mainType:n,query:t},(function(e){var i=e.coordinateSystem;if(\"geo\"===i.type){var r=wI(i,t,e.get(\"scaleLimit\"));e.setCenter&&e.setCenter(r.center),e.setZoom&&e.setZoom(r.zoom),\"series\"===n&&P(e.seriesGroup,(function(t){t.setCenter(r.center),t.setZoom(r.zoom)}))}}))}))}function II(t,e){var n=t.isExpand?t.children:[],i=t.parentNode.children,r=t.hierNode.i?i[t.hierNode.i-1]:null;if(n.length){!function(t){var e=t.children,n=e.length,i=0,r=0;for(;--n>=0;){var o=e[n];o.hierNode.prelim+=i,o.hierNode.modifier+=i,r+=o.hierNode.change,i+=o.hierNode.shift+r}}(t);var o=(n[0].hierNode.prelim+n[n.length-1].hierNode.prelim)/2;r?(t.hierNode.prelim=r.hierNode.prelim+e(t,r),t.hierNode.modifier=t.hierNode.prelim-o):t.hierNode.prelim=o}else r&&(t.hierNode.prelim=r.hierNode.prelim+e(t,r));t.parentNode.hierNode.defaultAncestor=function(t,e,n,i){if(e){for(var r=t,o=t,a=o.parentNode.children[0],s=e,l=r.hierNode.modifier,u=o.hierNode.modifier,h=a.hierNode.modifier,c=s.hierNode.modifier;s=AI(s),o=LI(o),s&&o;){r=AI(r),a=LI(a),r.hierNode.ancestor=t;var p=s.hierNode.prelim+c-o.hierNode.prelim-u+i(s,o);p>0&&(PI(kI(s,t,n),t,p),u+=p,l+=p),c+=s.hierNode.modifier,u+=o.hierNode.modifier,l+=r.hierNode.modifier,h+=a.hierNode.modifier}s&&!AI(r)&&(r.hierNode.thread=s,r.hierNode.modifier+=c-l),o&&!LI(a)&&(a.hierNode.thread=o,a.hierNode.modifier+=u-h,n=t)}return n}(t,r,t.parentNode.hierNode.defaultAncestor||i[0],e)}function TI(t){var e=t.hierNode.prelim+t.parentNode.hierNode.modifier;t.setLayout({x:e},!0),t.hierNode.modifier+=t.parentNode.hierNode.modifier}function CI(t){return arguments.length?t:OI}function DI(t,e){return t-=Math.PI/2,{x:e*Math.cos(t),y:e*Math.sin(t)}}function AI(t){var e=t.children;return e.length&&t.isExpand?e[e.length-1]:t.hierNode.thread}function LI(t){var e=t.children;return e.length&&t.isExpand?e[0]:t.hierNode.thread}function kI(t,e,n){return t.hierNode.ancestor.parentNode===e.parentNode?t.hierNode.ancestor:n}function PI(t,e,n){var i=n/(e.hierNode.i-t.hierNode.i);e.hierNode.change-=i,e.hierNode.shift+=n,e.hierNode.modifier+=n,e.hierNode.prelim+=n,t.hierNode.change+=i}function OI(t,e){return t.parentNode===e.parentNode?1:2}var RI=function(){this.parentPoint=[],this.childPoints=[]},NI=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new RI},e.prototype.buildPath=function(t,e){var n=e.childPoints,i=n.length,r=e.parentPoint,o=n[0],a=n[i-1];if(1===i)return t.moveTo(r[0],r[1]),void t.lineTo(o[0],o[1]);var s=e.orient,l=\"TB\"===s||\"BT\"===s?0:1,u=1-l,h=Zi(e.forkPosition,1),c=[];c[l]=r[l],c[u]=r[u]+(a[u]-r[u])*h,t.moveTo(r[0],r[1]),t.lineTo(c[0],c[1]),t.moveTo(o[0],o[1]),c[l]=o[l],t.lineTo(c[0],c[1]),c[l]=a[l],t.lineTo(c[0],c[1]),t.lineTo(a[0],a[1]);for(var p=1;p<i-1;p++){var d=n[p];t.moveTo(d[0],d[1]),c[l]=d[l],t.lineTo(c[0],c[1])}},e}(Ka),zI=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._mainGroup=new Ei,n}return n(e,t),e.prototype.init=function(t,e){this._controller=new WM(e.getZr()),this._controllerHost={target:this.group},this.group.add(this._mainGroup)},e.prototype.render=function(t,e,n){var i=t.getData(),r=t.layoutInfo,o=this._mainGroup;\"radial\"===t.get(\"layout\")?(o.x=r.x+r.width/2,o.y=r.y+r.height/2):(o.x=r.x,o.y=r.y),this._updateViewCoordSys(t),this._updateController(t,e,n);var a=this._data;i.diff(a).add((function(e){EI(i,e)&&VI(i,e,null,o,t)})).update((function(e,n){var r=a.getItemGraphicEl(n);EI(i,e)?VI(i,e,r,o,t):r&&BI(a,n,r,o,t)})).remove((function(e){var n=a.getItemGraphicEl(e);n&&BI(a,e,n,o,t)})).execute(),this._nodeScaleRatio=t.get(\"nodeScaleRatio\"),this._updateNodeAndLinkScale(t),!0===t.get(\"expandAndCollapse\")&&i.eachItemGraphicEl((function(e,i){e.off(\"click\").on(\"click\",(function(){n.dispatchAction({type:\"treeExpandAndCollapse\",seriesId:t.id,dataIndex:i})}))})),this._data=i},e.prototype._updateViewCoordSys=function(t){var e=t.getData(),n=[];e.each((function(t){var i=e.getItemLayout(t);!i||isNaN(i.x)||isNaN(i.y)||n.push([+i.x,+i.y])}));var i=[],r=[];ra(n,i,r);var o=this._min,a=this._max;r[0]-i[0]==0&&(i[0]=o?o[0]:i[0]-1,r[0]=a?a[0]:r[0]+1),r[1]-i[1]==0&&(i[1]=o?o[1]:i[1]-1,r[1]=a?a[1]:r[1]+1);var s=t.coordinateSystem=new fI;s.zoomLimit=t.get(\"scaleLimit\"),s.setBoundingRect(i[0],i[1],r[0]-i[0],r[1]-i[1]),s.setCenter(t.get(\"center\")),s.setZoom(t.get(\"zoom\")),this.group.attr({x:s.x,y:s.y,scaleX:s.scaleX,scaleY:s.scaleY}),this._min=i,this._max=r},e.prototype._updateController=function(t,e,n){var i=this,r=this._controller,o=this._controllerHost,a=this.group;r.setPointerChecker((function(e,i,r){var o=a.getBoundingRect();return o.applyTransform(a.transform),o.contain(i,r)&&!KM(e,n,t)})),r.enable(t.get(\"roam\")),o.zoomLimit=t.get(\"scaleLimit\"),o.zoom=t.coordinateSystem.getZoom(),r.off(\"pan\").off(\"zoom\").on(\"pan\",(function(e){ZM(o,e.dx,e.dy),n.dispatchAction({seriesId:t.id,type:\"treeRoam\",dx:e.dx,dy:e.dy})})).on(\"zoom\",(function(e){jM(o,e.scale,e.originX,e.originY),n.dispatchAction({seriesId:t.id,type:\"treeRoam\",zoom:e.scale,originX:e.originX,originY:e.originY}),i._updateNodeAndLinkScale(t),n.updateLabelLayout()}))},e.prototype._updateNodeAndLinkScale=function(t){var e=t.getData(),n=this._getNodeGlobalScale(t);e.eachItemGraphicEl((function(t,e){t.setSymbolScale(n)}))},e.prototype._getNodeGlobalScale=function(t){var e=t.coordinateSystem;if(\"view\"!==e.type)return 1;var n=this._nodeScaleRatio,i=e.scaleX||1;return((e.getZoom()-1)*n+1)/i},e.prototype.dispose=function(){this._controller&&this._controller.dispose(),this._controllerHost=null},e.prototype.remove=function(){this._mainGroup.removeAll(),this._data=null},e.type=\"tree\",e}(Tf);function EI(t,e){var n=t.getItemLayout(e);return n&&!isNaN(n.x)&&!isNaN(n.y)}function VI(t,e,n,i,r){var o=!n,a=t.tree.getNodeByDataIndex(e),s=a.getModel(),l=a.getVisual(\"style\").fill,u=!1===a.isExpand&&0!==a.children.length?l:\"#fff\",h=t.tree.root,c=a.parentNode===h?a:a.parentNode||a,p=t.getItemGraphicEl(c.dataIndex),d=c.getLayout(),f=p?{x:p.__oldX,y:p.__oldY,rawX:p.__radialOldRawX,rawY:p.__radialOldRawY}:d,g=a.getLayout();o?((n=new dw(t,e,null,{symbolInnerColor:u,useNameLabel:!0})).x=f.x,n.y=f.y):n.updateData(t,e,null,{symbolInnerColor:u,useNameLabel:!0}),n.__radialOldRawX=n.__radialRawX,n.__radialOldRawY=n.__radialRawY,n.__radialRawX=g.rawX,n.__radialRawY=g.rawY,i.add(n),t.setItemGraphicEl(e,n),n.__oldX=n.x,n.__oldY=n.y,Hu(n,{x:g.x,y:g.y},r);var y=n.getSymbolPath();if(\"radial\"===r.get(\"layout\")){var v=h.children[0],m=v.getLayout(),_=v.children.length,x=void 0,b=void 0;if(g.x===m.x&&!0===a.isExpand){var w={x:(v.children[0].getLayout().x+v.children[_-1].getLayout().x)/2,y:(v.children[0].getLayout().y+v.children[_-1].getLayout().y)/2};(x=Math.atan2(w.y-m.y,w.x-m.x))<0&&(x=2*Math.PI+x),(b=w.x<m.x)&&(x-=Math.PI)}else(x=Math.atan2(g.y-m.y,g.x-m.x))<0&&(x=2*Math.PI+x),0===a.children.length||0!==a.children.length&&!1===a.isExpand?(b=g.x<m.x)&&(x-=Math.PI):(b=g.x>m.x)||(x-=Math.PI);var S=b?\"left\":\"right\",M=s.getModel(\"label\"),I=M.get(\"rotate\"),C=I*(Math.PI/180),D=y.getTextContent();D&&(y.setTextConfig({position:M.get(\"position\")||S,rotation:null==I?-x:C,origin:\"center\"}),D.setStyle(\"verticalAlign\",\"middle\"))}var A=s.get([\"emphasis\",\"focus\"]),L=\"ancestor\"===A?a.getAncestorsIndices():\"descendant\"===A?a.getDescendantIndices():null;L&&(_s(n).focus=L),function(t,e,n,i,r,o,a,s){var l=e.getModel(),u=t.get(\"edgeShape\"),h=t.get(\"layout\"),c=t.getOrient(),p=t.get([\"lineStyle\",\"curveness\"]),d=t.get(\"edgeForkPosition\"),f=l.getModel(\"lineStyle\").getLineStyle(),g=i.__edge;if(\"curve\"===u)e.parentNode&&e.parentNode!==n&&(g||(g=i.__edge=new du({shape:FI(h,c,p,r,r)})),Hu(g,{shape:FI(h,c,p,o,a)},t));else if(\"polyline\"===u)if(\"orthogonal\"===h){if(e!==n&&e.children&&0!==e.children.length&&!0===e.isExpand){for(var y=e.children,v=[],m=0;m<y.length;m++){var _=y[m].getLayout();v.push([_.x,_.y])}g||(g=i.__edge=new NI({shape:{parentPoint:[a.x,a.y],childPoints:[[a.x,a.y]],orient:c,forkPosition:d}})),Hu(g,{shape:{parentPoint:[a.x,a.y],childPoints:v}},t)}}else 0;g&&(g.useStyle(T({strokeNoScale:!0,fill:null},f)),cl(g,l,\"lineStyle\"),Xs(g),s.add(g))}(r,a,h,n,f,d,g,i),n.__edge&&(n.onHoverStateChange=function(e){if(\"blur\"!==e){var i=a.parentNode&&t.getItemGraphicEl(a.parentNode.dataIndex);i&&1===i.hoverState||Ws(n.__edge,e)}})}function BI(t,e,n,i,r){for(var o,a=t.tree.getNodeByDataIndex(e),s=t.tree.root,l=a.parentNode===s?a:a.parentNode||a;null==(o=l.getLayout());)l=l.parentNode===s?l:l.parentNode||l;var u={duration:r.get(\"animationDurationUpdate\"),easing:r.get(\"animationEasingUpdate\")};Uu(n,{x:o.x+1,y:o.y+1},r,{cb:function(){i.remove(n),t.setItemGraphicEl(e,null)},removeOpt:u}),n.fadeOut(null,{fadeLabel:!0,animation:u});var h=t.getItemGraphicEl(l.dataIndex).__edge,c=n.__edge||(!1===l.isExpand||1===l.children.length?h:void 0),p=r.get(\"edgeShape\"),d=r.get(\"layout\"),f=r.get(\"orient\"),g=r.get([\"lineStyle\",\"curveness\"]);c&&(\"curve\"===p?Uu(c,{shape:FI(d,f,g,o,o),style:{opacity:0}},r,{cb:function(){i.remove(c)},removeOpt:u}):\"polyline\"===p&&\"orthogonal\"===r.get(\"layout\")&&Uu(c,{shape:{parentPoint:[o.x,o.y],childPoints:[[o.x,o.y]]},style:{opacity:0}},r,{cb:function(){i.remove(c)},removeOpt:u}))}function FI(t,e,n,i,r){var o,a,s,l,u,h,c,p;if(\"radial\"===t){u=i.rawX,c=i.rawY,h=r.rawX,p=r.rawY;var d=DI(u,c),f=DI(u,c+(p-c)*n),g=DI(h,p+(c-p)*n),y=DI(h,p);return{x1:d.x||0,y1:d.y||0,x2:y.x||0,y2:y.y||0,cpx1:f.x||0,cpy1:f.y||0,cpx2:g.x||0,cpy2:g.y||0}}return u=i.x,c=i.y,h=r.x,p=r.y,\"LR\"!==e&&\"RL\"!==e||(o=u+(h-u)*n,a=c,s=h+(u-h)*n,l=p),\"TB\"!==e&&\"BT\"!==e||(o=u,a=c+(p-c)*n,s=h,l=p+(c-p)*n),{x1:u,y1:c,x2:h,y2:p,cpx1:o,cpy1:a,cpx2:s,cpy2:l}}var GI=kr();function HI(t){var e=t.mainData,n=t.datas;n||(n={main:e},t.datasAttr={main:\"data\"}),t.datas=t.mainData=null,jI(e,n,t),P(n,(function(n){P(e.TRANSFERABLE_METHODS,(function(e){n.wrapMethod(e,B(WI,t))}))})),e.wrapMethod(\"cloneShallow\",B(XI,t)),P(e.CHANGABLE_METHODS,(function(n){e.wrapMethod(n,B(UI,t))})),rt(n[e.dataType]===e)}function WI(t,e){if(GI(i=this).mainData===i){var n=I({},GI(this).datas);n[this.dataType]=e,jI(e,n,t)}else qI(e,this.dataType,GI(this).mainData,t);var i;return e}function UI(t,e){return t.struct&&t.struct.update(),e}function XI(t,e){return P(GI(e).datas,(function(n,i){n!==e&&qI(n.cloneShallow(),i,e,t)})),e}function YI(t){var e=GI(this).mainData;return null==t||null==e?e:GI(e).datas[t]}function ZI(){var t=GI(this).mainData;return null==t?[{data:t}]:O(E(GI(t).datas),(function(e){return{type:e,data:GI(t).datas[e]}}))}function jI(t,e,n){GI(t).datas={},P(e,(function(e,i){qI(e,i,t,n)}))}function qI(t,e,n,i){GI(n).datas[e]=t,GI(t).mainData=n,t.dataType=e,i.struct&&(t[i.structAttr]=i.struct,i.struct[i.datasAttr[e]]=t),t.getLinkedData=YI,t.getLinkedDataAll=ZI}var KI=function(){function t(t,e){this.depth=0,this.height=0,this.dataIndex=-1,this.children=[],this.viewChildren=[],this.isExpand=!1,this.name=t||\"\",this.hostTree=e}return t.prototype.isRemoved=function(){return this.dataIndex<0},t.prototype.eachNode=function(t,e,n){\"function\"==typeof t&&(n=e,e=t,t=null),H(t=t||{})&&(t={order:t});var i,r=t.order||\"preorder\",o=this[t.attr||\"children\"];\"preorder\"===r&&(i=e.call(n,this));for(var a=0;!i&&a<o.length;a++)o[a].eachNode(t,e,n);\"postorder\"===r&&e.call(n,this)},t.prototype.updateDepthAndHeight=function(t){var e=0;this.depth=t;for(var n=0;n<this.children.length;n++){var i=this.children[n];i.updateDepthAndHeight(t+1),i.height>e&&(e=i.height)}this.height=e+1},t.prototype.getNodeById=function(t){if(this.getId()===t)return this;for(var e=0,n=this.children,i=n.length;e<i;e++){var r=n[e].getNodeById(t);if(r)return r}},t.prototype.contains=function(t){if(t===this)return!0;for(var e=0,n=this.children,i=n.length;e<i;e++){var r=n[e].contains(t);if(r)return r}},t.prototype.getAncestors=function(t){for(var e=[],n=t?this:this.parentNode;n;)e.push(n),n=n.parentNode;return e.reverse(),e},t.prototype.getAncestorsIndices=function(){for(var t=[],e=this;e;)t.push(e.dataIndex),e=e.parentNode;return t.reverse(),t},t.prototype.getDescendantIndices=function(){var t=[];return this.eachNode((function(e){t.push(e.dataIndex)})),t},t.prototype.getValue=function(t){var e=this.hostTree.data;return e.get(e.getDimension(t||\"value\"),this.dataIndex)},t.prototype.setLayout=function(t,e){this.dataIndex>=0&&this.hostTree.data.setItemLayout(this.dataIndex,t,e)},t.prototype.getLayout=function(){return this.hostTree.data.getItemLayout(this.dataIndex)},t.prototype.getModel=function(t){if(!(this.dataIndex<0))return this.hostTree.data.getItemModel(this.dataIndex).getModel(t)},t.prototype.getLevelModel=function(){return(this.hostTree.levelModels||[])[this.depth]},t.prototype.setVisual=function(t,e){this.dataIndex>=0&&this.hostTree.data.setItemVisual(this.dataIndex,t,e)},t.prototype.getVisual=function(t){return this.hostTree.data.getItemVisual(this.dataIndex,t)},t.prototype.getRawIndex=function(){return this.hostTree.data.getRawIndex(this.dataIndex)},t.prototype.getId=function(){return this.hostTree.data.getId(this.dataIndex)},t.prototype.isAncestorOf=function(t){for(var e=t.parentNode;e;){if(e===this)return!0;e=e.parentNode}return!1},t.prototype.isDescendantOf=function(t){return t!==this&&t.isAncestorOf(this)},t}(),$I=function(){function t(t){this.type=\"tree\",this._nodes=[],this.hostModel=t}return t.prototype.eachNode=function(t,e,n){this.root.eachNode(t,e,n)},t.prototype.getNodeByDataIndex=function(t){var e=this.data.getRawIndex(t);return this._nodes[e]},t.prototype.getNodeById=function(t){return this.root.getNodeById(t)},t.prototype.update=function(){for(var t=this.data,e=this._nodes,n=0,i=e.length;n<i;n++)e[n].dataIndex=-1;for(n=0,i=t.count();n<i;n++)e[t.getRawIndex(n)].dataIndex=n},t.prototype.clearLayouts=function(){this.data.clearItemLayouts()},t.createTree=function(e,n,i){var r=new t(n),o=[],a=1;!function t(e,n){var i=e.value;a=Math.max(a,F(i)?i.length:1),o.push(e);var s=new KI(Cr(e.name,\"\"),r);n?function(t,e){var n=e.children;if(t.parentNode===e)return;n.push(t),t.parentNode=e}(s,n):r.root=s,r._nodes.push(s);var l=e.children;if(l)for(var u=0;u<l.length;u++)t(l[u],s)}(e),r.root.updateDepthAndHeight(0);var s=O_(o,{coordDimensions:[\"value\"],dimensionsCount:a}),l=new L_(s,n);return l.initData(o),i&&i(l),HI({mainData:l,struct:r,structAttr:\"tree\"}),r.update(),r},t}();function JI(t,e,n){if(t&&D(e,t.type)>=0){var i=n.getData().tree.root,r=t.targetNode;if(\"string\"==typeof r&&(r=i.getNodeById(r)),r&&i.contains(r))return{node:r};var o=t.targetNodeId;if(null!=o&&(r=i.getNodeById(o)))return{node:r}}}function QI(t){for(var e=[];t;)(t=t.parentNode)&&e.push(t);return e.reverse()}function tT(t,e){return D(QI(t),e)>=0}function eT(t,e){for(var n=[];t;){var i=t.dataIndex;n.push({name:t.name,dataIndex:i,value:e.getRawValue(i)}),t=t.parentNode}return n.reverse(),n}var nT=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.hasSymbolVisual=!0,e.ignoreStyleOnData=!0,e}return n(e,t),e.prototype.getInitialData=function(t){var e={name:t.name,children:t.data},n=t.leaves||{},i=new Oh(n,this,this.ecModel),r=$I.createTree(e,this,(function(t){t.wrapMethod(\"getItemModel\",(function(t,e){var n=r.getNodeByDataIndex(e);return n&&n.children.length&&n.isExpand||(t.parentModel=i),t}))}));var o=0;r.eachNode(\"preorder\",(function(t){t.depth>o&&(o=t.depth)}));var a=t.expandAndCollapse&&t.initialTreeDepth>=0?t.initialTreeDepth:o;return r.root.eachNode(\"preorder\",(function(t){var e=t.hostTree.data.getRawDataItem(t.dataIndex);t.isExpand=e&&null!=e.collapsed?!e.collapsed:t.depth<=a})),r.data},e.prototype.getOrient=function(){var t=this.get(\"orient\");return\"horizontal\"===t?t=\"LR\":\"vertical\"===t&&(t=\"TB\"),t},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.formatTooltip=function(t,e,n){for(var i=this.getData().tree,r=i.root.children[0],o=i.getNodeByDataIndex(t),a=o.getValue(),s=o.name;o&&o!==r;)s=o.parentNode.name+\".\"+s,o=o.parentNode;return tf(\"nameValue\",{name:s,value:a,noValue:isNaN(a)||null==a})},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treeAncestors=eT(i,this),n},e.type=\"series.tree\",e.layoutMode=\"box\",e.defaultOption={zlevel:0,z:2,coordinateSystem:\"view\",left:\"12%\",top:\"12%\",right:\"12%\",bottom:\"12%\",layout:\"orthogonal\",edgeShape:\"curve\",edgeForkPosition:\"50%\",roam:!1,nodeScaleRatio:.4,center:null,zoom:1,orient:\"LR\",symbol:\"emptyCircle\",symbolSize:7,expandAndCollapse:!0,initialTreeDepth:2,lineStyle:{color:\"#ccc\",width:1.5,curveness:.5},itemStyle:{color:\"lightsteelblue\",borderWidth:1.5},label:{show:!0},animationEasing:\"linear\",animationDuration:700,animationDurationUpdate:500},e}(ff);function iT(t,e){for(var n,i=[t];n=i.pop();)if(e(n),n.isExpand){var r=n.children;if(r.length)for(var o=r.length-1;o>=0;o--)i.push(r[o])}}function rT(t,e){t.eachSeriesByType(\"tree\",(function(t){!function(t,e){var n=function(t,e){return Vc(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e);t.layoutInfo=n;var i=t.get(\"layout\"),r=0,o=0,a=null;\"radial\"===i?(r=2*Math.PI,o=Math.min(n.height,n.width)/2,a=CI((function(t,e){return(t.parentNode===e.parentNode?1:2)/t.depth}))):(r=n.width,o=n.height,a=CI());var s=t.getData().tree.root,l=s.children[0];if(l){!function(t){var e=t;e.hierNode={defaultAncestor:null,ancestor:e,prelim:0,modifier:0,change:0,shift:0,i:0,thread:null};for(var n,i,r=[e];n=r.pop();)if(i=n.children,n.isExpand&&i.length)for(var o=i.length-1;o>=0;o--){var a=i[o];a.hierNode={defaultAncestor:null,ancestor:a,prelim:0,modifier:0,change:0,shift:0,i:o,thread:null},r.push(a)}}(s),function(t,e,n){for(var i,r=[t],o=[];i=r.pop();)if(o.push(i),i.isExpand){var a=i.children;if(a.length)for(var s=0;s<a.length;s++)r.push(a[s])}for(;i=o.pop();)e(i,n)}(l,II,a),s.hierNode.modifier=-l.hierNode.prelim,iT(l,TI);var u=l,h=l,c=l;iT(l,(function(t){var e=t.getLayout().x;e<u.getLayout().x&&(u=t),e>h.getLayout().x&&(h=t),t.depth>c.depth&&(c=t)}));var p=u===h?1:a(u,h)/2,d=p-u.getLayout().x,f=0,g=0,y=0,v=0;if(\"radial\"===i)f=r/(h.getLayout().x+p+d),g=o/(c.depth-1||1),iT(l,(function(t){y=(t.getLayout().x+d)*f,v=(t.depth-1)*g;var e=DI(y,v);t.setLayout({x:e.x,y:e.y,rawX:y,rawY:v},!0)}));else{var m=t.getOrient();\"RL\"===m||\"LR\"===m?(g=o/(h.getLayout().x+p+d),f=r/(c.depth-1||1),iT(l,(function(t){v=(t.getLayout().x+d)*g,y=\"LR\"===m?(t.depth-1)*f:r-(t.depth-1)*f,t.setLayout({x:y,y:v},!0)}))):\"TB\"!==m&&\"BT\"!==m||(f=r/(h.getLayout().x+p+d),g=o/(c.depth-1||1),iT(l,(function(t){y=(t.getLayout().x+d)*f,v=\"TB\"===m?(t.depth-1)*g:o-(t.depth-1)*g,t.setLayout({x:y,y:v},!0)})))}}}(t,e)}))}function oT(t){t.eachSeriesByType(\"tree\",(function(t){var e=t.getData();e.tree.eachNode((function(t){var n=t.getModel().getModel(\"itemStyle\").getItemStyle();I(e.ensureUniqueItemVisual(t.dataIndex,\"style\"),n)}))}))}var aT=function(){},sT=[\"treemapZoomToNode\",\"treemapRender\",\"treemapMove\"];function lT(t){var e=t.getData().tree,n={};e.eachNode((function(e){for(var i=e;i&&i.depth>1;)i=i.parentNode;var r=xp(t.ecModel,i.name||i.dataIndex+\"\",n);e.setVisual(\"decal\",r)}))}var uT=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.preventUsingHoverLayer=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n={name:t.name,children:t.data};hT(n);var i=t.levels||[],r=this.designatedVisualItemStyle={},o=new Oh({itemStyle:r},this,e),a=O((i=t.levels=function(t,e){var n,i,r=xr(e.get(\"color\")),o=xr(e.get([\"aria\",\"decal\",\"decals\"]));if(!r)return;P(t=t||[],(function(t){var e=new Oh(t),r=e.get(\"color\"),o=e.get(\"decal\");(e.get([\"itemStyle\",\"color\"])||r&&\"none\"!==r)&&(n=!0),(e.get([\"itemStyle\",\"decal\"])||o&&\"none\"!==o)&&(i=!0)}));var a=t[0]||(t[0]={});n||(a.color=r.slice());!i&&o&&(a.decal=o.slice());return t}(i,e))||[],(function(t){return new Oh(t,o,e)}),this),s=$I.createTree(n,this,(function(t){t.wrapMethod(\"getItemModel\",(function(t,e){var n=s.getNodeByDataIndex(e),i=n?a[n.depth]:null;return t.parentModel=i||o,t}))}));return s.data},e.prototype.optionUpdated=function(){this.resetViewRoot()},e.prototype.formatTooltip=function(t,e,n){var i=this.getData(),r=this.getRawValue(t);return tf(\"nameValue\",{name:i.getName(t),value:r})},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treePathInfo=eT(i,this),n},e.prototype.setLayoutInfo=function(t){this.layoutInfo=this.layoutInfo||{},I(this.layoutInfo,t)},e.prototype.mapIdToIndex=function(t){var e=this._idIndexMap;e||(e=this._idIndexMap=ht(),this._idIndexMapCount=0);var n=e.get(t);return null==n&&e.set(t,n=this._idIndexMapCount++),n},e.prototype.getViewRoot=function(){return this._viewRoot},e.prototype.resetViewRoot=function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)},e.prototype.enableAriaDecal=function(){lT(this)},e.type=\"series.treemap\",e.layoutMode=\"box\",e.defaultOption={progressive:0,left:\"center\",top:\"middle\",width:\"80%\",height:\"80%\",sort:!0,clipWindow:\"origin\",squareRatio:.5*(1+Math.sqrt(5)),leafDepth:null,drillDownIcon:\"▶\",zoomToNodeRatio:.1024,roam:!0,nodeClick:\"zoomToNode\",animation:!0,animationDurationUpdate:900,animationEasing:\"quinticInOut\",breadcrumb:{show:!0,height:22,left:\"center\",top:\"bottom\",emptyItemWidth:25,itemStyle:{color:\"rgba(0,0,0,0.7)\",textStyle:{color:\"#fff\"}}},label:{show:!0,distance:0,padding:5,position:\"inside\",color:\"#fff\",overflow:\"truncate\"},upperLabel:{show:!1,position:[0,\"50%\"],height:20,overflow:\"truncate\",verticalAlign:\"middle\"},itemStyle:{color:null,colorAlpha:null,colorSaturation:null,borderWidth:0,gapWidth:0,borderColor:\"#fff\",borderColorSaturation:null},emphasis:{upperLabel:{show:!0,position:[0,\"50%\"],ellipsis:!0,verticalAlign:\"middle\"}},visualDimension:0,visualMin:null,visualMax:null,color:[],colorAlpha:null,colorSaturation:null,colorMappingBy:\"index\",visibleMin:10,childrenVisibleMin:null,levels:[]},e}(ff);function hT(t){var e=0;P(t.children,(function(t){hT(t);var n=t.value;F(n)&&(n=n[0]),e+=n}));var n=t.value;F(n)&&(n=n[0]),(null==n||isNaN(n))&&(n=e),n<0&&(n=0),F(t.value)?t.value[0]=n:t.value=n}var cT=function(){function t(t){this.group=new Ei,t.add(this.group)}return t.prototype.render=function(t,e,n,i){var r=t.getModel(\"breadcrumb\"),o=this.group;if(o.removeAll(),r.get(\"show\")&&n){var a=r.getModel(\"itemStyle\"),s=a.getModel(\"textStyle\"),l={pos:{left:r.get(\"left\"),right:r.get(\"right\"),top:r.get(\"top\"),bottom:r.get(\"bottom\")},box:{width:e.getWidth(),height:e.getHeight()},emptyItemWidth:r.get(\"emptyItemWidth\"),totalWidth:0,renderList:[]};this._prepare(n,l,s),this._renderContent(t,l,a,s,i),Bc(o,l.pos,l.box)}},t.prototype._prepare=function(t,e,n){for(var i=t;i;i=i.parentNode){var r=Cr(i.getModel().get(\"name\"),\"\"),o=n.getTextRect(r),a=Math.max(o.width+16,e.emptyItemWidth);e.totalWidth+=a+8,e.renderList.push({node:i,text:r,width:a})}},t.prototype._renderContent=function(t,e,n,i,r){for(var o,a,s,l,u,h,c,p,d,f=0,g=e.emptyItemWidth,y=t.get([\"breadcrumb\",\"height\"]),v=(o=e.pos,a=e.box,l=a.width,u=a.height,h=Zi(o.left,l),c=Zi(o.top,u),p=Zi(o.right,l),d=Zi(o.bottom,u),(isNaN(h)||isNaN(parseFloat(o.left)))&&(h=0),(isNaN(p)||isNaN(parseFloat(o.right)))&&(p=l),(isNaN(c)||isNaN(parseFloat(o.top)))&&(c=0),(isNaN(d)||isNaN(parseFloat(o.bottom)))&&(d=u),s=wc(s||0),{width:Math.max(p-h-s[1]-s[3],0),height:Math.max(d-c-s[0]-s[2],0)}),m=e.totalWidth,_=e.renderList,x=_.length-1;x>=0;x--){var b=_[x],w=b.node,S=b.width,M=b.text;m>v.width&&(m-=S-g,S=g,M=null);var I=new ru({shape:{points:pT(f,0,S,y,x===_.length-1,0===x)},style:T(n.getItemStyle(),{lineJoin:\"bevel\"}),textContent:new cs({style:{text:M,fill:i.getTextColor(),font:i.getFont()}}),textConfig:{position:\"inside\"},z2:1e5,onclick:B(r,w)});I.disableLabelAnimation=!0,this.group.add(I),dT(I,t,w),f+=S+8}},t.prototype.remove=function(){this.group.removeAll()},t}();function pT(t,e,n,i,r,o){var a=[[r?t:t-5,e],[t+n,e],[t+n,e+i],[r?t:t-5,e+i]];return!o&&a.splice(2,0,[t+n+5,e+i/2]),!r&&a.push([t,e+i/2]),a}function dT(t,e,n){_s(t).eventData={componentType:\"series\",componentSubType:\"treemap\",componentIndex:e.componentIndex,seriesIndex:e.componentIndex,seriesName:e.name,seriesType:\"treemap\",selfType:\"breadcrumb\",nodeData:{dataIndex:n&&n.dataIndex,name:n&&n.name},treePathInfo:n&&eT(n,e)}}var fT=function(){function t(){this._storage=[],this._elExistsMap={}}return t.prototype.add=function(t,e,n,i,r){return!this._elExistsMap[t.id]&&(this._elExistsMap[t.id]=!0,this._storage.push({el:t,target:e,duration:n,delay:i,easing:r}),!0)},t.prototype.finished=function(t){return this._finishedCallback=t,this},t.prototype.start=function(){for(var t=this,e=this._storage.length,n=function(){--e<=0&&(t._storage.length=0,t._elExistsMap={},t._finishedCallback&&t._finishedCallback())},i=0,r=this._storage.length;i<r;i++){var o=this._storage[i];o.el.animateTo(o.target,{duration:o.duration,delay:o.delay,easing:o.easing,setToFinal:!0,done:n,aborted:n})}return this},t}();var gT=Ei,yT=ls,vT=\"label\",mT=\"upperLabel\",_T=$r([[\"fill\",\"color\"],[\"stroke\",\"strokeColor\"],[\"lineWidth\",\"strokeWidth\"],[\"shadowBlur\"],[\"shadowOffsetX\"],[\"shadowOffsetY\"],[\"shadowColor\"]]),xT=function(t){var e=_T(t);return e.stroke=e.fill=e.lineWidth=null,e},bT=kr(),wT=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._state=\"ready\",n._storage={nodeGroup:[],background:[],content:[]},n}return n(e,t),e.prototype.render=function(t,e,n,i){if(!(D(e.findComponents({mainType:\"series\",subType:\"treemap\",query:i}),t)<0)){this.seriesModel=t,this.api=n,this.ecModel=e;var r=JI(i,[\"treemapZoomToNode\",\"treemapRootToNode\"],t),o=i&&i.type,a=t.layoutInfo,s=!this._oldTree,l=this._storage,u=\"treemapRootToNode\"===o&&r&&l?{rootNodeGroup:l.nodeGroup[r.node.getRawIndex()],direction:i.direction}:null,h=this._giveContainerGroup(a),c=this._doRender(h,t,u);s||o&&\"treemapZoomToNode\"!==o&&\"treemapRootToNode\"!==o?c.renderFinally():this._doAnimation(h,c,t,u),this._resetController(n),this._renderBreadcrumb(t,n,r)}},e.prototype._giveContainerGroup=function(t){var e=this._containerGroup;return e||(e=this._containerGroup=new gT,this._initEvents(e),this.group.add(e)),e.x=t.x,e.y=t.y,e},e.prototype._doRender=function(t,e,n){var i=e.getData().tree,r=this._oldTree,o={nodeGroup:[],background:[],content:[]},a={nodeGroup:[],background:[],content:[]},s=this._storage,l=[];function u(t,i,r,u){return function(t,e,n,i,r,o,a,s,l,u){if(!a)return;var h=a.getLayout(),c=t.getData(),p=a.getModel();if(c.setItemGraphicEl(a.dataIndex,null),!h||!h.isInView)return;var d=h.width,f=h.height,g=h.borderWidth,y=h.invisible,v=a.getRawIndex(),m=s&&s.getRawIndex(),_=a.viewChildren,x=h.upperHeight,b=_&&_.length,w=p.getModel(\"itemStyle\"),S=p.getModel([\"emphasis\",\"itemStyle\"]),M=p.getModel([\"blur\",\"itemStyle\"]),T=p.getModel([\"select\",\"itemStyle\"]),C=w.get(\"borderRadius\")||0,D=B(\"nodeGroup\",gT);if(!D)return;if(l.add(D),D.x=h.x||0,D.y=h.y||0,D.markRedraw(),bT(D).nodeWidth=d,bT(D).nodeHeight=f,h.isAboveViewRoot)return D;var A=B(\"background\",yT,u,20);A&&R(D,A,b&&h.upperLabelHeight);var L=p.get([\"emphasis\",\"focus\"]),k=p.get([\"emphasis\",\"blurScope\"]),P=\"ancestor\"===L?a.getAncestorsIndices():\"descendant\"===L?a.getDescendantIndices():L;if(b)dl(D)&&pl(D,!1),A&&(pl(A,!0),c.setItemGraphicEl(a.dataIndex,A),ll(A,P,k));else{var O=B(\"content\",yT,u,30);O&&N(D,O),A&&dl(A)&&pl(A,!1),pl(D,!0),c.setItemGraphicEl(a.dataIndex,D),ll(D,P,k)}return D;function R(e,n,i){var r=_s(n);if(r.dataIndex=a.dataIndex,r.seriesIndex=t.seriesIndex,n.setShape({x:0,y:0,width:d,height:f,r:C}),y)z(n);else{n.invisible=!1;var o=a.getVisual(\"style\"),s=o.stroke,l=xT(w);l.fill=s;var u=_T(S);u.fill=S.get(\"borderColor\");var h=_T(M);h.fill=M.get(\"borderColor\");var c=_T(T);if(c.fill=T.get(\"borderColor\"),i){var p=d-2*g;E(n,s,o.opacity,{x:g,y:0,width:p,height:x})}else n.removeTextContent();n.setStyle(l),n.ensureState(\"emphasis\").style=u,n.ensureState(\"blur\").style=h,n.ensureState(\"select\").style=c,Xs(n)}e.add(n)}function N(e,n){var i=_s(n);i.dataIndex=a.dataIndex,i.seriesIndex=t.seriesIndex;var r=Math.max(d-2*g,0),o=Math.max(f-2*g,0);if(n.culling=!0,n.setShape({x:g,y:g,width:r,height:o,r:C}),y)z(n);else{n.invisible=!1;var s=a.getVisual(\"style\"),l=s.fill,u=xT(w);u.fill=l,u.decal=s.decal;var h=_T(S),c=_T(M),p=_T(T);E(n,l,s.opacity,null),n.setStyle(u),n.ensureState(\"emphasis\").style=h,n.ensureState(\"blur\").style=c,n.ensureState(\"select\").style=p,Xs(n)}e.add(n)}function z(t){!t.invisible&&o.push(t)}function E(e,n,i,r){var o=p.getModel(r?mT:vT),s=Cr(p.get(\"name\"),null),l=o.getShallow(\"show\");hh(e,ch(p,r?mT:vT),{defaultText:l?s:null,inheritColor:n,defaultOpacity:i,labelFetcher:t,labelDataIndex:a.dataIndex});var u=e.getTextContent(),c=u.style,d=it(c.padding||0);r&&(e.setTextConfig({layoutRect:r}),u.disableLabelLayout=!0),u.beforeUpdate=function(){var t=Math.max((r?r.width:e.shape.width)-d[1]-d[3],0),n=Math.max((r?r.height:e.shape.height)-d[0]-d[2],0);c.width===t&&c.height===n||u.setStyle({width:t,height:n})},c.truncateMinChar=2,c.lineOverflow=\"truncate\",V(c,r,h);var f=u.getState(\"emphasis\");V(f?f.style:null,r,h)}function V(e,n,i){var r=e?e.text:null;if(!n&&i.isLeafRoot&&null!=r){var o=t.get(\"drillDownIcon\",!0);e.text=o?o+\" \"+r:r}}function B(t,i,o,a){var s=null!=m&&n[t][m],l=r[t];return s?(n[t][m]=null,F(l,s)):y||((s=new i)instanceof So&&(s.z2=function(t,e){return 100*t+e}(o,a)),G(l,s)),e[t][v]=s}function F(t,e){var n=t[v]={};e instanceof gT?(n.oldX=e.x,n.oldY=e.y):n.oldShape=I({},e.shape)}function G(t,e){var n=t[v]={},o=a.parentNode,s=e instanceof Ei;if(o&&(!i||\"drillDown\"===i.direction)){var l=0,u=0,h=r.background[o.getRawIndex()];!i&&h&&h.oldShape&&(l=h.oldShape.width,u=h.oldShape.height),s?(n.oldX=0,n.oldY=u):n.oldShape={x:l,y:u,width:0,height:0}}n.fadein=!s}}(e,a,s,n,o,l,t,i,r,u)}!function t(e,n,i,r,o){r?(n=e,P(e,(function(t,e){!t.isRemoved()&&s(e,e)}))):new n_(n,e,a,a).add(s).update(s).remove(B(s,null)).execute();function a(t){return t.getId()}function s(a,s){var l=null!=a?e[a]:null,h=null!=s?n[s]:null,c=u(l,h,i,o);c&&t(l&&l.viewChildren||[],h&&h.viewChildren||[],c,r,o+1)}}(i.root?[i.root]:[],r&&r.root?[r.root]:[],t,i===r||!r,0);var h=function(t){var e={nodeGroup:[],background:[],content:[]};return t&&P(t,(function(t,n){var i=e[n];P(t,(function(t){t&&(i.push(t),bT(t).willDelete=!0)}))})),e}(s);return this._oldTree=i,this._storage=a,{lastsForAnimation:o,willDeleteEls:h,renderFinally:function(){P(h,(function(t){P(t,(function(t){t.parent&&t.parent.remove(t)}))})),P(l,(function(t){t.invisible=!0,t.dirty()}))}}},e.prototype._doAnimation=function(t,e,n,i){if(n.get(\"animation\")){var r=n.get(\"animationDurationUpdate\"),o=n.get(\"animationEasing\"),a=(G(r)?0:r)||0,s=(G(o)?null:o)||\"cubicOut\",l=new fT;P(e.willDeleteEls,(function(t,e){P(t,(function(t,n){if(!t.invisible){var r,o=t.parent,u=bT(o);if(i&&\"drillDown\"===i.direction)r=o===i.rootNodeGroup?{shape:{x:0,y:0,width:u.nodeWidth,height:u.nodeHeight},style:{opacity:0}}:{style:{opacity:0}};else{var h=0,c=0;u.willDelete||(h=u.nodeWidth/2,c=u.nodeHeight/2),r=\"nodeGroup\"===e?{x:h,y:c,style:{opacity:0}}:{shape:{x:h,y:c,width:0,height:0},style:{opacity:0}}}r&&l.add(t,r,a,0,s)}}))})),P(this._storage,(function(t,n){P(t,(function(t,i){var r=e.lastsForAnimation[n][i],o={};r&&(t instanceof Ei?null!=r.oldX&&(o.x=t.x,o.y=t.y,t.x=r.oldX,t.y=r.oldY):(r.oldShape&&(o.shape=I({},t.shape),t.setShape(r.oldShape)),r.fadein?(t.setStyle(\"opacity\",0),o.style={opacity:1}):1!==t.style.opacity&&(o.style={opacity:1})),l.add(t,o,a,0,s))}))}),this),this._state=\"animating\",l.finished(V((function(){this._state=\"ready\",e.renderFinally()}),this)).start()}},e.prototype._resetController=function(t){var e=this._controller;e||((e=this._controller=new WM(t.getZr())).enable(this.seriesModel.get(\"roam\")),e.on(\"pan\",V(this._onPan,this)),e.on(\"zoom\",V(this._onZoom,this)));var n=new gi(0,0,t.getWidth(),t.getHeight());e.setPointerChecker((function(t,e,i){return n.contain(e,i)}))},e.prototype._clearController=function(){var t=this._controller;t&&(t.dispose(),t=null)},e.prototype._onPan=function(t){if(\"animating\"!==this._state&&(Math.abs(t.dx)>3||Math.abs(t.dy)>3)){var e=this.seriesModel.getData().tree.root;if(!e)return;var n=e.getLayout();if(!n)return;this.api.dispatchAction({type:\"treemapMove\",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:n.x+t.dx,y:n.y+t.dy,width:n.width,height:n.height}})}},e.prototype._onZoom=function(t){var e=t.originX,n=t.originY;if(\"animating\"!==this._state){var i=this.seriesModel.getData().tree.root;if(!i)return;var r=i.getLayout();if(!r)return;var o=new gi(r.x,r.y,r.width,r.height),a=this.seriesModel.layoutInfo,s=[1,0,0,1,0,0];Un(s,s,[-(e-=a.x),-(n-=a.y)]),Yn(s,s,[t.scale,t.scale]),Un(s,s,[e,n]),o.applyTransform(s),this.api.dispatchAction({type:\"treemapRender\",from:this.uid,seriesId:this.seriesModel.id,rootRect:{x:o.x,y:o.y,width:o.width,height:o.height}})}},e.prototype._initEvents=function(t){var e=this;t.on(\"click\",(function(t){if(\"ready\"===e._state){var n=e.seriesModel.get(\"nodeClick\",!0);if(n){var i=e.findTarget(t.offsetX,t.offsetY);if(i){var r=i.node;if(r.getLayout().isLeafRoot)e._rootToNode(i);else if(\"zoomToNode\"===n)e._zoomToNode(i);else if(\"link\"===n){var o=r.hostTree.data.getItemModel(r.dataIndex),a=o.get(\"link\",!0),s=o.get(\"target\",!0)||\"blank\";a&&Pc(a,s)}}}}}),this)},e.prototype._renderBreadcrumb=function(t,e,n){var i=this;n||(n=null!=t.get(\"leafDepth\",!0)?{node:t.getViewRoot()}:this.findTarget(e.getWidth()/2,e.getHeight()/2))||(n={node:t.getData().tree.root}),(this._breadcrumb||(this._breadcrumb=new cT(this.group))).render(t,e,n.node,(function(e){\"animating\"!==i._state&&(tT(t.getViewRoot(),e)?i._rootToNode({node:e}):i._zoomToNode({node:e}))}))},e.prototype.remove=function(){this._clearController(),this._containerGroup&&this._containerGroup.removeAll(),this._storage={nodeGroup:[],background:[],content:[]},this._state=\"ready\",this._breadcrumb&&this._breadcrumb.remove()},e.prototype.dispose=function(){this._clearController()},e.prototype._zoomToNode=function(t){this.api.dispatchAction({type:\"treemapZoomToNode\",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},e.prototype._rootToNode=function(t){this.api.dispatchAction({type:\"treemapRootToNode\",from:this.uid,seriesId:this.seriesModel.id,targetNode:t.node})},e.prototype.findTarget=function(t,e){var n;return this.seriesModel.getViewRoot().eachNode({attr:\"viewChildren\",order:\"preorder\"},(function(i){var r=this._storage.background[i.getRawIndex()];if(r){var o=r.transformCoordToLocal(t,e),a=r.shape;if(!(a.x<=o[0]&&o[0]<=a.x+a.width&&a.y<=o[1]&&o[1]<=a.y+a.height))return!1;n={node:i,offsetX:o[0],offsetY:o[1]}}}),this),n},e.type=\"treemap\",e}(Tf);var ST=P,MT=X,IT=-1,TT=function(){function t(e){var n=e.mappingMethod,i=e.type,r=this.option=w(e);this.type=i,this.mappingMethod=n,this._normalizeData=zT[n];var o=t.visualHandlers[i];this.applyVisual=o.applyVisual,this.getColorMapper=o.getColorMapper,this._normalizedToVisual=o._normalizedToVisual[n],\"piecewise\"===n?(CT(r),function(t){var e=t.pieceList;t.hasSpecialVisual=!1,P(e,(function(e,n){e.originIndex=n,null!=e.visual&&(t.hasSpecialVisual=!0)}))}(r)):\"category\"===n?r.categories?function(t){var e=t.categories,n=t.categoryMap={},i=t.visual;if(ST(e,(function(t,e){n[t]=e})),!F(i)){var r=[];X(i)?ST(i,(function(t,e){var i=n[e];r[null!=i?i:IT]=t})):r[-1]=i,i=NT(t,r)}for(var o=e.length-1;o>=0;o--)null==i[o]&&(delete n[e[o]],e.pop())}(r):CT(r,!0):(rt(\"linear\"!==n||r.dataExtent),CT(r))}return t.prototype.mapValueToVisual=function(t){var e=this._normalizeData(t);return this._normalizedToVisual(e,t)},t.prototype.getNormalizer=function(){return V(this._normalizeData,this)},t.listVisualTypes=function(){return E(t.visualHandlers)},t.isValidType=function(e){return t.visualHandlers.hasOwnProperty(e)},t.eachVisual=function(t,e,n){X(t)?P(t,e,n):e.call(n,t)},t.mapVisual=function(e,n,i){var r,o=F(e)?[]:X(e)?{}:(r=!0,null);return t.eachVisual(e,(function(t,e){var a=n.call(i,t,e);r?o=a:o[e]=a})),o},t.retrieveVisuals=function(e){var n,i={};return e&&ST(t.visualHandlers,(function(t,r){e.hasOwnProperty(r)&&(i[r]=e[r],n=!0)})),n?i:null},t.prepareVisualTypes=function(t){if(F(t))t=t.slice();else{if(!MT(t))return[];var e=[];ST(t,(function(t,n){e.push(n)})),t=e}return t.sort((function(t,e){return\"color\"===e&&\"color\"!==t&&0===t.indexOf(\"color\")?1:-1})),t},t.dependsOn=function(t,e){return\"color\"===e?!(!t||0!==t.indexOf(e)):t===e},t.findPieceIndex=function(t,e,n){for(var i,r=1/0,o=0,a=e.length;o<a;o++){var s=e[o].value;if(null!=s){if(s===t||\"string\"==typeof s&&s===t+\"\")return o;n&&c(s,o)}}for(o=0,a=e.length;o<a;o++){var l=e[o],u=l.interval,h=l.close;if(u){if(u[0]===-1/0){if(ET(h[1],t,u[1]))return o}else if(u[1]===1/0){if(ET(h[0],u[0],t))return o}else if(ET(h[0],u[0],t)&&ET(h[1],t,u[1]))return o;n&&c(u[0],o),n&&c(u[1],o)}}if(n)return t===1/0?e.length-1:t===-1/0?0:i;function c(e,n){var o=Math.abs(e-t);o<r&&(r=o,i=n)}},t.visualHandlers={color:{applyVisual:LT(\"color\"),getColorMapper:function(){var t=this.option;return V(\"category\"===t.mappingMethod?function(t,e){return!e&&(t=this._normalizeData(t)),kT.call(this,t)}:function(e,n,i){var r=!!i;return!n&&(e=this._normalizeData(e)),i=Ye(e,t.parsedVisual,i),r?i:Je(i,\"rgba\")},this)},_normalizedToVisual:{linear:function(t){return Je(Ye(t,this.option.parsedVisual),\"rgba\")},category:kT,piecewise:function(t,e){var n=RT.call(this,e);return null==n&&(n=Je(Ye(t,this.option.parsedVisual),\"rgba\")),n},fixed:PT}},colorHue:DT((function(t,e){return Ke(t,e)})),colorSaturation:DT((function(t,e){return Ke(t,null,e)})),colorLightness:DT((function(t,e){return Ke(t,null,null,e)})),colorAlpha:DT((function(t,e){return $e(t,e)})),decal:{applyVisual:LT(\"decal\"),_normalizedToVisual:{linear:null,category:kT,piecewise:null,fixed:null}},opacity:{applyVisual:LT(\"opacity\"),_normalizedToVisual:OT([0,1])},liftZ:{applyVisual:LT(\"liftZ\"),_normalizedToVisual:{linear:PT,category:PT,piecewise:PT,fixed:PT}},symbol:{applyVisual:function(t,e,n){n(\"symbol\",this.mapValueToVisual(t))},_normalizedToVisual:{linear:AT,category:kT,piecewise:function(t,e){var n=RT.call(this,e);return null==n&&(n=AT.call(this,t)),n},fixed:PT}},symbolSize:{applyVisual:LT(\"symbolSize\"),_normalizedToVisual:OT([0,1])}},t}();function CT(t,e){var n=t.visual,i=[];X(n)?ST(n,(function(t){i.push(t)})):null!=n&&i.push(n);e||1!==i.length||{color:1,symbol:1}.hasOwnProperty(t.type)||(i[1]=i[0]),NT(t,i)}function DT(t){return{applyVisual:function(e,n,i){var r=this.mapValueToVisual(e);i(\"color\",t(n(\"color\"),r))},_normalizedToVisual:OT([0,1])}}function AT(t){var e=this.option.visual;return e[Math.round(Yi(t,[0,1],[0,e.length-1],!0))]||{}}function LT(t){return function(e,n,i){i(t,this.mapValueToVisual(e))}}function kT(t){var e=this.option.visual;return e[this.option.loop&&t!==IT?t%e.length:t]}function PT(){return this.option.visual[0]}function OT(t){return{linear:function(e){return Yi(e,t,this.option.visual,!0)},category:kT,piecewise:function(e,n){var i=RT.call(this,n);return null==i&&(i=Yi(e,t,this.option.visual,!0)),i},fixed:PT}}function RT(t){var e=this.option,n=e.pieceList;if(e.hasSpecialVisual){var i=n[TT.findPieceIndex(t,n)];if(i&&i.visual)return i.visual[this.type]}}function NT(t,e){return t.visual=e,\"color\"===t.type&&(t.parsedVisual=O(e,(function(t){return He(t)}))),e}var zT={linear:function(t){return Yi(t,this.option.dataExtent,[0,1],!0)},piecewise:function(t){var e=this.option.pieceList,n=TT.findPieceIndex(t,e,!0);if(null!=n)return Yi(n,[0,e.length-1],[0,1],!0)},category:function(t){var e=this.option.categories?this.option.categoryMap[t]:t;return null==e?IT:e},fixed:ft};function ET(t,e,n){return t?e<=n:e<n}var VT=kr(),BT={seriesType:\"treemap\",reset:function(t){var e=t.getData().tree.root;e.isRemoved()||FT(e,{},t.getViewRoot().getAncestors(),t)}};function FT(t,e,n,i){var r=t.getModel(),o=t.getLayout(),a=t.hostTree.data;if(o&&!o.invisible&&o.isInView){var s,l=r.getModel(\"itemStyle\"),u=function(t,e,n){var i=I({},e),r=n.designatedVisualItemStyle;return P([\"color\",\"colorAlpha\",\"colorSaturation\"],(function(n){r[n]=e[n];var o=t.get(n);r[n]=null,null!=o&&(i[n]=o)})),i}(l,e,i),h=a.ensureUniqueItemVisual(t.dataIndex,\"style\"),c=l.get(\"borderColor\"),p=l.get(\"borderColorSaturation\");null!=p&&(c=function(t,e){return null!=e?Ke(e,null,null,t):null}(p,s=GT(u))),h.stroke=c;var d=t.viewChildren;if(d&&d.length){var f=function(t,e,n,i,r,o){if(!o||!o.length)return;var a=WT(e,\"color\")||null!=r.color&&\"none\"!==r.color&&(WT(e,\"colorAlpha\")||WT(e,\"colorSaturation\"));if(!a)return;var s=e.get(\"visualMin\"),l=e.get(\"visualMax\"),u=n.dataExtent.slice();null!=s&&s<u[0]&&(u[0]=s),null!=l&&l>u[1]&&(u[1]=l);var h=e.get(\"colorMappingBy\"),c={type:a.name,dataExtent:u,visual:a.range};\"color\"!==c.type||\"index\"!==h&&\"id\"!==h?c.mappingMethod=\"linear\":(c.mappingMethod=\"category\",c.loop=!0);var p=new TT(c);return VT(p).drColorMappingBy=h,p}(0,r,o,0,u,d);P(d,(function(t,e){if(t.depth>=n.length||t===n[t.depth]){var o=function(t,e,n,i,r,o){var a=I({},e);if(r){var s=r.type,l=\"color\"===s&&VT(r).drColorMappingBy,u=\"index\"===l?i:\"id\"===l?o.mapIdToIndex(n.getId()):n.getValue(t.get(\"visualDimension\"));a[s]=r.mapValueToVisual(u)}return a}(r,u,t,e,f,i);FT(t,o,n,i)}}))}else s=GT(u),h.fill=s}}function GT(t){var e=HT(t,\"color\");if(e){var n=HT(t,\"colorAlpha\"),i=HT(t,\"colorSaturation\");return i&&(e=Ke(e,null,null,i)),n&&(e=$e(e,n)),e}}function HT(t,e){var n=t[e];if(null!=n&&\"none\"!==n)return n}function WT(t,e){var n=t.get(e);return F(n)&&n.length?{name:e,range:n}:null}var UT=Math.max,XT=Math.min,YT=Q,ZT=P,jT=[\"itemStyle\",\"borderWidth\"],qT=[\"itemStyle\",\"gapWidth\"],KT=[\"upperLabel\",\"show\"],$T=[\"upperLabel\",\"height\"],JT={seriesType:\"treemap\",reset:function(t,e,n,i){var r=n.getWidth(),o=n.getHeight(),a=t.option,s=Vc(t.getBoxLayoutParams(),{width:n.getWidth(),height:n.getHeight()}),l=a.size||[],u=Zi(YT(s.width,l[0]),r),h=Zi(YT(s.height,l[1]),o),c=i&&i.type,p=JI(i,[\"treemapZoomToNode\",\"treemapRootToNode\"],t),d=\"treemapRender\"===c||\"treemapMove\"===c?i.rootRect:null,f=t.getViewRoot(),g=QI(f);if(\"treemapMove\"!==c){var y=\"treemapZoomToNode\"===c?function(t,e,n,i,r){var o,a=(e||{}).node,s=[i,r];if(!a||a===n)return s;var l=i*r,u=l*t.option.zoomToNodeRatio;for(;o=a.parentNode;){for(var h=0,c=o.children,p=0,d=c.length;p<d;p++)h+=c[p].getValue();var f=a.getValue();if(0===f)return s;u*=h/f;var g=o.getModel(),y=g.get(jT);(u+=4*y*y+(3*y+Math.max(y,iC(g)))*Math.pow(u,.5))>er&&(u=er),a=o}u<l&&(u=l);var v=Math.pow(u/l,.5);return[i*v,r*v]}(t,p,f,u,h):d?[d.width,d.height]:[u,h],v=a.sort;v&&\"asc\"!==v&&\"desc\"!==v&&(v=\"desc\");var m={squareRatio:a.squareRatio,sort:v,leafDepth:a.leafDepth};f.hostTree.clearLayouts();var _={x:0,y:0,width:y[0],height:y[1],area:y[0]*y[1]};f.setLayout(_),QT(f,m,!1,0),_=f.getLayout(),ZT(g,(function(t,e){var n=(g[e+1]||f).getValue();t.setLayout(I({dataExtent:[n,n],borderWidth:0,upperHeight:0},_))}))}var x=t.getData().tree.root;x.setLayout(function(t,e,n){if(e)return{x:e.x,y:e.y};var i={x:0,y:0};if(!n)return i;var r=n.node,o=r.getLayout();if(!o)return i;var a=[o.width/2,o.height/2],s=r;for(;s;){var l=s.getLayout();a[0]+=l.x,a[1]+=l.y,s=s.parentNode}return{x:t.width/2-a[0],y:t.height/2-a[1]}}(s,d,p),!0),t.setLayoutInfo(s),nC(x,new gi(-s.x,-s.y,r,o),g,f,0)}};function QT(t,e,n,i){var r,o;if(!t.isRemoved()){var a=t.getLayout();r=a.width,o=a.height;var s=t.getModel(),l=s.get(jT),u=s.get(qT)/2,h=iC(s),c=Math.max(l,h),p=l-u,d=c-u;t.setLayout({borderWidth:l,upperHeight:c,upperLabelHeight:h},!0);var f=(r=UT(r-2*p,0))*(o=UT(o-p-d,0)),g=function(t,e,n,i,r,o){var a=t.children||[],s=i.sort;\"asc\"!==s&&\"desc\"!==s&&(s=null);var l=null!=i.leafDepth&&i.leafDepth<=o;if(r&&!l)return t.viewChildren=[];!function(t,e){e&&t.sort((function(t,n){var i=\"asc\"===e?t.getValue()-n.getValue():n.getValue()-t.getValue();return 0===i?\"asc\"===e?t.dataIndex-n.dataIndex:n.dataIndex-t.dataIndex:i}))}(a=N(a,(function(t){return!t.isRemoved()})),s);var u=function(t,e,n){for(var i=0,r=0,o=e.length;r<o;r++)i+=e[r].getValue();var a,s=t.get(\"visualDimension\");e&&e.length?\"value\"===s&&n?(a=[e[e.length-1].getValue(),e[0].getValue()],\"asc\"===n&&a.reverse()):(a=[1/0,-1/0],ZT(e,(function(t){var e=t.getValue(s);e<a[0]&&(a[0]=e),e>a[1]&&(a[1]=e)}))):a=[NaN,NaN];return{sum:i,dataExtent:a}}(e,a,s);if(0===u.sum)return t.viewChildren=[];if(u.sum=function(t,e,n,i,r){if(!i)return n;for(var o=t.get(\"visibleMin\"),a=r.length,s=a,l=a-1;l>=0;l--){var u=r[\"asc\"===i?a-l-1:l].getValue();u/n*e<o&&(s=l,n-=u)}return\"asc\"===i?r.splice(0,a-s):r.splice(s,a-s),n}(e,n,u.sum,s,a),0===u.sum)return t.viewChildren=[];for(var h=0,c=a.length;h<c;h++){var p=a[h].getValue()/u.sum*n;a[h].setLayout({area:p})}l&&(a.length&&t.setLayout({isLeafRoot:!0},!0),a.length=0);return t.viewChildren=a,t.setLayout({dataExtent:u.dataExtent},!0),a}(t,s,f,e,n,i);if(g.length){var y={x:p,y:d,width:r,height:o},v=XT(r,o),m=1/0,_=[];_.area=0;for(var x=0,b=g.length;x<b;){var w=g[x];_.push(w),_.area+=w.getLayout().area;var S=tC(_,v,e.squareRatio);S<=m?(x++,m=S):(_.area-=_.pop().getLayout().area,eC(_,v,y,u,!1),v=XT(y.width,y.height),_.length=_.area=0,m=1/0)}if(_.length&&eC(_,v,y,u,!0),!n){var M=s.get(\"childrenVisibleMin\");null!=M&&f<M&&(n=!0)}for(x=0,b=g.length;x<b;x++)QT(g[x],e,n,i+1)}}}function tC(t,e,n){for(var i=0,r=1/0,o=0,a=void 0,s=t.length;o<s;o++)(a=t[o].getLayout().area)&&(a<r&&(r=a),a>i&&(i=a));var l=t.area*t.area,u=e*e*n;return l?UT(u*i/l,l/(u*r)):1/0}function eC(t,e,n,i,r){var o=e===n.width?0:1,a=1-o,s=[\"x\",\"y\"],l=[\"width\",\"height\"],u=n[s[o]],h=e?t.area/e:0;(r||h>n[l[a]])&&(h=n[l[a]]);for(var c=0,p=t.length;c<p;c++){var d=t[c],f={},g=h?d.getLayout().area/h:0,y=f[l[a]]=UT(h-2*i,0),v=n[s[o]]+n[l[o]]-u,m=c===p-1||v<g?v:g,_=f[l[o]]=UT(m-2*i,0);f[s[a]]=n[s[a]]+XT(i,y/2),f[s[o]]=u+XT(i,_/2),u+=m,d.setLayout(f,!0)}n[s[a]]+=h,n[l[a]]-=h}function nC(t,e,n,i,r){var o=t.getLayout(),a=n[r],s=a&&a===t;if(!(a&&!s||r===n.length&&t!==i)){t.setLayout({isInView:!0,invisible:!s&&!e.intersect(o),isAboveViewRoot:s},!0);var l=new gi(e.x-o.x,e.y-o.y,e.width,e.height);ZT(t.viewChildren||[],(function(t){nC(t,l,n,i,r+1)}))}}function iC(t){return t.get(KT)?t.get($T):0}function rC(t){var e=t.findComponents({mainType:\"legend\"});e&&e.length&&t.eachSeriesByType(\"graph\",(function(t){var n=t.getCategoriesData(),i=t.getGraph().data,r=n.mapArray(n.getName);i.filterSelf((function(t){var n=i.getItemModel(t).getShallow(\"category\");if(null!=n){\"number\"==typeof n&&(n=r[n]);for(var o=0;o<e.length;o++)if(!e[o].isSelected(n))return!1}return!0}))}))}function oC(t){var e={};t.eachSeriesByType(\"graph\",(function(t){var n=t.getCategoriesData(),i=t.getData(),r={};n.each((function(i){var o=n.getName(i);r[\"ec-\"+o]=i;var a=n.getItemModel(i),s=a.getModel(\"itemStyle\").getItemStyle();s.fill||(s.fill=t.getColorFromPalette(o,e)),n.setItemVisual(i,\"style\",s);for(var l=[\"symbol\",\"symbolSize\",\"symbolKeepAspect\"],u=0;u<l.length;u++){var h=a.getShallow(l[u],!0);null!=h&&n.setItemVisual(i,l[u],h)}})),n.count()&&i.each((function(t){var e=i.getItemModel(t).getShallow(\"category\");if(null!=e){\"string\"==typeof e&&(e=r[\"ec-\"+e]);var o=n.getItemVisual(e,\"style\");I(i.ensureUniqueItemVisual(t,\"style\"),o);for(var a=[\"symbol\",\"symbolSize\",\"symbolKeepAspect\"],s=0;s<a.length;s++)i.setItemVisual(t,a[s],n.getItemVisual(e,a[s]))}}))}))}function aC(t){return t instanceof Array||(t=[t,t]),t}function sC(t){t.eachSeriesByType(\"graph\",(function(t){var e=t.getGraph(),n=t.getEdgeData(),i=aC(t.get(\"edgeSymbol\")),r=aC(t.get(\"edgeSymbolSize\"));n.setVisual(\"fromSymbol\",i&&i[0]),n.setVisual(\"toSymbol\",i&&i[1]),n.setVisual(\"fromSymbolSize\",r&&r[0]),n.setVisual(\"toSymbolSize\",r&&r[1]),n.setVisual(\"style\",t.getModel(\"lineStyle\").getLineStyle()),n.each((function(t){var i=n.getItemModel(t),r=e.getEdgeByIndex(t),o=aC(i.getShallow(\"symbol\",!0)),a=aC(i.getShallow(\"symbolSize\",!0)),s=i.getModel(\"lineStyle\").getLineStyle(),l=n.ensureUniqueItemVisual(t,\"style\");switch(I(l,s),l.stroke){case\"source\":var u=r.node1.getVisual(\"style\");l.stroke=u&&u.fill;break;case\"target\":u=r.node2.getVisual(\"style\");l.stroke=u&&u.fill}o[0]&&r.setVisual(\"fromSymbol\",o[0]),o[1]&&r.setVisual(\"toSymbol\",o[1]),a[0]&&r.setVisual(\"fromSymbolSize\",a[0]),a[1]&&r.setVisual(\"toSymbolSize\",a[1])}))}))}var lC=\"--\\x3e\",uC=function(t){return t.get(\"autoCurveness\")||null},hC=function(t,e){var n=uC(t),i=20,r=[];if(\"number\"==typeof n)i=n;else if(F(n))return void(t.__curvenessList=n);e>i&&(i=e);var o=i%2?i+2:i+3;r=[];for(var a=0;a<o;a++)r.push((a%2?a+1:a)/10*(a%2?-1:1));t.__curvenessList=r},cC=function(t,e,n){var i=[t.id,t.dataIndex].join(\".\"),r=[e.id,e.dataIndex].join(\".\");return[n.uid,i,r].join(lC)},pC=function(t){var e=t.split(lC);return[e[0],e[2],e[1]].join(lC)},dC=function(t,e){var n=e.__edgeMap;return n[t]?n[t].length:0};function fC(t,e,n,i){var r=uC(e),o=F(r);if(!r)return null;var a=function(t,e){var n=cC(t.node1,t.node2,e);return e.__edgeMap[n]}(t,e);if(!a)return null;for(var s=-1,l=0;l<a.length;l++)if(a[l]===n){s=l;break}var u=function(t,e){return dC(cC(t.node1,t.node2,e),e)+dC(cC(t.node2,t.node1,e),e)}(t,e);hC(e,u),t.lineStyle=t.lineStyle||{};var h=cC(t.node1,t.node2,e),c=e.__curvenessList,p=o||u%2?0:1;if(a.isForward)return c[p+s];var d=pC(h),f=dC(d,e),g=c[s+f+p];return i?o?r&&0===r[0]?(f+p)%2?g:-g:((f%2?0:1)+p)%2?g:-g:(f+p)%2?g:-g:c[s+f+p]}function gC(t){var e=t.coordinateSystem;if(!e||\"view\"===e.type){var n=t.getGraph();n.eachNode((function(t){var e=t.getModel();t.setLayout([+e.get(\"x\"),+e.get(\"y\")])})),yC(n,t)}}function yC(t,e){t.eachEdge((function(t,n){var i=et(t.getModel().get([\"lineStyle\",\"curveness\"]),-fC(t,e,n,!0),0),r=mt(t.node1.getLayout()),o=mt(t.node2.getLayout()),a=[r,o];+i&&a.push([(r[0]+o[0])/2-(r[1]-o[1])*i,(r[1]+o[1])/2-(o[0]-r[0])*i]),t.setLayout(a)}))}function vC(t,e){t.eachSeriesByType(\"graph\",(function(t){var e=t.get(\"layout\"),n=t.coordinateSystem;if(n&&\"view\"!==n.type){var i=t.getData(),r=[];P(n.dimensions,(function(t){r=r.concat(i.mapDimensionsAll(t))}));for(var o=0;o<i.count();o++){for(var a=[],s=!1,l=0;l<r.length;l++){var u=i.get(r[l],o);isNaN(u)||(s=!0),a.push(u)}s?i.setItemLayout(o,n.dataToPoint(a)):i.setItemLayout(o,[NaN,NaN])}yC(i.graph,t)}else e&&\"none\"!==e||gC(t)}))}function mC(t){var e=t.coordinateSystem;if(\"view\"!==e.type)return 1;var n=t.option.nodeScaleRatio,i=e.scaleX;return((e.getZoom()-1)*n+1)/i}function _C(t){var e=t.getVisual(\"symbolSize\");return e instanceof Array&&(e=(e[0]+e[1])/2),+e}var xC=Math.PI,bC=[];function wC(t,e){var n=t.coordinateSystem;if(!n||\"view\"===n.type){var i=n.getBoundingRect(),r=t.getData(),o=r.graph,a=i.width/2+i.x,s=i.height/2+i.y,l=Math.min(i.width,i.height)/2,u=r.count();r.setLayout({cx:a,cy:s}),u&&(SC[e](t,o,r,l,a,s,u),o.eachEdge((function(e,n){var i,r=et(e.getModel().get([\"lineStyle\",\"curveness\"]),fC(e,t,n),0),o=mt(e.node1.getLayout()),l=mt(e.node2.getLayout()),u=(o[0]+l[0])/2,h=(o[1]+l[1])/2;+r&&(i=[a*(r*=3)+u*(1-r),s*r+h*(1-r)]),e.setLayout([o,l,i])})))}}var SC={value:function(t,e,n,i,r,o,a){var s=0,l=n.getSum(\"value\"),u=2*Math.PI/(l||a);e.eachNode((function(t){var e=t.getValue(\"value\"),n=u*(l?e:1)/2;s+=n,t.setLayout([i*Math.cos(s)+r,i*Math.sin(s)+o]),s+=n}))},symbolSize:function(t,e,n,i,r,o,a){var s=0;bC.length=a;var l=mC(t);e.eachNode((function(t){var e=_C(t);isNaN(e)&&(e=2),e<0&&(e=0),e*=l;var n=Math.asin(e/2/i);isNaN(n)&&(n=xC/2),bC[t.dataIndex]=n,s+=2*n}));var u=(2*xC-s)/a/2,h=0;e.eachNode((function(t){var e=u+bC[t.dataIndex];h+=e,t.setLayout([i*Math.cos(h)+r,i*Math.sin(h)+o]),h+=e}))}};function MC(t){t.eachSeriesByType(\"graph\",(function(t){\"circular\"===t.get(\"layout\")&&wC(t,\"symbolSize\")}))}var IC=bt;function TC(t){t.eachSeriesByType(\"graph\",(function(t){var e=t.coordinateSystem;if(!e||\"view\"===e.type)if(\"force\"===t.get(\"layout\")){var n=t.preservedPoints||{},i=t.getGraph(),r=i.data,o=i.edgeData,a=t.getModel(\"force\"),s=a.get(\"initLayout\");t.preservedPoints?r.each((function(t){var e=r.getId(t);r.setItemLayout(t,n[e]||[NaN,NaN])})):s&&\"none\"!==s?\"circular\"===s&&wC(t,\"value\"):gC(t);var l=r.getDataExtent(\"value\"),u=o.getDataExtent(\"value\"),h=a.get(\"repulsion\"),c=a.get(\"edgeLength\"),p=F(h)?h:[h,h],d=F(c)?c:[c,c];d=[d[1],d[0]];var f=r.mapArray(\"value\",(function(t,e){var n=r.getItemLayout(e),i=Yi(t,l,p);return isNaN(i)&&(i=(p[0]+p[1])/2),{w:i,rep:i,fixed:r.getItemModel(e).get(\"fixed\"),p:!n||isNaN(n[0])||isNaN(n[1])?null:n}})),g=o.mapArray(\"value\",(function(e,n){var r=i.getEdgeByIndex(n),o=Yi(e,u,d);isNaN(o)&&(o=(d[0]+d[1])/2);var a=r.getModel(),s=et(r.getModel().get([\"lineStyle\",\"curveness\"]),-fC(r,t,n,!0),0);return{n1:f[r.node1.dataIndex],n2:f[r.node2.dataIndex],d:o,curveness:s,ignoreForceLayout:a.get(\"ignoreForceLayout\")}})),y=e.getBoundingRect(),v=function(t,e,n){for(var i=t,r=e,o=n.rect,a=o.width,s=o.height,l=[o.x+a/2,o.y+s/2],u=null==n.gravity?.1:n.gravity,h=0;h<i.length;h++){var c=i[h];c.p||(c.p=yt(a*(Math.random()-.5)+l[0],s*(Math.random()-.5)+l[1])),c.pp=mt(c.p),c.edges=null}var p,d,f=null==n.friction?.6:n.friction,g=f;return{warmUp:function(){g=.8*f},setFixed:function(t){i[t].fixed=!0},setUnfixed:function(t){i[t].fixed=!1},beforeStep:function(t){p=t},afterStep:function(t){d=t},step:function(t){p&&p(i,r);for(var e=[],n=i.length,o=0;o<r.length;o++){var a=r[o];if(!a.ignoreForceLayout){var s=a.n1;wt(e,(y=a.n2).p,s.p);var h=St(e)-a.d,c=y.w/(s.w+y.w);isNaN(c)&&(c=0),Dt(e,e),!s.fixed&&IC(s.p,s.p,e,c*h*g),!y.fixed&&IC(y.p,y.p,e,-(1-c)*h*g)}}for(o=0;o<n;o++)(_=i[o]).fixed||(wt(e,l,_.p),IC(_.p,_.p,e,u*g));for(o=0;o<n;o++){s=i[o];for(var f=o+1;f<n;f++){var y;wt(e,(y=i[f]).p,s.p),0===(h=St(e))&&(_t(e,Math.random()-.5,Math.random()-.5),h=1);var v=(s.rep+y.rep)/h/h;!s.fixed&&IC(s.pp,s.pp,e,v),!y.fixed&&IC(y.pp,y.pp,e,-v)}}var m=[];for(o=0;o<n;o++){var _;(_=i[o]).fixed||(wt(m,_.p,_.pp),IC(_.p,_.p,m,g),vt(_.pp,_.p))}var x=(g*=.992)<.01;d&&d(i,r,x),t&&t(x)}}}(f,g,{rect:y,gravity:a.get(\"gravity\"),friction:a.get(\"friction\")});v.beforeStep((function(t,e){for(var n=0,r=t.length;n<r;n++)t[n].fixed&&vt(t[n].p,i.getNodeByIndex(n).getLayout())})),v.afterStep((function(t,e,o){for(var a=0,s=t.length;a<s;a++)t[a].fixed||i.getNodeByIndex(a).setLayout(t[a].p),n[r.getId(a)]=t[a].p;for(a=0,s=e.length;a<s;a++){var l=e[a],u=i.getEdgeByIndex(a),h=l.n1.p,c=l.n2.p,p=u.getLayout();(p=p?p.slice():[])[0]=p[0]||[],p[1]=p[1]||[],vt(p[0],h),vt(p[1],c),+l.curveness&&(p[2]=[(h[0]+c[0])/2-(h[1]-c[1])*l.curveness,(h[1]+c[1])/2-(c[0]-h[0])*l.curveness]),u.setLayout(p)}})),t.forceLayout=v,t.preservedPoints=n,v.step()}else t.forceLayout=null}))}function CC(t,e){var n=[];return t.eachSeriesByType(\"graph\",(function(t){var i=t.get(\"coordinateSystem\");if(!i||\"view\"===i){var r=t.getData(),o=[],a=[];ra(r.mapArray((function(t){var e=r.getItemModel(t);return[+e.get(\"x\"),+e.get(\"y\")]})),o,a),a[0]-o[0]==0&&(a[0]+=1,o[0]-=1),a[1]-o[1]==0&&(a[1]+=1,o[1]-=1);var s=(a[0]-o[0])/(a[1]-o[1]),l=function(t,e,n){return Vc(I(t.getBoxLayoutParams(),{aspect:n}),{width:e.getWidth(),height:e.getHeight()})}(t,e,s);isNaN(s)&&(o=[l.x,l.y],a=[l.x+l.width,l.y+l.height]);var u=a[0]-o[0],h=a[1]-o[1],c=l.width,p=l.height,d=t.coordinateSystem=new fI;d.zoomLimit=t.get(\"scaleLimit\"),d.setBoundingRect(o[0],o[1],u,h),d.setViewRect(l.x,l.y,c,p),d.setCenter(t.get(\"center\")),d.setZoom(t.get(\"zoom\")),n.push(d)}})),n}var DC=uu.prototype,AC=du.prototype,LC=function(){this.x1=0,this.y1=0,this.x2=0,this.y2=0,this.percent=1};!function(t){function e(){return null!==t&&t.apply(this,arguments)||this}n(e,t)}(LC);function kC(t){return isNaN(+t.cpx1)||isNaN(+t.cpy1)}var PC=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"ec-line\",n}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new LC},e.prototype.buildPath=function(t,e){kC(e)?DC.buildPath.call(this,t,e):AC.buildPath.call(this,t,e)},e.prototype.pointAt=function(t){return kC(this.shape)?DC.pointAt.call(this,t):AC.pointAt.call(this,t)},e.prototype.tangentAt=function(t){var e=this.shape,n=kC(e)?[e.x2-e.x1,e.y2-e.y1]:AC.tangentAt.call(this,t);return Dt(n,n)},e}(Ka),OC=[\"fromSymbol\",\"toSymbol\"];function RC(t){return\"_\"+t+\"Type\"}function NC(t,e,n){var i=e.getItemVisual(n,t);if(i&&\"none\"!==i){var r=e.getItemVisual(n,t+\"Size\"),o=e.getItemVisual(n,t+\"Rotate\"),a=e.getItemVisual(n,t+\"Offset\")||0,s=e.getItemVisual(n,t+\"KeepAspect\"),l=F(r)?r:[r,r],u=F(a)?a:[a,a];u[0]=Zi(u[0],l[0]),u[1]=Zi(tt(u[1],u[0]),l[1]);var h=fy(i,-l[0]/2+u[0],-l[1]/2+u[1],l[0],l[1],null,s);return h.__specifiedRotation=null==o||isNaN(o)?void 0:+o*Math.PI/180||0,h.name=t,h}}function zC(t,e){t.x1=e[0][0],t.y1=e[0][1],t.x2=e[1][0],t.y2=e[1][1],t.percent=1;var n=e[2];n?(t.cpx1=n[0],t.cpy1=n[1]):(t.cpx1=NaN,t.cpy1=NaN)}var EC=function(t){function e(e,n,i){var r=t.call(this)||this;return r._createLine(e,n,i),r}return n(e,t),e.prototype._createLine=function(t,e,n){var i=t.hostModel,r=function(t){var e=new PC({name:\"line\",subPixelOptimize:!0});return zC(e.shape,t),e}(t.getItemLayout(e));r.shape.percent=0,Wu(r,{shape:{percent:1}},i,e),this.add(r),P(OC,(function(n){var i=NC(n,t,e);this.add(i),this[RC(n)]=t.getItemVisual(e,n)}),this),this._updateCommonStl(t,e,n)},e.prototype.updateData=function(t,e,n){var i=t.hostModel,r=this.childOfName(\"line\"),o=t.getItemLayout(e),a={shape:{}};zC(a.shape,o),Hu(r,a,i,e),P(OC,(function(n){var i=t.getItemVisual(e,n),r=RC(n);if(this[r]!==i){this.remove(this.childOfName(n));var o=NC(n,t,e);this.add(o)}this[r]=i}),this),this._updateCommonStl(t,e,n)},e.prototype.getLinePath=function(){return this.childAt(0)},e.prototype._updateCommonStl=function(t,e,n){var i=t.hostModel,r=this.childOfName(\"line\"),o=n&&n.emphasisLineStyle,a=n&&n.blurLineStyle,s=n&&n.selectLineStyle,l=n&&n.labelStatesModels;if(!n||t.hasItemOption){var u=t.getItemModel(e);o=u.getModel([\"emphasis\",\"lineStyle\"]).getLineStyle(),a=u.getModel([\"blur\",\"lineStyle\"]).getLineStyle(),s=u.getModel([\"select\",\"lineStyle\"]).getLineStyle(),l=ch(u)}var h=t.getItemVisual(e,\"style\"),c=h.stroke;r.useStyle(h),r.style.fill=null,r.style.strokeNoScale=!0,r.ensureState(\"emphasis\").style=o,r.ensureState(\"blur\").style=a,r.ensureState(\"select\").style=s,P(OC,(function(t){var e=this.childOfName(t);if(e){e.setColor(c),e.style.opacity=h.opacity;for(var n=0;n<Ss.length;n++){var i=Ss[n],o=r.getState(i);if(o){var a=o.style||{},s=e.ensureState(i),l=s.style||(s.style={});null!=a.stroke&&(l[e.__isEmptyBrush?\"stroke\":\"fill\"]=a.stroke),null!=a.opacity&&(l.opacity=a.opacity)}}e.markRedraw()}}),this);var p=i.getRawValue(e);hh(this,l,{labelDataIndex:e,labelFetcher:{getFormattedLabel:function(e,n){return i.getFormattedLabel(e,n,t.dataType)}},inheritColor:c||\"#000\",defaultOpacity:h.opacity,defaultText:(null==p?t.getName(e):isFinite(p)?ji(p):p)+\"\"});var d=this.getTextContent();if(d){var f=l.normal;d.__align=d.style.align,d.__verticalAlign=d.style.verticalAlign,d.__position=f.get(\"position\")||\"middle\";var g=f.get(\"distance\");F(g)||(g=[g,g]),d.__labelDistance=g}this.setTextConfig({position:null,local:!0,inside:!1}),sl(this)},e.prototype.highlight=function(){js(this)},e.prototype.downplay=function(){qs(this)},e.prototype.updateLayout=function(t,e){this.setLinePoints(t.getItemLayout(e))},e.prototype.setLinePoints=function(t){var e=this.childOfName(\"line\");zC(e.shape,t),e.dirty()},e.prototype.beforeUpdate=function(){var t=this,e=t.childOfName(\"fromSymbol\"),n=t.childOfName(\"toSymbol\"),i=t.getTextContent();if(e||n||i&&!i.ignore){for(var r=1,o=this.parent;o;)o.scaleX&&(r/=o.scaleX),o=o.parent;var a=t.childOfName(\"line\");if(this.__dirty||a.__dirty){var s=a.shape.percent,l=a.pointAt(0),u=a.pointAt(s),h=wt([],u,l);if(Dt(h,h),e&&(e.setPosition(l),S(e,0),e.scaleX=e.scaleY=r*s,e.markRedraw()),n&&(n.setPosition(u),S(n,1),n.scaleX=n.scaleY=r*s,n.markRedraw()),i&&!i.ignore){i.x=i.y=0,i.originX=i.originY=0;var c=void 0,p=void 0,d=i.__labelDistance,f=d[0]*r,g=d[1]*r,y=s/2,v=a.tangentAt(y),m=[v[1],-v[0]],_=a.pointAt(y);m[1]>0&&(m[0]=-m[0],m[1]=-m[1]);var x=v[0]<0?-1:1;if(\"start\"!==i.__position&&\"end\"!==i.__position){var b=-Math.atan2(v[1],v[0]);u[0]<l[0]&&(b=Math.PI+b),i.rotation=b}var w=void 0;switch(i.__position){case\"insideStartTop\":case\"insideMiddleTop\":case\"insideEndTop\":case\"middle\":w=-g,p=\"bottom\";break;case\"insideStartBottom\":case\"insideMiddleBottom\":case\"insideEndBottom\":w=g,p=\"top\";break;default:w=0,p=\"middle\"}switch(i.__position){case\"end\":i.x=h[0]*f+u[0],i.y=h[1]*g+u[1],c=h[0]>.8?\"left\":h[0]<-.8?\"right\":\"center\",p=h[1]>.8?\"top\":h[1]<-.8?\"bottom\":\"middle\";break;case\"start\":i.x=-h[0]*f+l[0],i.y=-h[1]*g+l[1],c=h[0]>.8?\"right\":h[0]<-.8?\"left\":\"center\",p=h[1]>.8?\"bottom\":h[1]<-.8?\"top\":\"middle\";break;case\"insideStartTop\":case\"insideStart\":case\"insideStartBottom\":i.x=f*x+l[0],i.y=l[1]+w,c=v[0]<0?\"right\":\"left\",i.originX=-f*x,i.originY=-w;break;case\"insideMiddleTop\":case\"insideMiddle\":case\"insideMiddleBottom\":case\"middle\":i.x=_[0],i.y=_[1]+w,c=\"center\",i.originY=-w;break;case\"insideEndTop\":case\"insideEnd\":case\"insideEndBottom\":i.x=-f*x+u[0],i.y=u[1]+w,c=v[0]>=0?\"right\":\"left\",i.originX=f*x,i.originY=-w}i.scaleX=i.scaleY=r,i.setStyle({verticalAlign:i.__verticalAlign||p,align:i.__align||c})}}}function S(t,e){var n=t.__specifiedRotation;if(null==n){var i=a.tangentAt(e);t.attr(\"rotation\",(1===e?-1:1)*Math.PI/2-Math.atan2(i[1],i[0]))}else t.attr(\"rotation\",n)}},e}(Ei),VC=function(){function t(t){this.group=new Ei,this._LineCtor=t||EC}return t.prototype.isPersistent=function(){return!0},t.prototype.updateData=function(t){var e=this,n=this,i=n.group,r=n._lineData;n._lineData=t,r||i.removeAll();var o=BC(t);t.diff(r).add((function(n){e._doAdd(t,n,o)})).update((function(n,i){e._doUpdate(r,t,i,n,o)})).remove((function(t){i.remove(r.getItemGraphicEl(t))})).execute()},t.prototype.updateLayout=function(){var t=this._lineData;t&&t.eachItemGraphicEl((function(e,n){e.updateLayout(t,n)}),this)},t.prototype.incrementalPrepareUpdate=function(t){this._seriesScope=BC(t),this._lineData=null,this.group.removeAll()},t.prototype.incrementalUpdate=function(t,e){function n(t){t.isGroup||function(t){return t.animators&&t.animators.length>0}(t)||(t.incremental=!0,t.ensureState(\"emphasis\").hoverLayer=!0)}for(var i=t.start;i<t.end;i++){if(GC(e.getItemLayout(i))){var r=new this._LineCtor(e,i,this._seriesScope);r.traverse(n),this.group.add(r),e.setItemGraphicEl(i,r)}}},t.prototype.remove=function(){this.group.removeAll()},t.prototype._doAdd=function(t,e,n){if(GC(t.getItemLayout(e))){var i=new this._LineCtor(t,e,n);t.setItemGraphicEl(e,i),this.group.add(i)}},t.prototype._doUpdate=function(t,e,n,i,r){var o=t.getItemGraphicEl(n);GC(e.getItemLayout(i))?(o?o.updateData(e,i,r):o=new this._LineCtor(e,i,r),e.setItemGraphicEl(i,o),this.group.add(o)):this.group.remove(o)},t}();function BC(t){var e=t.hostModel;return{lineStyle:e.getModel(\"lineStyle\").getLineStyle(),emphasisLineStyle:e.getModel([\"emphasis\",\"lineStyle\"]).getLineStyle(),blurLineStyle:e.getModel([\"blur\",\"lineStyle\"]).getLineStyle(),selectLineStyle:e.getModel([\"select\",\"lineStyle\"]).getLineStyle(),labelStatesModels:ch(e)}}function FC(t){return isNaN(t[0])||isNaN(t[1])}function GC(t){return!FC(t[0])&&!FC(t[1])}var HC=[],WC=[],UC=[],XC=Uo,YC=Pt,ZC=Math.abs;function jC(t,e,n){for(var i,r=t[0],o=t[1],a=t[2],s=1/0,l=n*n,u=.1,h=.1;h<=.9;h+=.1){HC[0]=XC(r[0],o[0],a[0],h),HC[1]=XC(r[1],o[1],a[1],h),(d=ZC(YC(HC,e)-l))<s&&(s=d,i=h)}for(var c=0;c<32;c++){var p=i+u;WC[0]=XC(r[0],o[0],a[0],i),WC[1]=XC(r[1],o[1],a[1],i),UC[0]=XC(r[0],o[0],a[0],p),UC[1]=XC(r[1],o[1],a[1],p);var d=YC(WC,e)-l;if(ZC(d)<.01)break;var f=YC(UC,e)-l;u/=2,d<0?f>=0?i+=u:i-=u:f>=0?i-=u:i+=u}return i}function qC(t,e){var n=[],i=Zo,r=[[],[],[]],o=[[],[]],a=[];e/=2,t.eachEdge((function(t,s){var l=t.getLayout(),u=t.getVisual(\"fromSymbol\"),h=t.getVisual(\"toSymbol\");l.__original||(l.__original=[mt(l[0]),mt(l[1])],l[2]&&l.__original.push(mt(l[2])));var c=l.__original;if(null!=l[2]){if(vt(r[0],c[0]),vt(r[1],c[2]),vt(r[2],c[1]),u&&\"none\"!==u){var p=_C(t.node1),d=jC(r,c[0],p*e);i(r[0][0],r[1][0],r[2][0],d,n),r[0][0]=n[3],r[1][0]=n[4],i(r[0][1],r[1][1],r[2][1],d,n),r[0][1]=n[3],r[1][1]=n[4]}if(h&&\"none\"!==h){p=_C(t.node2),d=jC(r,c[1],p*e);i(r[0][0],r[1][0],r[2][0],d,n),r[1][0]=n[1],r[2][0]=n[2],i(r[0][1],r[1][1],r[2][1],d,n),r[1][1]=n[1],r[2][1]=n[2]}vt(l[0],r[0]),vt(l[1],r[2]),vt(l[2],r[1])}else{if(vt(o[0],c[0]),vt(o[1],c[1]),wt(a,o[1],o[0]),Dt(a,a),u&&\"none\"!==u){p=_C(t.node1);bt(o[0],o[0],a,p*e)}if(h&&\"none\"!==h){p=_C(t.node2);bt(o[1],o[1],a,-p*e)}vt(l[0],o[0]),vt(l[1],o[1])}}))}function KC(t){return\"view\"===t.type}var $C=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){var n=new mw,i=new VC,r=this.group;this._controller=new WM(e.getZr()),this._controllerHost={target:r},r.add(n.group),r.add(i.group),this._symbolDraw=n,this._lineDraw=i,this._firstRender=!0},e.prototype.render=function(t,e,n){var i=this,r=t.coordinateSystem;this._model=t;var o=this._symbolDraw,a=this._lineDraw,s=this.group;if(KC(r)){var l={x:r.x,y:r.y,scaleX:r.scaleX,scaleY:r.scaleY};this._firstRender?s.attr(l):Hu(s,l,t)}qC(t.getGraph(),mC(t));var u=t.getData();o.updateData(u);var h=t.getEdgeData();a.updateData(h),this._updateNodeAndLinkScale(),this._updateController(t,e,n),clearTimeout(this._layoutTimeout);var c=t.forceLayout,p=t.get([\"force\",\"layoutAnimation\"]);c&&this._startForceLayoutIteration(c,p),u.graph.eachNode((function(t){var e=t.dataIndex,n=t.getGraphicEl(),r=t.getModel();n.off(\"drag\").off(\"dragend\");var o=r.get(\"draggable\");o&&n.on(\"drag\",(function(){c&&(c.warmUp(),!i._layouting&&i._startForceLayoutIteration(c,p),c.setFixed(e),u.setItemLayout(e,[n.x,n.y]))})).on(\"dragend\",(function(){c&&c.setUnfixed(e)})),n.setDraggable(o&&!!c),\"adjacency\"===r.get([\"emphasis\",\"focus\"])&&(_s(n).focus=t.getAdjacentDataIndices())})),u.graph.eachEdge((function(t){var e=t.getGraphicEl();\"adjacency\"===t.getModel().get([\"emphasis\",\"focus\"])&&(_s(e).focus={edge:[t.dataIndex],node:[t.node1.dataIndex,t.node2.dataIndex]})}));var d=\"circular\"===t.get(\"layout\")&&t.get([\"circular\",\"rotateLabel\"]),f=u.getLayout(\"cx\"),g=u.getLayout(\"cy\");u.eachItemGraphicEl((function(t,e){var n=u.getItemModel(e).get([\"label\",\"rotate\"])||0,i=t.getSymbolPath();if(d){var r=u.getItemLayout(e),o=Math.atan2(r[1]-g,r[0]-f);o<0&&(o=2*Math.PI+o);var a=r[0]<f;a&&(o-=Math.PI);var s=a?\"left\":\"right\";i.setTextConfig({rotation:-o,position:s,origin:\"center\"});var l=i.ensureState(\"emphasis\");I(l.textConfig||(l.textConfig={}),{position:s})}else i.setTextConfig({rotation:n*=Math.PI/180})})),this._firstRender=!1},e.prototype.dispose=function(){this._controller&&this._controller.dispose(),this._controllerHost=null},e.prototype._startForceLayoutIteration=function(t,e){var n=this;!function i(){t.step((function(t){n.updateLayout(n._model),(n._layouting=!t)&&(e?n._layoutTimeout=setTimeout(i,16):i())}))}()},e.prototype._updateController=function(t,e,n){var i=this,r=this._controller,o=this._controllerHost,a=this.group;r.setPointerChecker((function(e,i,r){var o=a.getBoundingRect();return o.applyTransform(a.transform),o.contain(i,r)&&!KM(e,n,t)})),KC(t.coordinateSystem)?(r.enable(t.get(\"roam\")),o.zoomLimit=t.get(\"scaleLimit\"),o.zoom=t.coordinateSystem.getZoom(),r.off(\"pan\").off(\"zoom\").on(\"pan\",(function(e){ZM(o,e.dx,e.dy),n.dispatchAction({seriesId:t.id,type:\"graphRoam\",dx:e.dx,dy:e.dy})})).on(\"zoom\",(function(e){jM(o,e.scale,e.originX,e.originY),n.dispatchAction({seriesId:t.id,type:\"graphRoam\",zoom:e.scale,originX:e.originX,originY:e.originY}),i._updateNodeAndLinkScale(),qC(t.getGraph(),mC(t)),i._lineDraw.updateLayout(),n.updateLabelLayout()}))):r.disable()},e.prototype._updateNodeAndLinkScale=function(){var t=this._model,e=t.getData(),n=mC(t);e.eachItemGraphicEl((function(t,e){t.setSymbolScale(n)}))},e.prototype.updateLayout=function(t){qC(t.getGraph(),mC(t)),this._symbolDraw.updateLayout(),this._lineDraw.updateLayout()},e.prototype.remove=function(t,e){this._symbolDraw&&this._symbolDraw.remove(),this._lineDraw&&this._lineDraw.remove()},e.type=\"graph\",e}(Tf);function JC(t){return\"_EC_\"+t}var QC=function(){function t(t){this.type=\"graph\",this.nodes=[],this.edges=[],this._nodesMap={},this._edgesMap={},this._directed=t||!1}return t.prototype.isDirected=function(){return this._directed},t.prototype.addNode=function(t,e){t=null==t?\"\"+e:\"\"+t;var n=this._nodesMap;if(!n[JC(t)]){var i=new tD(t,e);return i.hostGraph=this,this.nodes.push(i),n[JC(t)]=i,i}},t.prototype.getNodeByIndex=function(t){var e=this.data.getRawIndex(t);return this.nodes[e]},t.prototype.getNodeById=function(t){return this._nodesMap[JC(t)]},t.prototype.addEdge=function(t,e,n){var i=this._nodesMap,r=this._edgesMap;if(\"number\"==typeof t&&(t=this.nodes[t]),\"number\"==typeof e&&(e=this.nodes[e]),t instanceof tD||(t=i[JC(t)]),e instanceof tD||(e=i[JC(e)]),t&&e){var o=t.id+\"-\"+e.id,a=new eD(t,e,n);return a.hostGraph=this,this._directed&&(t.outEdges.push(a),e.inEdges.push(a)),t.edges.push(a),t!==e&&e.edges.push(a),this.edges.push(a),r[o]=a,a}},t.prototype.getEdgeByIndex=function(t){var e=this.edgeData.getRawIndex(t);return this.edges[e]},t.prototype.getEdge=function(t,e){t instanceof tD&&(t=t.id),e instanceof tD&&(e=e.id);var n=this._edgesMap;return this._directed?n[t+\"-\"+e]:n[t+\"-\"+e]||n[e+\"-\"+t]},t.prototype.eachNode=function(t,e){for(var n=this.nodes,i=n.length,r=0;r<i;r++)n[r].dataIndex>=0&&t.call(e,n[r],r)},t.prototype.eachEdge=function(t,e){for(var n=this.edges,i=n.length,r=0;r<i;r++)n[r].dataIndex>=0&&n[r].node1.dataIndex>=0&&n[r].node2.dataIndex>=0&&t.call(e,n[r],r)},t.prototype.breadthFirstTraverse=function(t,e,n,i){if(e instanceof tD||(e=this._nodesMap[JC(e)]),e){for(var r=\"out\"===n?\"outEdges\":\"in\"===n?\"inEdges\":\"edges\",o=0;o<this.nodes.length;o++)this.nodes[o].__visited=!1;if(!t.call(i,e,null))for(var a=[e];a.length;){var s=a.shift(),l=s[r];for(o=0;o<l.length;o++){var u=l[o],h=u.node1===s?u.node2:u.node1;if(!h.__visited){if(t.call(i,h,s))return;a.push(h),h.__visited=!0}}}}},t.prototype.update=function(){for(var t=this.data,e=this.edgeData,n=this.nodes,i=this.edges,r=0,o=n.length;r<o;r++)n[r].dataIndex=-1;for(r=0,o=t.count();r<o;r++)n[t.getRawIndex(r)].dataIndex=r;e.filterSelf((function(t){var n=i[e.getRawIndex(t)];return n.node1.dataIndex>=0&&n.node2.dataIndex>=0}));for(r=0,o=i.length;r<o;r++)i[r].dataIndex=-1;for(r=0,o=e.count();r<o;r++)i[e.getRawIndex(r)].dataIndex=r},t.prototype.clone=function(){for(var e=new t(this._directed),n=this.nodes,i=this.edges,r=0;r<n.length;r++)e.addNode(n[r].id,n[r].dataIndex);for(r=0;r<i.length;r++){var o=i[r];e.addEdge(o.node1.id,o.node2.id,o.dataIndex)}return e},t}(),tD=function(){function t(t,e){this.inEdges=[],this.outEdges=[],this.edges=[],this.dataIndex=-1,this.id=null==t?\"\":t,this.dataIndex=null==e?-1:e}return t.prototype.degree=function(){return this.edges.length},t.prototype.inDegree=function(){return this.inEdges.length},t.prototype.outDegree=function(){return this.outEdges.length},t.prototype.getModel=function(t){if(!(this.dataIndex<0))return this.hostGraph.data.getItemModel(this.dataIndex).getModel(t)},t.prototype.getAdjacentDataIndices=function(){for(var t={edge:[],node:[]},e=0;e<this.edges.length;e++){var n=this.edges[e];n.dataIndex<0||(t.edge.push(n.dataIndex),t.node.push(n.node1.dataIndex,n.node2.dataIndex))}return t},t}(),eD=function(){function t(t,e,n){this.dataIndex=-1,this.node1=t,this.node2=e,this.dataIndex=null==n?-1:n}return t.prototype.getModel=function(t){if(!(this.dataIndex<0))return this.hostGraph.edgeData.getItemModel(this.dataIndex).getModel(t)},t.prototype.getAdjacentDataIndices=function(){return{edge:[this.dataIndex],node:[this.node1.dataIndex,this.node2.dataIndex]}},t}();function nD(t,e){return{getValue:function(n){var i=this[t][e];return i.get(i.getDimension(n||\"value\"),this.dataIndex)},setVisual:function(n,i){this.dataIndex>=0&&this[t][e].setItemVisual(this.dataIndex,n,i)},getVisual:function(n){return this[t][e].getItemVisual(this.dataIndex,n)},setLayout:function(n,i){this.dataIndex>=0&&this[t][e].setItemLayout(this.dataIndex,n,i)},getLayout:function(){return this[t][e].getItemLayout(this.dataIndex)},getGraphicEl:function(){return this[t][e].getItemGraphicEl(this.dataIndex)},getRawIndex:function(){return this[t][e].getRawIndex(this.dataIndex)}}}function iD(t,e,n,i,r){for(var o=new QC(i),a=0;a<t.length;a++)o.addNode(Q(t[a].id,t[a].name,a),a);var s=[],l=[],u=0;for(a=0;a<e.length;a++){var h=e[a],c=h.source,p=h.target;o.addEdge(c,p,u)&&(l.push(h),s.push(Q(Cr(h.id,null),c+\" > \"+p)),u++)}var d,f=n.get(\"coordinateSystem\");if(\"cartesian2d\"===f||\"polar\"===f)d=F_(t,n);else{var g=Ap.get(f),y=g&&g.dimensions||[];D(y,\"value\")<0&&y.concat([\"value\"]);var v=O_(t,{coordDimensions:y});(d=new L_(v,n)).initData(t)}var m=new L_([\"value\"],n);return m.initData(l,s),r&&r(d,m),HI({mainData:d,struct:o,structAttr:\"graph\",datas:{node:d,edge:m},datasAttr:{node:\"data\",edge:\"edgeData\"}}),o.update(),o}L(tD,nD(\"hostGraph\",\"data\")),L(eD,nD(\"hostGraph\",\"edgeData\"));var rD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments);var n=this;function i(){return n._categoriesData}this.legendVisualProvider=new IS(i,i),this.fillDataTextStyle(e.edges||e.links),this._updateCategoriesData()},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),this.fillDataTextStyle(e.edges||e.links),this._updateCategoriesData()},e.prototype.mergeDefaultAndTheme=function(e){t.prototype.mergeDefaultAndTheme.apply(this,arguments),br(e,\"edgeLabel\",[\"show\"])},e.prototype.getInitialData=function(t,e){var n,i=t.edges||t.links||[],r=t.data||t.nodes||[],o=this;if(r&&i){uC(n=this)&&(n.__curvenessList=[],n.__edgeMap={},hC(n));var a=iD(r,i,this,!0,(function(t,e){t.wrapMethod(\"getItemModel\",(function(t){var e=o._categoriesModels[t.getShallow(\"category\")];return e&&(e.parentModel=t.parentModel,t.parentModel=e),t}));var n=Oh.prototype.getModel;function i(t,e){var i=n.call(this,t,e);return i.resolveParentPath=r,i}function r(t){if(t&&(\"label\"===t[0]||\"label\"===t[1])){var e=t.slice();return\"label\"===t[0]?e[0]=\"edgeLabel\":\"label\"===t[1]&&(e[1]=\"edgeLabel\"),e}return t}e.wrapMethod(\"getItemModel\",(function(t){return t.resolveParentPath=r,t.getModel=i,t}))}));return P(a.edges,(function(t){!function(t,e,n,i){if(uC(n)){var r=cC(t,e,n),o=n.__edgeMap,a=o[pC(r)];o[r]&&!a?o[r].isForward=!0:a&&o[r]&&(a.isForward=!0,o[r].isForward=!1),o[r]=o[r]||[],o[r].push(i)}}(t.node1,t.node2,this,t.dataIndex)}),this),a.data}},e.prototype.getGraph=function(){return this.getData().graph},e.prototype.getEdgeData=function(){return this.getGraph().edgeData},e.prototype.getCategoriesData=function(){return this._categoriesData},e.prototype.formatTooltip=function(t,e,n){if(\"edge\"===n){var i=this.getData(),r=this.getDataParams(t,n),o=i.graph.getEdgeByIndex(t),a=i.getName(o.node1.dataIndex),s=i.getName(o.node2.dataIndex),l=[];return null!=a&&l.push(a),null!=s&&l.push(s),tf(\"nameValue\",{name:l.join(\" > \"),value:r.value,noValue:null==r.value})}return cf({series:this,dataIndex:t,multipleSeries:e})},e.prototype._updateCategoriesData=function(){var t=O(this.option.categories||[],(function(t){return null!=t.value?t:I({value:0},t)})),e=new L_([\"value\"],this);e.initData(t),this._categoriesData=e,this._categoriesModels=e.mapArray((function(t){return e.getItemModel(t)}))},e.prototype.setZoom=function(t){this.option.zoom=t},e.prototype.setCenter=function(t){this.option.center=t},e.prototype.isAnimationEnabled=function(){return t.prototype.isAnimationEnabled.call(this)&&!(\"force\"===this.get(\"layout\")&&this.get([\"force\",\"layoutAnimation\"]))},e.type=\"series.graph\",e.dependencies=[\"grid\",\"polar\",\"geo\",\"singleAxis\",\"calendar\"],e.defaultOption={zlevel:0,z:2,coordinateSystem:\"view\",legendHoverLink:!0,layout:null,circular:{rotateLabel:!1},force:{initLayout:null,repulsion:[0,50],gravity:.1,friction:.6,edgeLength:30,layoutAnimation:!0},left:\"center\",top:\"center\",symbol:\"circle\",symbolSize:10,edgeSymbol:[\"none\",\"none\"],edgeSymbolSize:10,edgeLabel:{position:\"middle\",distance:5},draggable:!1,roam:!1,center:null,zoom:1,nodeScaleRatio:.6,label:{show:!1,formatter:\"{b}\"},itemStyle:{},lineStyle:{color:\"#aaa\",width:1,opacity:.5},emphasis:{scale:!0,label:{show:!0}},select:{itemStyle:{borderColor:\"#212121\"}}},e}(ff),oD={type:\"graphRoam\",event:\"graphRoam\",update:\"none\"};var aD=function(){this.angle=0,this.width=10,this.r=10,this.x=0,this.y=0},sD=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"pointer\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new aD},e.prototype.buildPath=function(t,e){var n=Math.cos,i=Math.sin,r=e.r,o=e.width,a=e.angle,s=e.x-n(a)*o*(o>=r/3?1:2),l=e.y-i(a)*o*(o>=r/3?1:2);a=e.angle-Math.PI/2,t.moveTo(s,l),t.lineTo(e.x+n(a)*o,e.y+i(a)*o),t.lineTo(e.x+n(e.angle)*r,e.y+i(e.angle)*r),t.lineTo(e.x-n(a)*o,e.y-i(a)*o),t.lineTo(s,l)},e}(Ka);function lD(t,e){var n=null==t?\"\":t+\"\";return e&&(\"string\"==typeof e?n=e.replace(\"{value}\",n):\"function\"==typeof e&&(n=e(t))),n}var uD=2*Math.PI,hD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeAll();var i=t.get([\"axisLine\",\"lineStyle\",\"color\"]),r=function(t,e){var n=t.get(\"center\"),i=e.getWidth(),r=e.getHeight(),o=Math.min(i,r);return{cx:Zi(n[0],e.getWidth()),cy:Zi(n[1],e.getHeight()),r:Zi(t.get(\"radius\"),o/2)}}(t,n);this._renderMain(t,e,n,i,r),this._data=t.getData()},e.prototype.dispose=function(){},e.prototype._renderMain=function(t,e,n,i,r){for(var o=this.group,a=t.get(\"clockwise\"),s=-t.get(\"startAngle\")/180*Math.PI,l=-t.get(\"endAngle\")/180*Math.PI,u=t.getModel(\"axisLine\"),h=u.get(\"roundCap\")?Jw:Jl,c=u.get(\"show\"),p=u.getModel(\"lineStyle\"),d=p.get(\"width\"),f=(l-s)%uD||l===s?(l-s)%uD:uD,g=s,y=0;c&&y<i.length;y++){var v=new h({shape:{startAngle:g,endAngle:l=s+f*Math.min(Math.max(i[y][0],0),1),cx:r.cx,cy:r.cy,clockwise:a,r0:r.r-d,r:r.r},silent:!0});v.setStyle({fill:i[y][1]}),v.setStyle(p.getLineStyle([\"color\",\"width\"])),o.add(v),g=l}var m=function(t){if(t<=0)return i[0][1];var e;for(e=0;e<i.length;e++)if(i[e][0]>=t&&(0===e?0:i[e-1][0])<t)return i[e][1];return i[e-1][1]};if(!a){var _=s;s=l,l=_}this._renderTicks(t,e,n,m,r,s,l,a,d),this._renderTitleAndDetail(t,e,n,m,r),this._renderAnchor(t,r),this._renderPointer(t,e,n,m,r,s,l,a,d)},e.prototype._renderTicks=function(t,e,n,i,r,o,a,s,l){for(var u,h,c=this.group,p=r.cx,d=r.cy,f=r.r,g=+t.get(\"min\"),y=+t.get(\"max\"),v=t.getModel(\"splitLine\"),m=t.getModel(\"axisTick\"),_=t.getModel(\"axisLabel\"),x=t.get(\"splitNumber\"),b=m.get(\"splitNumber\"),w=Zi(v.get(\"length\"),f),S=Zi(m.get(\"length\"),f),M=o,I=(a-o)/x,T=I/b,C=v.getModel(\"lineStyle\").getLineStyle(),D=m.getModel(\"lineStyle\").getLineStyle(),A=v.get(\"distance\"),L=0;L<=x;L++){if(u=Math.cos(M),h=Math.sin(M),v.get(\"show\")){var k=new uu({shape:{x1:u*(f-(P=A?A+l:l))+p,y1:h*(f-P)+d,x2:u*(f-w-P)+p,y2:h*(f-w-P)+d},style:C,silent:!0});\"auto\"===C.stroke&&k.setStyle({stroke:i(L/x)}),c.add(k)}if(_.get(\"show\")){var P=_.get(\"distance\")+A,O=lD(ji(L/x*(y-g)+g),_.get(\"formatter\")),R=i(L/x);c.add(new cs({style:ph(_,{text:O,x:u*(f-w-P)+p,y:h*(f-w-P)+d,verticalAlign:h<-.8?\"top\":h>.8?\"bottom\":\"middle\",align:u<-.4?\"left\":u>.4?\"right\":\"center\"},{inheritColor:R}),silent:!0}))}if(m.get(\"show\")&&L!==x){P=(P=m.get(\"distance\"))?P+l:l;for(var N=0;N<=b;N++){u=Math.cos(M),h=Math.sin(M);var z=new uu({shape:{x1:u*(f-P)+p,y1:h*(f-P)+d,x2:u*(f-S-P)+p,y2:h*(f-S-P)+d},silent:!0,style:D});\"auto\"===D.stroke&&z.setStyle({stroke:i((L+N/b)/x)}),c.add(z),M+=T}M-=T}else M+=I}},e.prototype._renderPointer=function(t,e,n,i,r,o,a,s,l){var u=this.group,h=this._data,c=this._progressEls,p=[],d=t.get([\"pointer\",\"show\"]),f=t.getModel(\"progress\"),g=f.get(\"show\"),y=t.getData(),v=y.mapDimension(\"value\"),m=+t.get(\"min\"),_=+t.get(\"max\"),x=[m,_],b=[o,a];function w(e,n){var i,o=y.getItemModel(e).getModel(\"pointer\"),a=Zi(o.get(\"width\"),r.r),s=Zi(o.get(\"length\"),r.r),l=t.get([\"pointer\",\"icon\"]),u=o.get(\"offsetCenter\"),h=Zi(u[0],r.r),c=Zi(u[1],r.r),p=o.get(\"keepAspect\");return(i=l?fy(l,h-a/2,c-s,a,s,null,p):new sD({shape:{angle:-Math.PI/2,width:a,r:s,x:h,y:c}})).rotation=-(n+Math.PI/2),i.x=r.cx,i.y=r.cy,i}function S(t,e){var n=f.get(\"roundCap\")?Jw:Jl,i=f.get(\"overlap\"),a=i?f.get(\"width\"):l/y.count(),u=i?r.r-a:r.r-(t+1)*a,h=i?r.r:r.r-t*a,c=new n({shape:{startAngle:o,endAngle:e,cx:r.cx,cy:r.cy,clockwise:s,r0:u,r:h}});return i&&(c.z2=_-y.get(v,t)%_),c}(g||d)&&(y.diff(h).add((function(e){if(d){var n=w(e,o);Wu(n,{rotation:-(Yi(y.get(v,e),x,b,!0)+Math.PI/2)},t),u.add(n),y.setItemGraphicEl(e,n)}if(g){var i=S(e,o),r=f.get(\"clip\");Wu(i,{shape:{endAngle:Yi(y.get(v,e),x,b,r)}},t),u.add(i),p[e]=i}})).update((function(e,n){if(d){var i=h.getItemGraphicEl(n),r=i?i.rotation:o,a=w(e,r);a.rotation=r,Hu(a,{rotation:-(Yi(y.get(v,e),x,b,!0)+Math.PI/2)},t),u.add(a),y.setItemGraphicEl(e,a)}if(g){var s=c[n],l=S(e,s?s.shape.endAngle:o),m=f.get(\"clip\");Hu(l,{shape:{endAngle:Yi(y.get(v,e),x,b,m)}},t),u.add(l),p[e]=l}})).execute(),y.each((function(t){var e=y.getItemModel(t),n=e.getModel(\"emphasis\");if(d){var r=y.getItemGraphicEl(t),o=y.getItemVisual(t,\"style\"),a=o.fill;if(r instanceof es){var s=r.style;r.useStyle(I({image:s.image,x:s.x,y:s.y,width:s.width,height:s.height},o))}else r.useStyle(o),\"pointer\"!==r.type&&r.setColor(a);r.setStyle(e.getModel([\"pointer\",\"itemStyle\"]).getItemStyle()),\"auto\"===r.style.fill&&r.setStyle(\"fill\",i(Yi(y.get(v,t),x,[0,1],!0))),r.z2EmphasisLift=0,cl(r,e),sl(r,n.get(\"focus\"),n.get(\"blurScope\"))}if(g){var l=p[t];l.useStyle(y.getItemVisual(t,\"style\")),l.setStyle(e.getModel([\"progress\",\"itemStyle\"]).getItemStyle()),l.z2EmphasisLift=0,cl(l,e),sl(l,n.get(\"focus\"),n.get(\"blurScope\"))}})),this._progressEls=p)},e.prototype._renderAnchor=function(t,e){var n=t.getModel(\"anchor\");if(n.get(\"show\")){var i=n.get(\"size\"),r=n.get(\"icon\"),o=n.get(\"offsetCenter\"),a=n.get(\"keepAspect\"),s=fy(r,e.cx-i/2+Zi(o[0],e.r),e.cy-i/2+Zi(o[1],e.r),i,i,null,a);s.z2=n.get(\"showAbove\")?1:0,s.setStyle(n.getModel(\"itemStyle\").getItemStyle()),this.group.add(s)}},e.prototype._renderTitleAndDetail=function(t,e,n,i,r){var o=this,a=t.getData(),s=a.mapDimension(\"value\"),l=+t.get(\"min\"),u=+t.get(\"max\"),h=new Ei,c=[],p=[],d=t.isAnimationEnabled();a.diff(this._data).add((function(t){c[t]=new cs({silent:!0}),p[t]=new cs({silent:!0})})).update((function(t,e){c[t]=o._titleEls[e],p[t]=o._detailEls[e]})).execute(),a.each((function(e){var n=a.getItemModel(e),o=a.get(s,e),f=new Ei,g=i(Yi(o,[l,u],[0,1],!0)),y=n.getModel(\"title\");if(y.get(\"show\")){var v=y.get(\"offsetCenter\"),m=r.cx+Zi(v[0],r.r),_=r.cy+Zi(v[1],r.r);(C=c[e]).attr({style:ph(y,{x:m,y:_,text:a.getName(e),align:\"center\",verticalAlign:\"middle\"},{inheritColor:g})}),f.add(C)}var x=n.getModel(\"detail\");if(x.get(\"show\")){var b=x.get(\"offsetCenter\"),w=r.cx+Zi(b[0],r.r),S=r.cy+Zi(b[1],r.r),M=Zi(x.get(\"width\"),r.r),I=Zi(x.get(\"height\"),r.r),T=t.get([\"progress\",\"show\"])?a.getItemVisual(e,\"style\").fill:g,C=p[e],D=x.get(\"formatter\");C.attr({style:ph(x,{x:w,y:S,text:lD(o,D),width:isNaN(M)?null:M,height:isNaN(I)?null:I,align:\"center\",verticalAlign:\"middle\"},{inheritColor:T})}),xh(C,{normal:x},o,(function(t){return lD(t,D)})),d&&bh(C,e,a,t,{getFormattedLabel:function(t,e,n,i,r,a){return lD(a?a.interpolatedValue:o,D)}}),f.add(C)}h.add(f)})),this.group.add(h),this._titleEls=c,this._detailEls=p},e.type=\"gauge\",e}(Tf),cD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath=\"itemStyle\",n.useColorPaletteOnData=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){return MS(this,[\"value\"])},e.type=\"series.gauge\",e.defaultOption={zlevel:0,z:2,center:[\"50%\",\"50%\"],legendHoverLink:!0,radius:\"75%\",startAngle:225,endAngle:-45,clockwise:!0,min:0,max:100,splitNumber:10,axisLine:{show:!0,roundCap:!1,lineStyle:{color:[[1,\"#E6EBF8\"]],width:10}},progress:{show:!1,overlap:!0,width:10,roundCap:!1,clip:!0},splitLine:{show:!0,length:10,distance:10,lineStyle:{color:\"#63677A\",width:3,type:\"solid\"}},axisTick:{show:!0,splitNumber:5,length:6,distance:10,lineStyle:{color:\"#63677A\",width:1,type:\"solid\"}},axisLabel:{show:!0,distance:15,color:\"#464646\",fontSize:12},pointer:{icon:null,offsetCenter:[0,0],show:!0,length:\"60%\",width:6,keepAspect:!1},anchor:{show:!1,showAbove:!1,size:6,icon:\"circle\",offsetCenter:[0,0],keepAspect:!1,itemStyle:{color:\"#fff\",borderWidth:0,borderColor:\"#5470c6\"}},title:{show:!0,offsetCenter:[0,\"20%\"],color:\"#464646\",fontSize:16,valueAnimation:!1},detail:{show:!0,backgroundColor:\"rgba(0,0,0,0)\",borderWidth:0,borderColor:\"#ccc\",width:100,height:null,padding:[5,10],offsetCenter:[0,\"40%\"],color:\"#464646\",fontSize:30,fontWeight:\"bold\",lineHeight:30,valueAnimation:!1}},e}(ff);var pD=[\"itemStyle\",\"opacity\"],dD=function(t){function e(e,n){var i=t.call(this)||this,r=i,o=new au,a=new cs;return r.setTextContent(a),i.setTextGuideLine(o),i.updateData(e,n,!0),i}return n(e,t),e.prototype.updateData=function(t,e,n){var i=this,r=t.hostModel,o=t.getItemModel(e),a=t.getItemLayout(e),s=o.getModel(\"emphasis\"),l=o.get(pD);l=null==l?1:l,i.useStyle(t.getItemVisual(e,\"style\")),i.style.lineJoin=\"round\",n?(i.setShape({points:a.points}),i.style.opacity=0,Wu(i,{style:{opacity:l}},r,e)):Hu(i,{style:{opacity:l},shape:{points:a.points}},r,e),cl(i,o),this._updateLabel(t,e),sl(this,s.get(\"focus\"),s.get(\"blurScope\"))},e.prototype._updateLabel=function(t,e){var n=this,i=this.getTextGuideLine(),r=n.getTextContent(),o=t.hostModel,a=t.getItemModel(e),s=t.getItemLayout(e).label,l=t.getItemVisual(e,\"style\"),u=l.fill;hh(r,ch(a),{labelFetcher:t.hostModel,labelDataIndex:e,defaultOpacity:l.opacity,defaultText:t.getName(e)},{normal:{align:s.textAlign,verticalAlign:s.verticalAlign}}),n.setTextConfig({local:!0,inside:!!s.inside,insideStroke:u,outsideFill:u});var h=s.linePoints;i.setShape({points:h}),n.textGuideLineConfig={anchor:h?new ai(h[0][0],h[0][1]):null},Hu(r,{style:{x:s.x,y:s.y}},o,e),r.attr({rotation:s.rotation,originX:s.x,originY:s.y,z2:10}),Hg(n,Wg(a),{stroke:u})},e}(ru),fD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.ignoreLabelLineUpdate=!0,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this._data,o=this.group;i.diff(r).add((function(t){var e=new dD(i,t);i.setItemGraphicEl(t,e),o.add(e)})).update((function(t,e){var n=r.getItemGraphicEl(e);n.updateData(i,t),o.add(n),i.setItemGraphicEl(t,n)})).remove((function(e){Yu(r.getItemGraphicEl(e),t,e)})).execute(),this._data=i},e.prototype.remove=function(){this.group.removeAll(),this._data=null},e.prototype.dispose=function(){},e.type=\"funnel\",e}(Tf),gD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.useColorPaletteOnData=!0,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new IS(V(this.getData,this),V(this.getRawData,this)),this._defaultLabelLine(e)},e.prototype.getInitialData=function(t,e){return MS(this,{coordDimensions:[\"value\"],encodeDefaulter:B(up,this)})},e.prototype._defaultLabelLine=function(t){br(t,\"labelLine\",[\"show\"]);var e=t.labelLine,n=t.emphasis.labelLine;e.show=e.show&&t.label.show,n.show=n.show&&t.emphasis.label.show},e.prototype.getDataParams=function(e){var n=this.getData(),i=t.prototype.getDataParams.call(this,e),r=n.mapDimension(\"value\"),o=n.getSum(r);return i.percent=o?+(n.get(r,e)/o*100).toFixed(2):0,i.$vars.push(\"percent\"),i},e.type=\"series.funnel\",e.defaultOption={zlevel:0,z:2,legendHoverLink:!0,left:80,top:60,right:80,bottom:60,minSize:\"0%\",maxSize:\"100%\",sort:\"descending\",orient:\"vertical\",gap:0,funnelAlign:\"center\",label:{show:!0,position:\"outer\"},labelLine:{show:!0,length:20,lineStyle:{width:1}},itemStyle:{borderColor:\"#fff\",borderWidth:1},emphasis:{label:{show:!0}},select:{itemStyle:{borderColor:\"#212121\"}}},e}(ff);function yD(t,e){t.eachSeriesByType(\"funnel\",(function(t){var n=t.getData(),i=n.mapDimension(\"value\"),r=t.get(\"sort\"),o=function(t,e){return Vc(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e),a=t.get(\"orient\"),s=o.width,l=o.height,u=function(t,e){for(var n=t.mapDimension(\"value\"),i=t.mapArray(n,(function(t){return t})),r=[],o=\"ascending\"===e,a=0,s=t.count();a<s;a++)r[a]=a;return\"function\"==typeof e?r.sort(e):\"none\"!==e&&r.sort((function(t,e){return o?i[t]-i[e]:i[e]-i[t]})),r}(n,r),h=o.x,c=o.y,p=\"horizontal\"===a?[Zi(t.get(\"minSize\"),l),Zi(t.get(\"maxSize\"),l)]:[Zi(t.get(\"minSize\"),s),Zi(t.get(\"maxSize\"),s)],d=n.getDataExtent(i),f=t.get(\"min\"),g=t.get(\"max\");null==f&&(f=Math.min(d[0],0)),null==g&&(g=d[1]);var y=t.get(\"funnelAlign\"),v=t.get(\"gap\"),m=((\"horizontal\"===a?s:l)-v*(n.count()-1))/n.count(),_=function(t,e){if(\"horizontal\"===a){var r=Yi(n.get(i,t)||0,[f,g],p,!0),o=void 0;switch(y){case\"top\":o=c;break;case\"center\":o=c+(l-r)/2;break;case\"bottom\":o=c+(l-r)}return[[e,o],[e,o+r]]}var u,d=Yi(n.get(i,t)||0,[f,g],p,!0);switch(y){case\"left\":u=h;break;case\"center\":u=h+(s-d)/2;break;case\"right\":u=h+s-d}return[[u,e],[u+d,e]]};\"ascending\"===r&&(m=-m,v=-v,\"horizontal\"===a?h+=s:c+=l,u=u.reverse());for(var x=0;x<u.length;x++){var b=u[x],w=u[x+1],S=n.getItemModel(b);if(\"horizontal\"===a){var M=S.get([\"itemStyle\",\"width\"]);null==M?M=m:(M=Zi(M,s),\"ascending\"===r&&(M=-M));var I=_(b,h),T=_(w,h+M);h+=M+v,n.setItemLayout(b,{points:I.concat(T.slice().reverse())})}else{var C=S.get([\"itemStyle\",\"height\"]);null==C?C=m:(C=Zi(C,l),\"ascending\"===r&&(C=-C));I=_(b,c),T=_(w,c+C);c+=C+v,n.setItemLayout(b,{points:I.concat(T.slice().reverse())})}}!function(t){var e=t.hostModel.get(\"orient\");t.each((function(n){var i,r,o,a,s=t.getItemModel(n),l=s.getModel(\"label\").get(\"position\"),u=s.getModel(\"labelLine\"),h=t.getItemLayout(n),c=h.points,p=\"inner\"===l||\"inside\"===l||\"center\"===l||\"insideLeft\"===l||\"insideRight\"===l;if(p)\"insideLeft\"===l?(r=(c[0][0]+c[3][0])/2+5,o=(c[0][1]+c[3][1])/2,i=\"left\"):\"insideRight\"===l?(r=(c[1][0]+c[2][0])/2-5,o=(c[1][1]+c[2][1])/2,i=\"right\"):(r=(c[0][0]+c[1][0]+c[2][0]+c[3][0])/4,o=(c[0][1]+c[1][1]+c[2][1]+c[3][1])/4,i=\"center\"),a=[[r,o],[r,o]];else{var d=void 0,f=void 0,g=void 0,y=void 0,v=u.get(\"length\");\"left\"===l?(d=(c[3][0]+c[0][0])/2,f=(c[3][1]+c[0][1])/2,r=(g=d-v)-5,i=\"right\"):\"right\"===l?(d=(c[1][0]+c[2][0])/2,f=(c[1][1]+c[2][1])/2,r=(g=d+v)+5,i=\"left\"):\"top\"===l?(d=(c[3][0]+c[0][0])/2,o=(y=(f=(c[3][1]+c[0][1])/2)-v)-5,i=\"center\"):\"bottom\"===l?(d=(c[1][0]+c[2][0])/2,o=(y=(f=(c[1][1]+c[2][1])/2)+v)+5,i=\"center\"):\"rightTop\"===l?(d=\"horizontal\"===e?c[3][0]:c[1][0],f=\"horizontal\"===e?c[3][1]:c[1][1],\"horizontal\"===e?(o=(y=f-v)-5,i=\"center\"):(r=(g=d+v)+5,i=\"top\")):\"rightBottom\"===l?(d=c[2][0],f=c[2][1],\"horizontal\"===e?(o=(y=f+v)+5,i=\"center\"):(r=(g=d+v)+5,i=\"bottom\")):\"leftTop\"===l?(d=c[0][0],f=\"horizontal\"===e?c[0][1]:c[1][1],\"horizontal\"===e?(o=(y=f-v)-5,i=\"center\"):(r=(g=d-v)-5,i=\"right\")):\"leftBottom\"===l?(d=\"horizontal\"===e?c[1][0]:c[3][0],f=\"horizontal\"===e?c[1][1]:c[2][1],\"horizontal\"===e?(o=(y=f+v)+5,i=\"center\"):(r=(g=d-v)-5,i=\"right\")):(d=(c[1][0]+c[2][0])/2,f=(c[1][1]+c[2][1])/2,\"horizontal\"===e?(o=(y=f+v)+5,i=\"center\"):(r=(g=d+v)+5,i=\"left\")),\"horizontal\"===e?r=g=d:o=y=f,a=[[d,f],[g,y]]}h.label={linePoints:a,x:r,y:o,verticalAlign:\"middle\",textAlign:i,inside:p}}))}(n)}))}var vD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._dataGroup=new Ei,n._initialized=!1,n}return n(e,t),e.prototype.init=function(){this.group.add(this._dataGroup)},e.prototype.render=function(t,e,n,i){var r=this._dataGroup,o=t.getData(),a=this._data,s=t.coordinateSystem,l=s.dimensions,u=xD(t);if(o.diff(a).add((function(t){bD(_D(o,r,t,l,s),o,t,u)})).update((function(e,n){var i=a.getItemGraphicEl(n),r=mD(o,e,l,s);o.setItemGraphicEl(e,i),Hu(i,{shape:{points:r}},t,e),bD(i,o,e,u)})).remove((function(t){var e=a.getItemGraphicEl(t);r.remove(e)})).execute(),!this._initialized){this._initialized=!0;var h=function(t,e,n){var i=t.model,r=t.getRect(),o=new ls({shape:{x:r.x,y:r.y,width:r.width,height:r.height}}),a=\"horizontal\"===i.get(\"layout\")?\"width\":\"height\";return o.setShape(a,0),Wu(o,{shape:{width:r.width,height:r.height}},e,n),o}(s,t,(function(){setTimeout((function(){r.removeClipPath()}))}));r.setClipPath(h)}this._data=o},e.prototype.incrementalPrepareRender=function(t,e,n){this._initialized=!0,this._data=null,this._dataGroup.removeAll()},e.prototype.incrementalRender=function(t,e,n){for(var i=e.getData(),r=e.coordinateSystem,o=r.dimensions,a=xD(e),s=t.start;s<t.end;s++){var l=_D(i,this._dataGroup,s,o,r);l.incremental=!0,bD(l,i,s,a)}},e.prototype.remove=function(){this._dataGroup&&this._dataGroup.removeAll(),this._data=null},e.type=\"parallel\",e}(Tf);function mD(t,e,n,i){for(var r,o=[],a=0;a<n.length;a++){var s=n[a],l=t.get(t.mapDimension(s),e);r=l,(\"category\"===i.getAxis(s).type?null==r:null==r||isNaN(r))||o.push(i.dataToPoint(l,s))}return o}function _D(t,e,n,i,r){var o=mD(t,n,i,r),a=new au({shape:{points:o},z2:10});return e.add(a),t.setItemGraphicEl(n,a),a}function xD(t){var e=t.get(\"smooth\",!0);return!0===e&&(e=.3),J(e=cr(e))&&(e=0),{smooth:e}}function bD(t,e,n,i){t.useStyle(e.getItemVisual(n,\"style\")),t.style.fill=null,t.setShape(\"smooth\",i.smooth);var r=e.getItemModel(n),o=r.getModel(\"emphasis\");cl(t,r,\"lineStyle\"),sl(t,o.get(\"focus\"),o.get(\"blurScope\"))}var wD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath=\"lineStyle\",n.visualDrawType=\"stroke\",n}return n(e,t),e.prototype.getInitialData=function(t,e){return F_(this.getSource(),this,{useEncodeDefaulter:V(SD,null,this)})},e.prototype.getRawIndicesByActiveState=function(t){var e=this.coordinateSystem,n=this.getData(),i=[];return e.eachActiveState(n,(function(e,r){t===e&&i.push(n.getRawIndex(r))})),i},e.type=\"series.parallel\",e.dependencies=[\"parallel\"],e.defaultOption={zlevel:0,z:2,coordinateSystem:\"parallel\",parallelIndex:0,label:{show:!1},inactiveOpacity:.05,activeOpacity:1,lineStyle:{width:1,opacity:.45,type:\"solid\"},emphasis:{label:{show:!1}},progressive:500,smooth:!1,animationEasing:\"linear\"},e}(ff);function SD(t){var e=t.ecModel.getComponent(\"parallel\",t.get(\"parallelIndex\"));if(e){var n={};return P(e.dimensions,(function(t){var e=+t.replace(\"dim\",\"\");n[t]=e})),n}}var MD=[\"lineStyle\",\"opacity\"],ID={seriesType:\"parallel\",reset:function(t,e){var n=t.coordinateSystem,i={normal:t.get([\"lineStyle\",\"opacity\"]),active:t.get(\"activeOpacity\"),inactive:t.get(\"inactiveOpacity\")};return{progress:function(t,e){n.eachActiveState(e,(function(t,n){var r=i[t];if(\"normal\"===t&&e.hasItemOption){var o=e.getItemModel(n).get(MD,!0);null!=o&&(r=o)}e.ensureUniqueItemVisual(n,\"style\").opacity=r}),t.start,t.end)}}}};function TD(t){!function(t){if(t.parallel)return;var e=!1;P(t.series,(function(t){t&&\"parallel\"===t.type&&(e=!0)})),e&&(t.parallel=[{}])}(t),function(t){P(xr(t.parallelAxis),(function(e){if(X(e)){var n=e.parallelIndex||0,i=xr(t.parallel)[n];i&&i.parallelAxisDefault&&S(e,i.parallelAxisDefault,!1)}}))}(t)}var CD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this._model=t,this._api=n,this._handlers||(this._handlers={},P(DD,(function(t,e){n.getZr().on(e,this._handlers[e]=V(t,this))}),this)),zf(this,\"_throttledDispatchExpand\",t.get(\"axisExpandRate\"),\"fixRate\")},e.prototype.dispose=function(t,e){P(this._handlers,(function(t,n){e.getZr().off(n,t)})),this._handlers=null},e.prototype._throttledDispatchExpand=function(t){this._dispatchExpand(t)},e.prototype._dispatchExpand=function(t){t&&this._api.dispatchAction(I({type:\"parallelAxisExpand\"},t))},e.type=\"parallel\",e}(wf),DD={mousedown:function(t){AD(this,\"click\")&&(this._mouseDownPoint=[t.offsetX,t.offsetY])},mouseup:function(t){var e=this._mouseDownPoint;if(AD(this,\"click\")&&e){var n=[t.offsetX,t.offsetY];if(Math.pow(e[0]-n[0],2)+Math.pow(e[1]-n[1],2)>5)return;var i=this._model.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]);\"none\"!==i.behavior&&this._dispatchExpand({axisExpandWindow:i.axisExpandWindow})}this._mouseDownPoint=null},mousemove:function(t){if(!this._mouseDownPoint&&AD(this,\"mousemove\")){var e=this._model,n=e.coordinateSystem.getSlidedAxisExpandWindow([t.offsetX,t.offsetY]),i=n.behavior;\"jump\"===i&&this._throttledDispatchExpand.debounceNextCall(e.get(\"axisExpandDebounce\")),this._throttledDispatchExpand(\"none\"===i?null:{axisExpandWindow:n.axisExpandWindow,animation:\"jump\"===i?null:{duration:0}})}}};function AD(t,e){var n=t._model;return n.get(\"axisExpandable\")&&n.get(\"axisExpandTriggerOn\")===e}var LD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){t.prototype.init.apply(this,arguments),this.mergeOption({})},e.prototype.mergeOption=function(t){var e=this.option;t&&S(e,t,!0),this._initDimensions()},e.prototype.contains=function(t,e){var n=t.get(\"parallelIndex\");return null!=n&&e.getComponent(\"parallel\",n)===this},e.prototype.setAxisExpand=function(t){P([\"axisExpandable\",\"axisExpandCenter\",\"axisExpandCount\",\"axisExpandWidth\",\"axisExpandWindow\"],(function(e){t.hasOwnProperty(e)&&(this.option[e]=t[e])}),this)},e.prototype._initDimensions=function(){var t=this.dimensions=[],e=this.parallelAxisIndex=[];P(N(this.ecModel.queryComponents({mainType:\"parallelAxis\"}),(function(t){return(t.get(\"parallelIndex\")||0)===this.componentIndex}),this),(function(n){t.push(\"dim\"+n.get(\"dim\")),e.push(n.componentIndex)}))},e.type=\"parallel\",e.dependencies=[\"parallelAxis\"],e.layoutMode=\"box\",e.defaultOption={zlevel:0,z:0,left:80,top:60,right:80,bottom:60,layout:\"horizontal\",axisExpandable:!1,axisExpandCenter:null,axisExpandCount:0,axisExpandWidth:50,axisExpandRate:17,axisExpandDebounce:50,axisExpandSlideTriggerArea:[-.15,.05,.4],axisExpandTriggerOn:\"click\",parallelAxisDefault:null},e}(Xc),kD=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.type=r||\"value\",a.axisIndex=o,a}return n(e,t),e.prototype.isHorizontal=function(){return\"horizontal\"!==this.coordinateSystem.getModel().get(\"layout\")},e}(hb);function PD(t,e,n,i,r,o){t=t||0;var a=n[1]-n[0];if(null!=r&&(r=RD(r,[0,a])),null!=o&&(o=Math.max(o,null!=r?r:0)),\"all\"===i){var s=Math.abs(e[1]-e[0]);s=RD(s,[0,a]),r=o=RD(s,[r,o]),i=0}e[0]=RD(e[0],n),e[1]=RD(e[1],n);var l=OD(e,i);e[i]+=t;var u,h=r||0,c=n.slice();return l.sign<0?c[0]+=h:c[1]-=h,e[i]=RD(e[i],c),u=OD(e,i),null!=r&&(u.sign!==l.sign||u.span<r)&&(e[1-i]=e[i]+l.sign*r),u=OD(e,i),null!=o&&u.span>o&&(e[1-i]=e[i]+u.sign*o),e}function OD(t,e){var n=t[e]-t[1-e];return{span:Math.abs(n),sign:n>0?-1:n<0?1:e?-1:1}}function RD(t,e){return Math.min(null!=e[1]?e[1]:1/0,Math.max(null!=e[0]?e[0]:-1/0,t))}var ND=P,zD=Math.min,ED=Math.max,VD=Math.floor,BD=Math.ceil,FD=ji,GD=Math.PI,HD=function(){function t(t,e,n){this.type=\"parallel\",this._axesMap=ht(),this._axesLayout={},this.dimensions=t.dimensions,this._model=t,this._init(t,e,n)}return t.prototype._init=function(t,e,n){var i=t.dimensions,r=t.parallelAxisIndex;ND(i,(function(t,n){var i=r[n],o=e.getComponent(\"parallelAxis\",i),a=this._axesMap.set(t,new kD(t,Bx(o),[0,0],o.get(\"type\"),i)),s=\"category\"===a.type;a.onBand=s&&o.get(\"boundaryGap\"),a.inverse=o.get(\"inverse\"),o.axis=a,a.model=o,a.coordinateSystem=o.coordinateSystem=this}),this)},t.prototype.update=function(t,e){this._updateAxesFromSeries(this._model,t)},t.prototype.containPoint=function(t){var e=this._makeLayoutInfo(),n=e.axisBase,i=e.layoutBase,r=e.pixelDimIndex,o=t[1-r],a=t[r];return o>=n&&o<=n+e.axisLength&&a>=i&&a<=i+e.layoutLength},t.prototype.getModel=function(){return this._model},t.prototype._updateAxesFromSeries=function(t,e){e.eachSeries((function(n){if(t.contains(n,e)){var i=n.getData();ND(this.dimensions,(function(t){var e=this._axesMap.get(t);e.scale.unionExtentFromData(i,i.mapDimension(t)),Vx(e.scale,e.model)}),this)}}),this)},t.prototype.resize=function(t,e){this._rect=Vc(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()}),this._layoutAxes()},t.prototype.getRect=function(){return this._rect},t.prototype._makeLayoutInfo=function(){var t,e=this._model,n=this._rect,i=[\"x\",\"y\"],r=[\"width\",\"height\"],o=e.get(\"layout\"),a=\"horizontal\"===o?0:1,s=n[r[a]],l=[0,s],u=this.dimensions.length,h=WD(e.get(\"axisExpandWidth\"),l),c=WD(e.get(\"axisExpandCount\")||0,[0,u]),p=e.get(\"axisExpandable\")&&u>3&&u>c&&c>1&&h>0&&s>0,d=e.get(\"axisExpandWindow\");d?(t=WD(d[1]-d[0],l),d[1]=d[0]+t):(t=WD(h*(c-1),l),(d=[h*(e.get(\"axisExpandCenter\")||VD(u/2))-t/2])[1]=d[0]+t);var f=(s-t)/(u-c);f<3&&(f=0);var g=[VD(FD(d[0]/h,1))+1,BD(FD(d[1]/h,1))-1],y=f/h*d[0];return{layout:o,pixelDimIndex:a,layoutBase:n[i[a]],layoutLength:s,axisBase:n[i[1-a]],axisLength:n[r[1-a]],axisExpandable:p,axisExpandWidth:h,axisCollapseWidth:f,axisExpandWindow:d,axisCount:u,winInnerIndices:g,axisExpandWindow0Pos:y}},t.prototype._layoutAxes=function(){var t=this._rect,e=this._axesMap,n=this.dimensions,i=this._makeLayoutInfo(),r=i.layout;e.each((function(t){var e=[0,i.axisLength],n=t.inverse?1:0;t.setExtent(e[n],e[1-n])})),ND(n,(function(e,n){var o=(i.axisExpandable?XD:UD)(n,i),a={horizontal:{x:o.position,y:i.axisLength},vertical:{x:0,y:o.position}},s={horizontal:GD/2,vertical:0},l=[a[r].x+t.x,a[r].y+t.y],u=s[r],h=[1,0,0,1,0,0];Xn(h,h,u),Un(h,h,l),this._axesLayout[e]={position:l,rotation:u,transform:h,axisNameAvailableWidth:o.axisNameAvailableWidth,axisLabelShow:o.axisLabelShow,nameTruncateMaxWidth:o.nameTruncateMaxWidth,tickDirection:1,labelDirection:1}}),this)},t.prototype.getAxis=function(t){return this._axesMap.get(t)},t.prototype.dataToPoint=function(t,e){return this.axisCoordToPoint(this._axesMap.get(e).dataToCoord(t),e)},t.prototype.eachActiveState=function(t,e,n,i){null==n&&(n=0),null==i&&(i=t.count());var r=this._axesMap,o=this.dimensions,a=[],s=[];P(o,(function(e){a.push(t.mapDimension(e)),s.push(r.get(e).model)}));for(var l=this.hasAxisBrushed(),u=n;u<i;u++){var h=void 0;if(l){h=\"active\";for(var c=t.getValues(a,u),p=0,d=o.length;p<d;p++){if(\"inactive\"===s[p].getActiveState(c[p])){h=\"inactive\";break}}}else h=\"normal\";e(h,u)}},t.prototype.hasAxisBrushed=function(){for(var t=this.dimensions,e=this._axesMap,n=!1,i=0,r=t.length;i<r;i++)\"normal\"!==e.get(t[i]).model.getActiveState()&&(n=!0);return n},t.prototype.axisCoordToPoint=function(t,e){return qu([t,0],this._axesLayout[e].transform)},t.prototype.getAxisLayout=function(t){return w(this._axesLayout[t])},t.prototype.getSlidedAxisExpandWindow=function(t){var e=this._makeLayoutInfo(),n=e.pixelDimIndex,i=e.axisExpandWindow.slice(),r=i[1]-i[0],o=[0,e.axisExpandWidth*(e.axisCount-1)];if(!this.containPoint(t))return{behavior:\"none\",axisExpandWindow:i};var a,s=t[n]-e.layoutBase-e.axisExpandWindow0Pos,l=\"slide\",u=e.axisCollapseWidth,h=this._model.get(\"axisExpandSlideTriggerArea\"),c=null!=h[0];if(u)c&&u&&s<r*h[0]?(l=\"jump\",a=s-r*h[2]):c&&u&&s>r*(1-h[0])?(l=\"jump\",a=s-r*(1-h[2])):(a=s-r*h[1])>=0&&(a=s-r*(1-h[1]))<=0&&(a=0),(a*=e.axisExpandWidth/u)?PD(a,i,o,\"all\"):l=\"none\";else{var p=i[1]-i[0];(i=[ED(0,o[1]*s/p-p/2)])[1]=zD(o[1],i[0]+p),i[0]=i[1]-p}return{axisExpandWindow:i,behavior:l}},t}();function WD(t,e){return zD(ED(t,e[0]),e[1])}function UD(t,e){var n=e.layoutLength/(e.axisCount-1);return{position:n*t,axisNameAvailableWidth:n,axisLabelShow:!0}}function XD(t,e){var n,i,r=e.layoutLength,o=e.axisExpandWidth,a=e.axisCount,s=e.axisCollapseWidth,l=e.winInnerIndices,u=s,h=!1;return t<l[0]?(n=t*s,i=s):t<=l[1]?(n=e.axisExpandWindow0Pos+t*o-e.axisExpandWindow[0],u=o,h=!0):(n=r-(a-1-t)*s,i=s),{position:n,axisNameAvailableWidth:u,axisLabelShow:h,nameTruncateMaxWidth:i}}var YD={create:function(t,e){var n=[];return t.eachComponent(\"parallel\",(function(i,r){var o=new HD(i,t,e);o.name=\"parallel_\"+r,o.resize(i,e),i.coordinateSystem=o,o.model=i,n.push(o)})),t.eachSeries((function(t){if(\"parallel\"===t.get(\"coordinateSystem\")){var e=t.getReferringComponents(\"parallel\",Nr).models[0];t.coordinateSystem=e.coordinateSystem}})),n}},ZD=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.activeIntervals=[],n}return n(e,t),e.prototype.getAreaSelectStyle=function(){return $r([[\"fill\",\"color\"],[\"lineWidth\",\"borderWidth\"],[\"stroke\",\"borderColor\"],[\"width\",\"width\"],[\"opacity\",\"opacity\"]])(this.getModel(\"areaSelectStyle\"))},e.prototype.setActiveIntervals=function(t){var e=this.activeIntervals=w(t);if(e)for(var n=e.length-1;n>=0;n--)qi(e[n])},e.prototype.getActiveState=function(t){var e=this.activeIntervals;if(!e.length)return\"normal\";if(null==t||isNaN(+t))return\"inactive\";if(1===e.length){var n=e[0];if(n[0]<=t&&t<=n[1])return\"active\"}else for(var i=0,r=e.length;i<r;i++)if(e[i][0]<=t&&t<=e[i][1])return\"active\";return\"inactive\"},e}(Xc);L(ZD,Yx);var jD=!0,qD=Math.min,KD=Math.max,$D=Math.pow,JD=\"globalPan\",QD={w:[0,0],e:[0,1],n:[1,0],s:[1,1]},tA={w:\"ew\",e:\"ew\",n:\"ns\",s:\"ns\",ne:\"nesw\",sw:\"nesw\",nw:\"nwse\",se:\"nwse\"},eA={brushStyle:{lineWidth:2,stroke:\"rgba(210,219,238,0.3)\",fill:\"#D2DBEE\"},transformable:!0,brushMode:\"single\",removeOnClick:!1},nA=0,iA=function(t){function e(e){var n=t.call(this)||this;return n._track=[],n._covers=[],n._handlers={},n._zr=e,n.group=new Ei,n._uid=\"brushController_\"+nA++,P(LA,(function(t,e){this._handlers[e]=V(t,this)}),n),n}return n(e,t),e.prototype.enableBrush=function(t){return this._brushType&&this._doDisableBrush(),t.brushType&&this._doEnableBrush(t),this},e.prototype._doEnableBrush=function(t){var e=this._zr;this._enableGlobalPan||function(t,e,n){HM(t)[e]=n}(e,JD,this._uid),P(this._handlers,(function(t,n){e.on(n,t)})),this._brushType=t.brushType,this._brushOption=S(w(eA),t,!0)},e.prototype._doDisableBrush=function(){var t=this._zr;!function(t,e,n){var i=HM(t);i[e]===n&&(i[e]=null)}(t,JD,this._uid),P(this._handlers,(function(e,n){t.off(n,e)})),this._brushType=this._brushOption=null},e.prototype.setPanels=function(t){if(t&&t.length){var e=this._panels={};P(t,(function(t){e[t.panelId]=w(t)}))}else this._panels=null;return this},e.prototype.mount=function(t){t=t||{},this._enableGlobalPan=t.enableGlobalPan;var e=this.group;return this._zr.add(e),e.attr({x:t.x||0,y:t.y||0,rotation:t.rotation||0,scaleX:t.scaleX||1,scaleY:t.scaleY||1}),this._transform=e.getLocalTransform(),this},e.prototype.updateCovers=function(t){t=O(t,(function(t){return S(w(eA),t,!0)}));var e=this._covers,n=this._covers=[],i=this,r=this._creatingCover;return new n_(e,t,(function(t,e){return o(t.__brushOption,e)}),o).add(a).update(a).remove((function(t){e[t]!==r&&i.group.remove(e[t])})).execute(),this;function o(t,e){return(null!=t.id?t.id:\"\\0-brush-index-\"+e)+\"-\"+t.brushType}function a(o,a){var s=t[o];if(null!=a&&e[a]===r)n[o]=e[a];else{var l=n[o]=null!=a?(e[a].__brushOption=s,e[a]):oA(i,rA(i,s));lA(i,l)}}},e.prototype.unmount=function(){return this.enableBrush(!1),pA(this),this._zr.remove(this.group),this},e.prototype.dispose=function(){this.unmount(),this.off()},e}(Ft);function rA(t,e){var n=PA[e.brushType].createCover(t,e);return n.__brushOption=e,sA(n,e),t.group.add(n),n}function oA(t,e){var n=uA(e);return n.endCreating&&(n.endCreating(t,e),sA(e,e.__brushOption)),e}function aA(t,e){var n=e.__brushOption;uA(e).updateCoverShape(t,e,n.range,n)}function sA(t,e){var n=e.z;null==n&&(n=1e4),t.traverse((function(t){t.z=n,t.z2=n}))}function lA(t,e){uA(e).updateCommon(t,e),aA(t,e)}function uA(t){return PA[t.__brushOption.brushType]}function hA(t,e,n){var i,r=t._panels;if(!r)return jD;var o=t._transform;return P(r,(function(t){t.isTargetByCursor(e,n,o)&&(i=t)})),i}function cA(t,e){var n=t._panels;if(!n)return jD;var i=e.__brushOption.panelId;return null!=i?n[i]:jD}function pA(t){var e=t._covers,n=e.length;return P(e,(function(e){t.group.remove(e)}),t),e.length=0,!!n}function dA(t,e){var n=O(t._covers,(function(t){var e=t.__brushOption,n=w(e.range);return{brushType:e.brushType,panelId:e.panelId,range:n}}));t.trigger(\"brush\",{areas:n,isEnd:!!e.isEnd,removeOnClick:!!e.removeOnClick})}function fA(t){var e=t.length-1;return e<0&&(e=0),[t[0],t[e]]}function gA(t,e,n,i){var r=new Ei;return r.add(new ls({name:\"main\",style:_A(n),silent:!0,draggable:!0,cursor:\"move\",drift:B(wA,t,e,r,[\"n\",\"s\",\"w\",\"e\"]),ondragend:B(dA,e,{isEnd:!0})})),P(i,(function(n){r.add(new ls({name:n.join(\"\"),style:{opacity:0},draggable:!0,silent:!0,invisible:!0,drift:B(wA,t,e,r,n),ondragend:B(dA,e,{isEnd:!0})}))})),r}function yA(t,e,n,i){var r=i.brushStyle.lineWidth||0,o=KD(r,6),a=n[0][0],s=n[1][0],l=a-r/2,u=s-r/2,h=n[0][1],c=n[1][1],p=h-o+r/2,d=c-o+r/2,f=h-a,g=c-s,y=f+r,v=g+r;mA(t,e,\"main\",a,s,f,g),i.transformable&&(mA(t,e,\"w\",l,u,o,v),mA(t,e,\"e\",p,u,o,v),mA(t,e,\"n\",l,u,y,o),mA(t,e,\"s\",l,d,y,o),mA(t,e,\"nw\",l,u,o,o),mA(t,e,\"ne\",p,u,o,o),mA(t,e,\"sw\",l,d,o,o),mA(t,e,\"se\",p,d,o,o))}function vA(t,e){var n=e.__brushOption,i=n.transformable,r=e.childAt(0);r.useStyle(_A(n)),r.attr({silent:!i,cursor:i?\"move\":\"default\"}),P([[\"w\"],[\"e\"],[\"n\"],[\"s\"],[\"s\",\"e\"],[\"s\",\"w\"],[\"n\",\"e\"],[\"n\",\"w\"]],(function(n){var r=e.childOfName(n.join(\"\")),o=1===n.length?bA(t,n[0]):function(t,e){var n=[bA(t,e[0]),bA(t,e[1])];return(\"e\"===n[0]||\"w\"===n[0])&&n.reverse(),n.join(\"\")}(t,n);r&&r.attr({silent:!i,invisible:!i,cursor:i?tA[o]+\"-resize\":null})}))}function mA(t,e,n,i,r,o,a){var s=e.childOfName(n);s&&s.setShape(function(t){var e=qD(t[0][0],t[1][0]),n=qD(t[0][1],t[1][1]),i=KD(t[0][0],t[1][0]),r=KD(t[0][1],t[1][1]);return{x:e,y:n,width:i-e,height:r-n}}(IA(t,e,[[i,r],[i+o,r+a]])))}function _A(t){return T({strokeNoScale:!0},t.brushStyle)}function xA(t,e,n,i){var r=[qD(t,n),qD(e,i)],o=[KD(t,n),KD(e,i)];return[[r[0],o[0]],[r[1],o[1]]]}function bA(t,e){return{left:\"w\",right:\"e\",top:\"n\",bottom:\"s\"}[Ku({w:\"left\",e:\"right\",n:\"top\",s:\"bottom\"}[e],function(t){return ju(t.group)}(t))]}function wA(t,e,n,i,r,o){var a=n.__brushOption,s=t.toRectRange(a.range),l=MA(e,r,o);P(i,(function(t){var e=QD[t];s[e[0]][e[1]]+=l[e[0]]})),a.range=t.fromRectRange(xA(s[0][0],s[1][0],s[0][1],s[1][1])),lA(e,n),dA(e,{isEnd:!1})}function SA(t,e,n,i){var r=e.__brushOption.range,o=MA(t,n,i);P(r,(function(t){t[0]+=o[0],t[1]+=o[1]})),lA(t,e),dA(t,{isEnd:!1})}function MA(t,e,n){var i=t.group,r=i.transformCoordToLocal(e,n),o=i.transformCoordToLocal(0,0);return[r[0]-o[0],r[1]-o[1]]}function IA(t,e,n){var i=cA(t,e);return i&&i!==jD?i.clipPath(n,t._transform):w(n)}function TA(t){var e=t.event;e.preventDefault&&e.preventDefault()}function CA(t,e,n){return t.childOfName(\"main\").contain(e,n)}function DA(t,e,n,i){var r,o=t._creatingCover,a=t._creatingPanel,s=t._brushOption;if(t._track.push(n.slice()),function(t){var e=t._track;if(!e.length)return!1;var n=e[e.length-1],i=e[0],r=n[0]-i[0],o=n[1]-i[1];return $D(r*r+o*o,.5)>6}(t)||o){if(a&&!o){\"single\"===s.brushMode&&pA(t);var l=w(s);l.brushType=AA(l.brushType,a),l.panelId=a===jD?null:a.panelId,o=t._creatingCover=rA(t,l),t._covers.push(o)}if(o){var u=PA[AA(t._brushType,a)];o.__brushOption.range=u.getCreatingRange(IA(t,o,t._track)),i&&(oA(t,o),u.updateCommon(t,o)),aA(t,o),r={isEnd:i}}}else i&&\"single\"===s.brushMode&&s.removeOnClick&&hA(t,e,n)&&pA(t)&&(r={isEnd:i,removeOnClick:!0});return r}function AA(t,e){return\"auto\"===t?e.defaultBrushType:t}var LA={mousedown:function(t){if(this._dragging)kA(this,t);else if(!t.target||!t.target.draggable){TA(t);var e=this.group.transformCoordToLocal(t.offsetX,t.offsetY);this._creatingCover=null,(this._creatingPanel=hA(this,t,e))&&(this._dragging=!0,this._track=[e.slice()])}},mousemove:function(t){var e=t.offsetX,n=t.offsetY,i=this.group.transformCoordToLocal(e,n);if(function(t,e,n){if(t._brushType&&!function(t,e,n){var i=t._zr;return e<0||e>i.getWidth()||n<0||n>i.getHeight()}(t,e.offsetX,e.offsetY)){var i=t._zr,r=t._covers,o=hA(t,e,n);if(!t._dragging)for(var a=0;a<r.length;a++){var s=r[a].__brushOption;if(o&&(o===jD||s.panelId===o.panelId)&&PA[s.brushType].contain(r[a],n[0],n[1]))return}o&&i.setCursorStyle(\"crosshair\")}}(this,t,i),this._dragging){TA(t);var r=DA(this,t,i,!1);r&&dA(this,r)}},mouseup:function(t){kA(this,t)}};function kA(t,e){if(t._dragging){TA(e);var n=e.offsetX,i=e.offsetY,r=t.group.transformCoordToLocal(n,i),o=DA(t,e,r,!0);t._dragging=!1,t._track=[],t._creatingCover=null,o&&dA(t,o)}}var PA={lineX:OA(0),lineY:OA(1),rect:{createCover:function(t,e){function n(t){return t}return gA({toRectRange:n,fromRectRange:n},t,e,[[\"w\"],[\"e\"],[\"n\"],[\"s\"],[\"s\",\"e\"],[\"s\",\"w\"],[\"n\",\"e\"],[\"n\",\"w\"]])},getCreatingRange:function(t){var e=fA(t);return xA(e[1][0],e[1][1],e[0][0],e[0][1])},updateCoverShape:function(t,e,n,i){yA(t,e,n,i)},updateCommon:vA,contain:CA},polygon:{createCover:function(t,e){var n=new Ei;return n.add(new au({name:\"main\",style:_A(e),silent:!0})),n},getCreatingRange:function(t){return t},endCreating:function(t,e){e.remove(e.childAt(0)),e.add(new ru({name:\"main\",draggable:!0,drift:B(SA,t,e),ondragend:B(dA,t,{isEnd:!0})}))},updateCoverShape:function(t,e,n,i){e.childAt(0).setShape({points:IA(t,e,n)})},updateCommon:vA,contain:CA}};function OA(t){return{createCover:function(e,n){return gA({toRectRange:function(e){var n=[e,[0,100]];return t&&n.reverse(),n},fromRectRange:function(e){return e[t]}},e,n,[[[\"w\"],[\"e\"]],[[\"n\"],[\"s\"]]][t])},getCreatingRange:function(e){var n=fA(e);return[qD(n[0][t],n[1][t]),KD(n[0][t],n[1][t])]},updateCoverShape:function(e,n,i,r){var o,a=cA(e,n);if(a!==jD&&a.getLinearBrushOtherExtent)o=a.getLinearBrushOtherExtent(t);else{var s=e._zr;o=[0,[s.getWidth(),s.getHeight()][1-t]]}var l=[i,o];t&&l.reverse(),yA(e,n,l,r)},updateCommon:vA,contain:CA}}function RA(t){return t=EA(t),function(e){return Qu(e,t)}}function NA(t,e){return t=EA(t),function(n){var i=null!=e?e:n,r=i?t.width:t.height,o=i?t.x:t.y;return[o,o+(r||0)]}}function zA(t,e,n){var i=EA(t);return function(t,r){return i.contain(r[0],r[1])&&!KM(t,e,n)}}function EA(t){return gi.create(t)}var VA=[\"axisLine\",\"axisTickLabel\",\"axisName\"],BA=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(e,n){t.prototype.init.apply(this,arguments),(this._brushController=new iA(n.getZr())).on(\"brush\",V(this._onBrush,this))},e.prototype.render=function(t,e,n,i){if(!function(t,e,n){return n&&\"axisAreaSelect\"===n.type&&e.findComponents({mainType:\"parallelAxis\",query:n})[0]===t}(t,e,i)){this.axisModel=t,this.api=n,this.group.removeAll();var r=this._axisGroup;if(this._axisGroup=new Ei,this.group.add(this._axisGroup),t.get(\"show\")){var o=function(t,e){return e.getComponent(\"parallel\",t.get(\"parallelIndex\"))}(t,e),a=o.coordinateSystem,s=t.getAreaSelectStyle(),l=s.width,u=t.axis.dim,h=I({strokeContainThreshold:l},a.getAxisLayout(u)),c=new tM(t,h);P(VA,c.add,c),this._axisGroup.add(c.getGroup()),this._refreshBrushController(h,s,t,o,l,n),Ju(r,this._axisGroup,t)}}},e.prototype._refreshBrushController=function(t,e,n,i,r,o){var a=n.axis.getExtent(),s=a[1]-a[0],l=Math.min(30,.1*Math.abs(s)),u=gi.create({x:a[0],y:-r/2,width:s,height:r});u.x-=l,u.width+=2*l,this._brushController.mount({enableGlobalPan:!0,rotation:t.rotation,x:t.position[0],y:t.position[1]}).setPanels([{panelId:\"pl\",clipPath:RA(u),isTargetByCursor:zA(u,o,i),getLinearBrushOtherExtent:NA(u,0)}]).enableBrush({brushType:\"lineX\",brushStyle:e,removeOnClick:!0}).updateCovers(function(t){var e=t.axis;return O(t.activeIntervals,(function(t){return{brushType:\"lineX\",panelId:\"pl\",range:[e.dataToCoord(t[0],!0),e.dataToCoord(t[1],!0)]}}))}(n))},e.prototype._onBrush=function(t){var e=t.areas,n=this.axisModel,i=n.axis,r=O(e,(function(t){return[i.coordToData(t.range[0],!0),i.coordToData(t.range[1],!0)]}));(!n.option.realtime===t.isEnd||t.removeOnClick)&&this.api.dispatchAction({type:\"axisAreaSelect\",parallelAxisId:n.id,intervals:r})},e.prototype.dispose=function(){this._brushController.dispose()},e.type=\"parallelAxis\",e}(wf);var FA={type:\"axisAreaSelect\",event:\"axisAreaSelected\"};var GA={type:\"value\",areaSelectStyle:{width:20,borderWidth:1,borderColor:\"rgba(160,197,232)\",color:\"rgba(160,197,232)\",opacity:.3},realtime:!0,z:10};function HA(t){t.registerComponentView(CD),t.registerComponentModel(LD),t.registerCoordinateSystem(\"parallel\",YD),t.registerPreprocessor(TD),t.registerComponentModel(ZD),t.registerComponentView(BA),BS(t,\"parallel\",ZD,GA),function(t){t.registerAction(FA,(function(t,e){e.eachComponent({mainType:\"parallelAxis\",query:t},(function(e){e.axis.model.setActiveIntervals(t.intervals)}))})),t.registerAction(\"parallelAxisExpand\",(function(t,e){e.eachComponent({mainType:\"parallel\",query:t},(function(e){e.setAxisExpand(t)}))}))}(t)}var WA=function(){this.x1=0,this.y1=0,this.x2=0,this.y2=0,this.cpx1=0,this.cpy1=0,this.cpx2=0,this.cpy2=0,this.extent=0},UA=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultShape=function(){return new WA},e.prototype.buildPath=function(t,e){var n=e.extent;t.moveTo(e.x1,e.y1),t.bezierCurveTo(e.cpx1,e.cpy1,e.cpx2,e.cpy2,e.x2,e.y2),\"vertical\"===e.orient?(t.lineTo(e.x2+n,e.y2),t.bezierCurveTo(e.cpx2+n,e.cpy2,e.cpx1+n,e.cpy1,e.x1+n,e.y1)):(t.lineTo(e.x2,e.y2+n),t.bezierCurveTo(e.cpx2,e.cpy2+n,e.cpx1,e.cpy1+n,e.x1,e.y1+n)),t.closePath()},e.prototype.highlight=function(){js(this)},e.prototype.downplay=function(){qs(this)},e}(Ka),XA=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._focusAdjacencyDisabled=!1,n}return n(e,t),e.prototype.render=function(t,e,n){var i=this,r=t.getGraph(),o=this.group,a=t.layoutInfo,s=a.width,l=a.height,u=t.getData(),h=t.getData(\"edge\"),c=t.get(\"orient\");this._model=t,o.removeAll(),o.x=a.x,o.y=a.y,r.eachEdge((function(e){var n=new UA,i=_s(n);i.dataIndex=e.dataIndex,i.seriesIndex=t.seriesIndex,i.dataType=\"edge\";var r,a,u,p,d,f,g,y,v=e.getModel(),m=v.getModel(\"lineStyle\"),_=m.get(\"curveness\"),x=e.node1.getLayout(),b=e.node1.getModel(),w=b.get(\"localX\"),S=b.get(\"localY\"),M=e.node2.getLayout(),I=e.node2.getModel(),T=I.get(\"localX\"),C=I.get(\"localY\"),D=e.getLayout();switch(n.shape.extent=Math.max(1,D.dy),n.shape.orient=c,\"vertical\"===c?(r=(null!=w?w*s:x.x)+D.sy,a=(null!=S?S*l:x.y)+x.dy,u=(null!=T?T*s:M.x)+D.ty,d=r,f=a*(1-_)+(p=null!=C?C*l:M.y)*_,g=u,y=a*_+p*(1-_)):(r=(null!=w?w*s:x.x)+x.dx,a=(null!=S?S*l:x.y)+D.sy,d=r*(1-_)+(u=null!=T?T*s:M.x)*_,f=a,g=r*_+u*(1-_),y=p=(null!=C?C*l:M.y)+D.ty),n.setShape({x1:r,y1:a,x2:u,y2:p,cpx1:d,cpy1:f,cpx2:g,cpy2:y}),n.useStyle(m.getItemStyle()),n.style.fill){case\"source\":n.style.fill=e.node1.getVisual(\"color\"),n.style.decal=e.node1.getVisual(\"style\").decal;break;case\"target\":n.style.fill=e.node2.getVisual(\"color\"),n.style.decal=e.node2.getVisual(\"style\").decal;break;case\"gradient\":var A=e.node1.getVisual(\"color\"),L=e.node2.getVisual(\"color\");\"string\"==typeof A&&\"string\"==typeof L&&(n.style.fill=new mu(0,0,1,0,[{color:A,offset:0},{color:L,offset:1}]))}var k=v.getModel(\"emphasis\");cl(n,v,\"lineStyle\",(function(t){return t.getItemStyle()})),o.add(n),h.setItemGraphicEl(e.dataIndex,n);var P=k.get(\"focus\");sl(n,\"adjacency\"===P?e.getAdjacentDataIndices():P,k.get(\"blurScope\")),_s(n).dataType=\"edge\"})),r.eachNode((function(e){var n=e.getLayout(),i=e.getModel(),r=i.get(\"localX\"),a=i.get(\"localY\"),h=i.getModel(\"emphasis\"),c=new ls({shape:{x:null!=r?r*s:n.x,y:null!=a?a*l:n.y,width:n.dx,height:n.dy},style:i.getModel(\"itemStyle\").getItemStyle(),z2:10});hh(c,ch(i),{labelFetcher:t,labelDataIndex:e.dataIndex,defaultText:e.id}),c.disableLabelAnimation=!0,c.setStyle(\"fill\",e.getVisual(\"color\")),c.setStyle(\"decal\",e.getVisual(\"style\").decal),cl(c,i),o.add(c),u.setItemGraphicEl(e.dataIndex,c),_s(c).dataType=\"node\";var p=h.get(\"focus\");sl(c,\"adjacency\"===p?e.getAdjacentDataIndices():p,h.get(\"blurScope\"))})),u.eachItemGraphicEl((function(e,r){u.getItemModel(r).get(\"draggable\")&&(e.drift=function(e,o){i._focusAdjacencyDisabled=!0,this.shape.x+=e,this.shape.y+=o,this.dirty(),n.dispatchAction({type:\"dragNode\",seriesId:t.id,dataIndex:u.getRawIndex(r),localX:this.shape.x/s,localY:this.shape.y/l})},e.ondragend=function(){i._focusAdjacencyDisabled=!1},e.draggable=!0,e.cursor=\"move\")})),!this._data&&t.isAnimationEnabled()&&o.setClipPath(function(t,e,n){var i=new ls({shape:{x:t.x-10,y:t.y-10,width:0,height:t.height+20}});return Wu(i,{shape:{width:t.width+20}},e,n),i}(o.getBoundingRect(),t,(function(){o.removeClipPath()}))),this._data=t.getData()},e.prototype.dispose=function(){},e.type=\"sankey\",e}(Tf);var YA=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n=t.edges||t.links,i=t.data||t.nodes,r=t.levels;this.levelModels=[];for(var o=this.levelModels,a=0;a<r.length;a++)null!=r[a].depth&&r[a].depth>=0&&(o[r[a].depth]=new Oh(r[a],this,e));if(i&&n)return iD(i,n,this,!0,(function(t,e){t.wrapMethod(\"getItemModel\",(function(t,e){var n=t.parentModel,i=n.getData().getItemLayout(e);if(i){var r=i.depth,o=n.levelModels[r];o&&(t.parentModel=o)}return t})),e.wrapMethod(\"getItemModel\",(function(t,e){var n=t.parentModel,i=n.getGraph().getEdgeByIndex(e).node1.getLayout();if(i){var r=i.depth,o=n.levelModels[r];o&&(t.parentModel=o)}return t}))})).data},e.prototype.setNodePosition=function(t,e){var n=this.option.data[t];n.localX=e[0],n.localY=e[1]},e.prototype.getGraph=function(){return this.getData().graph},e.prototype.getEdgeData=function(){return this.getGraph().edgeData},e.prototype.formatTooltip=function(t,e,n){function i(t){return isNaN(t)||null==t}if(\"edge\"===n){var r=this.getDataParams(t,n),o=r.data,a=r.value;return tf(\"nameValue\",{name:o.source+\" -- \"+o.target,value:a,noValue:i(a)})}var s=this.getGraph().getNodeByIndex(t).getLayout().value,l=this.getDataParams(t,n).data.name;return tf(\"nameValue\",{name:null!=l?l+\"\":null,value:s,noValue:i(s)})},e.prototype.optionUpdated=function(){},e.prototype.getDataParams=function(e,n){var i=t.prototype.getDataParams.call(this,e,n);if(null==i.value&&\"node\"===n){var r=this.getGraph().getNodeByIndex(e).getLayout().value;i.value=r}return i},e.type=\"series.sankey\",e.defaultOption={zlevel:0,z:2,coordinateSystem:\"view\",left:\"5%\",top:\"5%\",right:\"20%\",bottom:\"5%\",orient:\"horizontal\",nodeWidth:20,nodeGap:8,draggable:!0,layoutIterations:32,label:{show:!0,position:\"right\",fontSize:12},levels:[],nodeAlign:\"justify\",lineStyle:{color:\"#314656\",opacity:.2,curveness:.5},emphasis:{label:{show:!0},lineStyle:{opacity:.5}},select:{itemStyle:{borderColor:\"#212121\"}},animationEasing:\"linear\",animationDuration:1e3},e}(ff);function ZA(t,e){t.eachSeriesByType(\"sankey\",(function(t){var n=t.get(\"nodeWidth\"),i=t.get(\"nodeGap\"),r=function(t,e){return Vc(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()})}(t,e);t.layoutInfo=r;var o=r.width,a=r.height,s=t.getGraph(),l=s.nodes,u=s.edges;!function(t){P(t,(function(t){var e=iL(t.outEdges,nL),n=iL(t.inEdges,nL),i=t.getValue()||0,r=Math.max(e,n,i);t.setLayout({value:r},!0)}))}(l),function(t,e,n,i,r,o,a,s,l){(function(t,e,n,i,r,o,a){for(var s=[],l=[],u=[],h=[],c=0,p=0;p<e.length;p++)s[p]=1;for(p=0;p<t.length;p++)l[p]=t[p].inEdges.length,0===l[p]&&u.push(t[p]);var d=-1;for(;u.length;){for(var f=0;f<u.length;f++){var g=u[f],y=g.hostGraph.data.getRawDataItem(g.dataIndex),v=null!=y.depth&&y.depth>=0;v&&y.depth>d&&(d=y.depth),g.setLayout({depth:v?y.depth:c},!0),\"vertical\"===o?g.setLayout({dy:n},!0):g.setLayout({dx:n},!0);for(var m=0;m<g.outEdges.length;m++){var _=g.outEdges[m];s[e.indexOf(_)]=0;var x=_.node2;0==--l[t.indexOf(x)]&&h.indexOf(x)<0&&h.push(x)}}++c,u=h,h=[]}for(p=0;p<s.length;p++)if(1===s[p])throw new Error(\"Sankey is a DAG, the original data has cycle!\");var b=d>c-1?d:c-1;a&&\"left\"!==a&&function(t,e,n,i){if(\"right\"===e){for(var r=[],o=t,a=0;o.length;){for(var s=0;s<o.length;s++){var l=o[s];l.setLayout({skNodeHeight:a},!0);for(var u=0;u<l.inEdges.length;u++){var h=l.inEdges[u];r.indexOf(h.node1)<0&&r.push(h.node1)}}o=r,r=[],++a}P(t,(function(t){jA(t)||t.setLayout({depth:Math.max(0,i-t.getLayout().skNodeHeight)},!0)}))}else\"justify\"===e&&function(t,e){P(t,(function(t){jA(t)||t.outEdges.length||t.setLayout({depth:e},!0)}))}(t,i)}(t,a,0,b);!function(t,e,n){P(t,(function(t){var i=t.getLayout().depth*e;\"vertical\"===n?t.setLayout({y:i},!0):t.setLayout({x:i},!0)}))}(t,\"vertical\"===o?(r-n)/b:(i-n)/b,o)})(t,e,n,r,o,s,l),function(t,e,n,i,r,o,a){var s=function(t,e){var n=[],i=\"vertical\"===e?\"y\":\"x\",r=Br(t,(function(t){return t.getLayout()[i]}));return r.keys.sort((function(t,e){return t-e})),P(r.keys,(function(t){n.push(r.buckets.get(t))})),n}(t,a);(function(t,e,n,i,r,o){var a=1/0;P(t,(function(t){var e=t.length,s=0;P(t,(function(t){s+=t.getLayout().value}));var l=\"vertical\"===o?(i-(e-1)*r)/s:(n-(e-1)*r)/s;l<a&&(a=l)})),P(t,(function(t){P(t,(function(t,e){var n=t.getLayout().value*a;\"vertical\"===o?(t.setLayout({x:e},!0),t.setLayout({dx:n},!0)):(t.setLayout({y:e},!0),t.setLayout({dy:n},!0))}))})),P(e,(function(t){var e=+t.getValue()*a;t.setLayout({dy:e},!0)}))})(s,e,n,i,r,a),qA(s,r,n,i,a);for(var l=1;o>0;o--)KA(s,l*=.99,a),qA(s,r,n,i,a),rL(s,l,a),qA(s,r,n,i,a)}(t,e,o,r,i,a,s),function(t,e){var n=\"vertical\"===e?\"x\":\"y\";P(t,(function(t){t.outEdges.sort((function(t,e){return t.node2.getLayout()[n]-e.node2.getLayout()[n]})),t.inEdges.sort((function(t,e){return t.node1.getLayout()[n]-e.node1.getLayout()[n]}))})),P(t,(function(t){var e=0,n=0;P(t.outEdges,(function(t){t.setLayout({sy:e},!0),e+=t.getLayout().dy})),P(t.inEdges,(function(t){t.setLayout({ty:n},!0),n+=t.getLayout().dy}))}))}(t,s)}(l,u,n,i,o,a,0!==N(l,(function(t){return 0===t.getLayout().value})).length?0:t.get(\"layoutIterations\"),t.get(\"orient\"),t.get(\"nodeAlign\"))}))}function jA(t){var e=t.hostGraph.data.getRawDataItem(t.dataIndex);return null!=e.depth&&e.depth>=0}function qA(t,e,n,i,r){var o=\"vertical\"===r?\"x\":\"y\";P(t,(function(t){var a,s,l;t.sort((function(t,e){return t.getLayout()[o]-e.getLayout()[o]}));for(var u=0,h=t.length,c=\"vertical\"===r?\"dx\":\"dy\",p=0;p<h;p++)(l=u-(s=t[p]).getLayout()[o])>0&&(a=s.getLayout()[o]+l,\"vertical\"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0)),u=s.getLayout()[o]+s.getLayout()[c]+e;if((l=u-e-(\"vertical\"===r?i:n))>0){a=s.getLayout()[o]-l,\"vertical\"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0),u=a;for(p=h-2;p>=0;--p)(l=(s=t[p]).getLayout()[o]+s.getLayout()[c]+e-u)>0&&(a=s.getLayout()[o]-l,\"vertical\"===r?s.setLayout({x:a},!0):s.setLayout({y:a},!0)),u=s.getLayout()[o]}}))}function KA(t,e,n){P(t.slice().reverse(),(function(t){P(t,(function(t){if(t.outEdges.length){var i=iL(t.outEdges,$A,n)/iL(t.outEdges,nL);if(isNaN(i)){var r=t.outEdges.length;i=r?iL(t.outEdges,JA,n)/r:0}if(\"vertical\"===n){var o=t.getLayout().x+(i-eL(t,n))*e;t.setLayout({x:o},!0)}else{var a=t.getLayout().y+(i-eL(t,n))*e;t.setLayout({y:a},!0)}}}))}))}function $A(t,e){return eL(t.node2,e)*t.getValue()}function JA(t,e){return eL(t.node2,e)}function QA(t,e){return eL(t.node1,e)*t.getValue()}function tL(t,e){return eL(t.node1,e)}function eL(t,e){return\"vertical\"===e?t.getLayout().x+t.getLayout().dx/2:t.getLayout().y+t.getLayout().dy/2}function nL(t){return t.getValue()}function iL(t,e,n){for(var i=0,r=t.length,o=-1;++o<r;){var a=+e(t[o],n);isNaN(a)||(i+=a)}return i}function rL(t,e,n){P(t,(function(t){P(t,(function(t){if(t.inEdges.length){var i=iL(t.inEdges,QA,n)/iL(t.inEdges,nL);if(isNaN(i)){var r=t.inEdges.length;i=r?iL(t.inEdges,tL,n)/r:0}if(\"vertical\"===n){var o=t.getLayout().x+(i-eL(t,n))*e;t.setLayout({x:o},!0)}else{var a=t.getLayout().y+(i-eL(t,n))*e;t.setLayout({y:a},!0)}}}))}))}function oL(t){t.eachSeriesByType(\"sankey\",(function(t){var e=t.getGraph().nodes;if(e.length){var n=1/0,i=-1/0;P(e,(function(t){var e=t.getLayout().value;e<n&&(n=e),e>i&&(i=e)})),P(e,(function(e){var r=new TT({type:\"color\",mappingMethod:\"linear\",dataExtent:[n,i],visual:t.get(\"color\")}).mapValueToVisual(e.getLayout().value),o=e.getModel().get([\"itemStyle\",\"color\"]);null!=o?(e.setVisual(\"color\",o),e.setVisual(\"style\",{fill:o})):(e.setVisual(\"color\",r),e.setVisual(\"style\",{fill:r}))}))}}))}var aL=function(){function t(){}return t.prototype.getInitialData=function(t,e){var n,i,r=e.getComponent(\"xAxis\",this.get(\"xAxisIndex\")),o=e.getComponent(\"yAxis\",this.get(\"yAxisIndex\")),a=r.get(\"type\"),s=o.get(\"type\");\"category\"===a?(t.layout=\"horizontal\",n=r.getOrdinalMeta(),i=!0):\"category\"===s?(t.layout=\"vertical\",n=o.getOrdinalMeta(),i=!0):t.layout=t.layout||\"horizontal\";var l=[\"x\",\"y\"],u=\"horizontal\"===t.layout?0:1,h=this._baseAxisDim=l[u],c=l[1-u],p=[r,o],d=p[u].get(\"type\"),f=p[1-u].get(\"type\"),g=t.data;if(g&&i){var y=[];P(g,(function(t,e){var n;F(t)?(n=t.slice(),t.unshift(e)):F(t.value)?(n=t.value.slice(),t.value.unshift(e)):n=t,y.push(n)})),t.data=y}var v=this.defaultValueDimensions,m=[{name:h,type:r_(d),ordinalMeta:n,otherDims:{tooltip:!1,itemName:0},dimsDef:[\"base\"]},{name:c,type:r_(f),dimsDef:v.slice()}];return MS(this,{coordDimensions:m,dimensionsCount:v.length+1,encodeDefaulter:B(lp,m,this)})},t.prototype.getBaseAxis=function(){var t=this._baseAxisDim;return this.ecModel.getComponent(t+\"Axis\",this.get(t+\"AxisIndex\")).axis},t}(),sL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.defaultValueDimensions=[{name:\"min\",defaultTooltip:!0},{name:\"Q1\",defaultTooltip:!0},{name:\"median\",defaultTooltip:!0},{name:\"Q3\",defaultTooltip:!0},{name:\"max\",defaultTooltip:!0}],n.visualDrawType=\"stroke\",n}return n(e,t),e.type=\"series.boxplot\",e.dependencies=[\"xAxis\",\"yAxis\",\"grid\"],e.defaultOption={zlevel:0,z:2,coordinateSystem:\"cartesian2d\",legendHoverLink:!0,layout:null,boxWidth:[7,50],itemStyle:{color:\"#fff\",borderWidth:1},emphasis:{scale:!0,itemStyle:{borderWidth:2,shadowBlur:5,shadowOffsetX:1,shadowOffsetY:1,shadowColor:\"rgba(0,0,0,0.2)\"}},animationDuration:800},e}(ff);L(sL,aL,!0);var lL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this.group,o=this._data;this._data||r.removeAll();var a=\"horizontal\"===t.get(\"layout\")?1:0;i.diff(o).add((function(t){if(i.hasValue(t)){var e=cL(i.getItemLayout(t),i,t,a,!0);i.setItemGraphicEl(t,e),r.add(e)}})).update((function(t,e){var n=o.getItemGraphicEl(e);if(i.hasValue(t)){var s=i.getItemLayout(t);n?pL(s,n,i,t):n=cL(s,i,t,a),r.add(n),i.setItemGraphicEl(t,n)}else r.remove(n)})).remove((function(t){var e=o.getItemGraphicEl(t);e&&r.remove(e)})).execute(),this._data=i},e.prototype.remove=function(t){var e=this.group,n=this._data;this._data=null,n&&n.eachItemGraphicEl((function(t){t&&e.remove(t)}))},e.type=\"boxplot\",e}(Tf),uL=function(){},hL=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"boxplotBoxPath\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new uL},e.prototype.buildPath=function(t,e){var n=e.points,i=0;for(t.moveTo(n[i][0],n[i][1]),i++;i<4;i++)t.lineTo(n[i][0],n[i][1]);for(t.closePath();i<n.length;i++)t.moveTo(n[i][0],n[i][1]),i++,t.lineTo(n[i][0],n[i][1])},e}(Ka);function cL(t,e,n,i,r){var o=t.ends,a=new hL({shape:{points:r?dL(o,i,t):o}});return pL(t,a,e,n,r),a}function pL(t,e,n,i,r){var o=n.hostModel;(0,ah[r?\"initProps\":\"updateProps\"])(e,{shape:{points:t.ends}},o,i),e.useStyle(n.getItemVisual(i,\"style\")),e.style.strokeNoScale=!0,e.z2=100;var a=n.getItemModel(i);cl(e,a),sl(e,a.get([\"emphasis\",\"focus\"]),a.get([\"emphasis\",\"blurScope\"]))}function dL(t,e,n){return O(t,(function(t){return(t=t.slice())[e]=n.initBaseline,t}))}function fL(t,e){}var gL=P;function yL(t){var e=function(t){var e=[],n=[];return t.eachSeriesByType(\"boxplot\",(function(t){var i=t.getBaseAxis(),r=D(n,i);r<0&&(r=n.length,n[r]=i,e[r]={axis:i,seriesModels:[]}),e[r].seriesModels.push(t)})),e}(t);gL(e,(function(t){var e=t.seriesModels;e.length&&(!function(t){var e,n,i=t.axis,r=t.seriesModels,o=r.length,a=t.boxWidthList=[],s=t.boxOffsetList=[],l=[];if(\"category\"===i.type)n=i.getBandWidth();else{var u=0;gL(r,(function(t){u=Math.max(u,t.getData().count())})),e=i.getExtent(),Math.abs(e[1]-e[0])}gL(r,(function(t){var e=t.get(\"boxWidth\");F(e)||(e=[e,e]),l.push([Zi(e[0],n)||0,Zi(e[1],n)||0])}));var h=.8*n-2,c=h/o*.3,p=(h-c*(o-1))/o,d=p/2-h/2;gL(r,(function(t,e){s.push(d),d+=c+p,a.push(Math.min(Math.max(p,l[e][0]),l[e][1]))}))}(t),gL(e,(function(e,n){!function(t,e,n){var i=t.coordinateSystem,r=t.getData(),o=n/2,a=\"horizontal\"===t.get(\"layout\")?0:1,s=1-a,l=[\"x\",\"y\"],u=r.mapDimension(l[a]),h=r.mapDimensionsAll(l[s]);if(null==u||h.length<5)return;for(var c=0;c<r.count();c++){var p=r.get(u,c),d=_(p,h[2],c),f=_(p,h[0],c),g=_(p,h[1],c),y=_(p,h[3],c),v=_(p,h[4],c),m=[];x(m,g,!1),x(m,y,!0),m.push(f,g,v,y),b(m,f),b(m,v),b(m,d),r.setItemLayout(c,{initBaseline:d[s],ends:m})}function _(t,n,o){var l,u=r.get(n,o),h=[];return h[a]=t,h[s]=u,isNaN(t)||isNaN(u)?l=[NaN,NaN]:(l=i.dataToPoint(h))[a]+=e,l}function x(t,e,n){var i=e.slice(),r=e.slice();i[a]+=o,r[a]-=o,n?t.push(i,r):t.push(r,i)}function b(t,e){var n=e.slice(),i=e.slice();n[a]-=o,i[a]+=o,t.push(n,i)}}(e,t.boxOffsetList[n],t.boxWidthList[n])})))}))}var vL={type:\"echarts:boxplot\",transform:function(t){var e=t.upstream;if(e.sourceFormat!==$c){var n=\"\";0,vr(n)}var i=function(t,e){for(var n=[],i=[],r=(e=e||{}).boundIQR,o=\"none\"===r||0===r,a=0;a<t.length;a++){var s=qi(t[a].slice()),l=ur(s,.25),u=ur(s,.5),h=ur(s,.75),c=s[0],p=s[s.length-1],d=(null==r?1.5:r)*(h-l),f=o?c:Math.max(c,l-d),g=o?p:Math.min(p,h+d),y=e.itemNameFormatter,v=G(y)?y({value:a}):H(y)?y.replace(\"{value}\",a+\"\"):a+\"\";n.push([v,f,l,u,h,g]);for(var m=0;m<s.length;m++){var _=s[m];if(_<f||_>g){var x=[v,_];i.push(x)}}}return{boxData:n,outliers:i}}(e.getRawData(),t.config);return[{dimensions:[\"ItemName\",\"Low\",\"Q1\",\"Q2\",\"Q3\",\"High\"],data:i.boxData},{data:i.outliers}]}};var mL=[\"color\",\"borderColor\"],_L=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){this.group.removeClipPath(),this._updateDrawMode(t),this._isLargeDraw?this._renderLarge(t):this._renderNormal(t)},e.prototype.incrementalPrepareRender=function(t,e,n){this._clear(),this._updateDrawMode(t)},e.prototype.incrementalRender=function(t,e,n,i){this._isLargeDraw?this._incrementalRenderLarge(t,e):this._incrementalRenderNormal(t,e)},e.prototype._updateDrawMode=function(t){var e=t.pipelineContext.large;null!=this._isLargeDraw&&e===this._isLargeDraw||(this._isLargeDraw=e,this._clear())},e.prototype._renderNormal=function(t){var e=t.getData(),n=this._data,i=this.group,r=e.getLayout(\"isSimpleBox\"),o=t.get(\"clip\",!0),a=t.coordinateSystem,s=a.getArea&&a.getArea();this._data||i.removeAll(),e.diff(n).add((function(n){if(e.hasValue(n)){var a=e.getItemLayout(n);if(o&&SL(s,a))return;var l=wL(a,n,!0);Wu(l,{shape:{points:a.ends}},t,n),ML(l,e,n,r),i.add(l),e.setItemGraphicEl(n,l)}})).update((function(a,l){var u=n.getItemGraphicEl(l);if(e.hasValue(a)){var h=e.getItemLayout(a);o&&SL(s,h)?i.remove(u):(u?Hu(u,{shape:{points:h.ends}},t,a):u=wL(h),ML(u,e,a,r),i.add(u),e.setItemGraphicEl(a,u))}else i.remove(u)})).remove((function(t){var e=n.getItemGraphicEl(t);e&&i.remove(e)})).execute(),this._data=e},e.prototype._renderLarge=function(t){this._clear(),DL(t,this.group);var e=t.get(\"clip\",!0)?Rw(t.coordinateSystem,!1,t):null;e?this.group.setClipPath(e):this.group.removeClipPath()},e.prototype._incrementalRenderNormal=function(t,e){for(var n,i=e.getData(),r=i.getLayout(\"isSimpleBox\");null!=(n=t.next());){var o=wL(i.getItemLayout(n));ML(o,i,n,r),o.incremental=!0,this.group.add(o)}},e.prototype._incrementalRenderLarge=function(t,e){DL(e,this.group,!0)},e.prototype.remove=function(t){this._clear()},e.prototype._clear=function(){this.group.removeAll(),this._data=null},e.type=\"candlestick\",e}(Tf),xL=function(){},bL=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"normalCandlestickBox\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new xL},e.prototype.buildPath=function(t,e){var n=e.points;this.__simpleBox?(t.moveTo(n[4][0],n[4][1]),t.lineTo(n[6][0],n[6][1])):(t.moveTo(n[0][0],n[0][1]),t.lineTo(n[1][0],n[1][1]),t.lineTo(n[2][0],n[2][1]),t.lineTo(n[3][0],n[3][1]),t.closePath(),t.moveTo(n[4][0],n[4][1]),t.lineTo(n[5][0],n[5][1]),t.moveTo(n[6][0],n[6][1]),t.lineTo(n[7][0],n[7][1]))},e}(Ka);function wL(t,e,n){var i=t.ends;return new bL({shape:{points:n?IL(i,t):i},z2:100})}function SL(t,e){for(var n=!0,i=0;i<e.ends.length;i++)if(t.contain(e.ends[i][0],e.ends[i][1])){n=!1;break}return n}function ML(t,e,n,i){var r=e.getItemModel(n);t.useStyle(e.getItemVisual(n,\"style\")),t.style.strokeNoScale=!0,t.__simpleBox=i,cl(t,r)}function IL(t,e){return O(t,(function(t){return(t=t.slice())[1]=e.initBaseline,t}))}var TL=function(){},CL=function(t){function e(e){var n=t.call(this,e)||this;return n.type=\"largeCandlestickBox\",n}return n(e,t),e.prototype.getDefaultShape=function(){return new TL},e.prototype.buildPath=function(t,e){for(var n=e.points,i=0;i<n.length;)if(this.__sign===n[i++]){var r=n[i++];t.moveTo(r,n[i++]),t.lineTo(r,n[i++])}else i+=3},e}(Ka);function DL(t,e,n){var i=t.getData().getLayout(\"largePoints\"),r=new CL({shape:{points:i},__sign:1});e.add(r);var o=new CL({shape:{points:i},__sign:-1});e.add(o),AL(1,r,t),AL(-1,o,t),n&&(r.incremental=!0,o.incremental=!0)}function AL(t,e,n,i){var r=n.get([\"itemStyle\",t>0?\"borderColor\":\"borderColor0\"])||n.get([\"itemStyle\",t>0?\"color\":\"color0\"]),o=n.getModel(\"itemStyle\").getItemStyle(mL);e.useStyle(o),e.style.fill=null,e.style.stroke=r}var LL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.defaultValueDimensions=[{name:\"open\",defaultTooltip:!0},{name:\"close\",defaultTooltip:!0},{name:\"lowest\",defaultTooltip:!0},{name:\"highest\",defaultTooltip:!0}],n}return n(e,t),e.prototype.getShadowDim=function(){return\"open\"},e.prototype.brushSelector=function(t,e,n){var i=e.getItemLayout(t);return i&&n.rect(i.brushRect)},e.type=\"series.candlestick\",e.dependencies=[\"xAxis\",\"yAxis\",\"grid\"],e.defaultOption={zlevel:0,z:2,coordinateSystem:\"cartesian2d\",legendHoverLink:!0,layout:null,clip:!0,itemStyle:{color:\"#eb5454\",color0:\"#47b262\",borderColor:\"#eb5454\",borderColor0:\"#47b262\",borderWidth:1},emphasis:{scale:!0,itemStyle:{borderWidth:2}},barMaxWidth:null,barMinWidth:null,barWidth:null,large:!0,largeThreshold:600,progressive:3e3,progressiveThreshold:1e4,progressiveChunkMode:\"mod\",animationEasing:\"linear\",animationDuration:300},e}(ff);function kL(t){t&&F(t.series)&&P(t.series,(function(t){X(t)&&\"k\"===t.type&&(t.type=\"candlestick\")}))}L(LL,aL,!0);var PL=[\"itemStyle\",\"borderColor\"],OL=[\"itemStyle\",\"borderColor0\"],RL=[\"itemStyle\",\"color\"],NL=[\"itemStyle\",\"color0\"],zL={seriesType:\"candlestick\",plan:Sf(),performRawSeries:!0,reset:function(t,e){function n(t,e){return e.get(t>0?RL:NL)}function i(t,e){return e.get(t>0?PL:OL)}if(!e.isSeriesFiltered(t))return!t.pipelineContext.large&&{progress:function(t,e){for(var r;null!=(r=t.next());){var o=e.getItemModel(r),a=e.getItemLayout(r).sign,s=o.getItemStyle();s.fill=n(a,o),s.stroke=i(a,o)||s.fill,I(e.ensureUniqueItemVisual(r,\"style\"),s)}}}}},EL=\"undefined\"!=typeof Float32Array?Float32Array:Array,VL={seriesType:\"candlestick\",plan:Sf(),reset:function(t){var e=t.coordinateSystem,n=t.getData(),i=function(t,e){var n,i=t.getBaseAxis(),r=\"category\"===i.type?i.getBandWidth():(n=i.getExtent(),Math.abs(n[1]-n[0])/e.count()),o=Zi(tt(t.get(\"barMaxWidth\"),r),r),a=Zi(tt(t.get(\"barMinWidth\"),1),r),s=t.get(\"barWidth\");return null!=s?Zi(s,r):Math.max(Math.min(r/2,o),a)}(t,n),r=[\"x\",\"y\"],o=n.mapDimension(r[0]),a=n.mapDimensionsAll(r[1]),s=a[0],l=a[1],u=a[2],h=a[3];if(n.setLayout({candleWidth:i,isSimpleBox:i<=1.3}),!(null==o||a.length<4))return{progress:t.pipelineContext.large?function(t,n){var i,r,a=new EL(4*t.count),c=0,p=[],d=[];for(;null!=(r=t.next());){var f=n.get(o,r),g=n.get(s,r),y=n.get(l,r),v=n.get(u,r),m=n.get(h,r);isNaN(f)||isNaN(v)||isNaN(m)?(a[c++]=NaN,c+=3):(a[c++]=BL(n,r,g,y,l),p[0]=f,p[1]=v,i=e.dataToPoint(p,null,d),a[c++]=i?i[0]:NaN,a[c++]=i?i[1]:NaN,p[1]=m,i=e.dataToPoint(p,null,d),a[c++]=i?i[1]:NaN)}n.setLayout(\"largePoints\",a)}:function(t,n){var r;for(;null!=(r=t.next());){var a=n.get(o,r),c=n.get(s,r),p=n.get(l,r),d=n.get(u,r),f=n.get(h,r),g=Math.min(c,p),y=Math.max(c,p),v=w(g,a),m=w(y,a),_=w(d,a),x=w(f,a),b=[];S(b,m,0),S(b,v,1),b.push(I(x),I(m),I(_),I(v)),n.setItemLayout(r,{sign:BL(n,r,c,p,l),initBaseline:c>p?m[1]:v[1],ends:b,brushRect:M(d,f,a)})}function w(t,n){var i=[];return i[0]=n,i[1]=t,isNaN(n)||isNaN(t)?[NaN,NaN]:e.dataToPoint(i)}function S(t,e,n){var r=e.slice(),o=e.slice();r[0]=Fu(r[0]+i/2,1,!1),o[0]=Fu(o[0]-i/2,1,!0),n?t.push(r,o):t.push(o,r)}function M(t,e,n){var r=w(t,n),o=w(e,n);return r[0]-=i/2,o[0]-=i/2,{x:r[0],y:r[1],width:i,height:o[1]-r[1]}}function I(t){return t[0]=Fu(t[0],1),t}}}}};function BL(t,e,n,i,r){return n>i?-1:n<i?1:e>0?t.get(r,e-1)<=i?1:-1:1}function FL(t,e){var n=e.rippleEffectColor||e.color;t.eachChild((function(t){t.attr({z:e.z,zlevel:e.zlevel,style:{stroke:\"stroke\"===e.brushType?n:null,fill:\"fill\"===e.brushType?n:null}})}))}var GL=function(t){function e(e,n){var i=t.call(this)||this,r=new dw(e,n),o=new Ei;return i.add(r),i.add(o),i.updateData(e,n),i}return n(e,t),e.prototype.stopEffectAnimation=function(){this.childAt(1).removeAll()},e.prototype.startEffectAnimation=function(t){for(var e=t.symbolType,n=t.color,i=this.childAt(1),r=0;r<3;r++){var o=fy(e,-1,-1,2,2,n);o.attr({style:{strokeNoScale:!0},z2:99,silent:!0,scaleX:.5,scaleY:.5});var a=-r/3*t.period+t.effectOffset;o.animate(\"\",!0).when(t.period,{scaleX:t.rippleScale/2,scaleY:t.rippleScale/2}).delay(a).start(),o.animateStyle(!0).when(t.period,{opacity:0}).delay(a).start(),i.add(o)}FL(i,t)},e.prototype.updateEffectAnimation=function(t){for(var e=this._effectCfg,n=this.childAt(1),i=[\"symbolType\",\"period\",\"rippleScale\"],r=0;r<i.length;r++){var o=i[r];if(e[o]!==t[o])return this.stopEffectAnimation(),void this.startEffectAnimation(t)}FL(n,t)},e.prototype.highlight=function(){js(this)},e.prototype.downplay=function(){qs(this)},e.prototype.updateData=function(t,e){var n=this,i=t.hostModel;this.childAt(0).updateData(t,e);var r=this.childAt(1),o=t.getItemModel(e),a=t.getItemVisual(e,\"symbol\"),s=function(t){return F(t)||(t=[+t,+t]),t}(t.getItemVisual(e,\"symbolSize\")),l=t.getItemVisual(e,\"style\"),u=l&&l.fill;r.setScale(s),r.traverse((function(t){t.setStyle(\"fill\",u)}));var h=t.getItemVisual(e,\"symbolOffset\");h&&(F(h)||(h=[h,h]),r.x=Zi(h[0],s[0]),r.y=Zi(tt(h[1],h[0])||0,s[1]));var c=t.getItemVisual(e,\"symbolRotate\");r.rotation=(c||0)*Math.PI/180||0;var p={};p.showEffectOn=i.get(\"showEffectOn\"),p.rippleScale=o.get([\"rippleEffect\",\"scale\"]),p.brushType=o.get([\"rippleEffect\",\"brushType\"]),p.period=1e3*o.get([\"rippleEffect\",\"period\"]),p.effectOffset=e/t.count(),p.z=i.getShallow(\"z\")||0,p.zlevel=i.getShallow(\"zlevel\")||0,p.symbolType=a,p.color=u,p.rippleEffectColor=o.get([\"rippleEffect\",\"color\"]),this.off(\"mouseover\").off(\"mouseout\").off(\"emphasis\").off(\"normal\"),\"render\"===p.showEffectOn?(this._effectCfg?this.updateEffectAnimation(p):this.startEffectAnimation(p),this._effectCfg=p):(this._effectCfg=null,this.stopEffectAnimation(),this.onHoverStateChange=function(t){\"emphasis\"===t?\"render\"!==p.showEffectOn&&n.startEffectAnimation(p):\"normal\"===t&&\"render\"!==p.showEffectOn&&n.stopEffectAnimation()}),this._effectCfg=p,sl(this)},e.prototype.fadeOut=function(t){this.off(\"mouseover\").off(\"mouseout\"),t&&t()},e}(Ei),HL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){this._symbolDraw=new mw(GL)},e.prototype.render=function(t,e,n){var i=t.getData(),r=this._symbolDraw;r.updateData(i,{clipShape:this._getClipShape(t)}),this.group.add(r.group)},e.prototype._getClipShape=function(t){var e=t.coordinateSystem,n=e&&e.getArea&&e.getArea();return t.get(\"clip\",!0)?n:null},e.prototype.updateTransform=function(t,e,n){var i=t.getData();this.group.dirty();var r=Xw(\"\").reset(t,e,n);r.progress&&r.progress({start:0,end:i.count(),count:i.count()},i),this._symbolDraw.updateLayout()},e.prototype._updateGroupTransform=function(t){var e=t.coordinateSystem;e&&e.getRoamTransform&&(this.group.transform=jn(e.getRoamTransform()),this.group.decomposeTransform())},e.prototype.remove=function(t,e){this._symbolDraw&&this._symbolDraw.remove(!0)},e.type=\"effectScatter\",e}(Tf),WL=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){return F_(this.getSource(),this,{useEncodeDefaulter:!0})},e.prototype.brushSelector=function(t,e,n){return n.point(e.getItemLayout(t))},e.type=\"series.effectScatter\",e.dependencies=[\"grid\",\"polar\"],e.defaultOption={coordinateSystem:\"cartesian2d\",zlevel:0,z:2,legendHoverLink:!0,effectType:\"ripple\",progressive:0,showEffectOn:\"render\",clip:!0,rippleEffect:{period:4,scale:2.5,brushType:\"fill\"},symbolSize:10},e}(ff);var UL=function(t){function e(e,n,i){var r=t.call(this)||this;return r.add(r.createLine(e,n,i)),r._updateEffectSymbol(e,n),r}return n(e,t),e.prototype.createLine=function(t,e,n){return new EC(t,e,n)},e.prototype._updateEffectSymbol=function(t,e){var n=t.getItemModel(e).getModel(\"effect\"),i=n.get(\"symbolSize\"),r=n.get(\"symbol\");F(i)||(i=[i,i]);var o=t.getItemVisual(e,\"style\"),a=n.get(\"color\")||o&&o.stroke,s=this.childAt(1);this._symbolType!==r&&(this.remove(s),(s=fy(r,-.5,-.5,1,1,a)).z2=100,s.culling=!0,this.add(s)),s&&(s.setStyle(\"shadowColor\",a),s.setStyle(n.getItemStyle([\"color\"])),s.scaleX=i[0],s.scaleY=i[1],s.setColor(a),this._symbolType=r,this._symbolScale=i,this._updateEffectAnimation(t,n,e))},e.prototype._updateEffectAnimation=function(t,e,n){var i=this.childAt(1);if(i){var r=this,o=t.getItemLayout(n),a=1e3*e.get(\"period\"),s=e.get(\"loop\"),l=e.get(\"constantSpeed\"),u=Q(e.get(\"delay\"),(function(e){return e/t.count()*a/3}));if(i.ignore=!0,this._updateAnimationPoints(i,o),l>0&&(a=this._getLineLength(i)/l*1e3),(a!==this._period||s!==this._loop)&&(i.stopAnimation(),a>0)){var h=void 0;h=\"function\"==typeof u?u(n):u,i.__t>0&&(h=-a*i.__t),i.__t=0;var c=i.animate(\"\",s).when(a,{__t:1}).delay(h).during((function(){r._updateSymbolPosition(i)}));s||c.done((function(){r.remove(i)})),c.start()}this._period=a,this._loop=s}},e.prototype._getLineLength=function(t){return Lt(t.__p1,t.__cp1)+Lt(t.__cp1,t.__p2)},e.prototype._updateAnimationPoints=function(t,e){t.__p1=e[0],t.__p2=e[1],t.__cp1=e[2]||[(e[0][0]+e[1][0])/2,(e[0][1]+e[1][1])/2]},e.prototype.updateData=function(t,e,n){this.childAt(0).updateData(t,e,n),this._updateEffectSymbol(t,e)},e.prototype._updateSymbolPosition=function(t){var e=t.__p1,n=t.__p2,i=t.__cp1,r=t.__t,o=[t.x,t.y],a=o.slice(),s=Uo,l=Xo;o[0]=s(e[0],i[0],n[0],r),o[1]=s(e[1],i[1],n[1],r);var u=l(e[0],i[0],n[0],r),h=l(e[1],i[1],n[1],r);t.rotation=-Math.atan2(h,u)-Math.PI/2,\"line\"!==this._symbolType&&\"rect\"!==this._symbolType&&\"roundRect\"!==this._symbolType||(void 0!==t.__lastT&&t.__lastT<t.__t?(t.scaleY=1.05*Lt(a,o),1===r&&(o[0]=a[0]+(o[0]-a[0])/2,o[1]=a[1]+(o[1]-a[1])/2)):1===t.__lastT?t.scaleY=2*Lt(e,o):t.scaleY=this._symbolScale[1]),t.__lastT=t.__t,t.ignore=!1,t.x=o[0],t.y=o[1]},e.prototype.updateLayout=function(t,e){this.childAt(0).updateLayout(t,e);var n=t.getItemModel(e).getModel(\"effect\");this._updateEffectAnimation(t,n,e)},e}(Ei),XL=function(t){function e(e,n,i){var r=t.call(this)||this;return r._createPolyline(e,n,i),r}return n(e,t),e.prototype._createPolyline=function(t,e,n){var i=t.getItemLayout(e),r=new au({shape:{points:i}});this.add(r),this._updateCommonStl(t,e,n)},e.prototype.updateData=function(t,e,n){var i=t.hostModel;Hu(this.childAt(0),{shape:{points:t.getItemLayout(e)}},i,e),this._updateCommonStl(t,e,n)},e.prototype._updateCommonStl=function(t,e,n){var i=this.childAt(0),r=t.getItemModel(e),o=n&&n.emphasisLineStyle;n&&!t.hasItemOption||(o=r.getModel([\"emphasis\",\"lineStyle\"]).getLineStyle()),i.useStyle(t.getItemVisual(e,\"style\")),i.style.fill=null,i.style.strokeNoScale=!0,i.ensureState(\"emphasis\").style=o,sl(this)},e.prototype.updateLayout=function(t,e){this.childAt(0).setShape(\"points\",t.getItemLayout(e))},e}(Ei),YL=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e._lastFrame=0,e._lastFramePercent=0,e}return n(e,t),e.prototype.createLine=function(t,e,n){return new XL(t,e,n)},e.prototype._updateAnimationPoints=function(t,e){this._points=e;for(var n=[0],i=0,r=1;r<e.length;r++){var o=e[r-1],a=e[r];i+=Lt(o,a),n.push(i)}if(0!==i){for(r=0;r<n.length;r++)n[r]/=i;this._offsets=n,this._length=i}else this._length=0},e.prototype._getLineLength=function(){return this._length},e.prototype._updateSymbolPosition=function(t){var e=t.__t,n=this._points,i=this._offsets,r=n.length;if(i){var o,a=this._lastFrame;if(e<this._lastFramePercent){for(o=Math.min(a+1,r-1);o>=0&&!(i[o]<=e);o--);o=Math.min(o,r-2)}else{for(o=a;o<r&&!(i[o]>e);o++);o=Math.min(o-1,r-2)}var s=(e-i[o])/(i[o+1]-i[o]),l=n[o],u=n[o+1];t.x=l[0]*(1-s)+s*u[0],t.y=l[1]*(1-s)+s*u[1];var h=u[0]-l[0],c=u[1]-l[1];t.rotation=-Math.atan2(c,h)-Math.PI/2,this._lastFrame=o,this._lastFramePercent=e,t.ignore=!1}},e}(UL),ZL=function(){this.polyline=!1,this.curveness=0,this.segs=[]},jL=function(t){function e(e){return t.call(this,e)||this}return n(e,t),e.prototype.getDefaultStyle=function(){return{stroke:\"#000\",fill:null}},e.prototype.getDefaultShape=function(){return new ZL},e.prototype.buildPath=function(t,e){var n=e.segs,i=e.curveness;if(e.polyline)for(var r=0;r<n.length;){var o=n[r++];if(o>0){t.moveTo(n[r++],n[r++]);for(var a=1;a<o;a++)t.lineTo(n[r++],n[r++])}}else for(r=0;r<n.length;){var s=n[r++],l=n[r++],u=n[r++],h=n[r++];if(t.moveTo(s,l),i>0){var c=(s+u)/2-(l-h)*i,p=(l+h)/2-(u-s)*i;t.quadraticCurveTo(c,p,u,h)}else t.lineTo(u,h)}},e.prototype.findDataIndex=function(t,e){var n=this.shape,i=n.segs,r=n.curveness,o=this.style.lineWidth;if(n.polyline)for(var a=0,s=0;s<i.length;){var l=i[s++];if(l>0)for(var u=i[s++],h=i[s++],c=1;c<l;c++){if(ka(u,h,p=i[s++],d=i[s++],o,t,e))return a}a++}else for(a=0,s=0;s<i.length;){u=i[s++],h=i[s++];var p=i[s++],d=i[s++];if(r>0){if(Oa(u,h,(u+p)/2-(h-d)*r,(h+d)/2-(p-u)*r,p,d,o,t,e))return a}else if(ka(u,h,p,d,o,t,e))return a;a++}return-1},e}(Ka),qL=function(){function t(){this.group=new Ei}return t.prototype.isPersistent=function(){return!this._incremental},t.prototype.updateData=function(t){this.group.removeAll();var e=new jL({rectHover:!0,cursor:\"default\"});e.setShape({segs:t.getLayout(\"linesPoints\")}),this._setCommon(e,t),this.group.add(e),this._incremental=null},t.prototype.incrementalPrepareUpdate=function(t){this.group.removeAll(),this._clearIncremental(),t.count()>5e5?(this._incremental||(this._incremental=new Tu({silent:!0})),this.group.add(this._incremental)):this._incremental=null},t.prototype.incrementalUpdate=function(t,e){var n=new jL;n.setShape({segs:e.getLayout(\"linesPoints\")}),this._setCommon(n,e,!!this._incremental),this._incremental?this._incremental.addDisplayable(n,!0):(n.rectHover=!0,n.cursor=\"default\",n.__startIndex=t.start,this.group.add(n))},t.prototype.remove=function(){this._clearIncremental(),this._incremental=null,this.group.removeAll()},t.prototype._setCommon=function(t,e,n){var i=e.hostModel;t.setShape({polyline:i.get(\"polyline\"),curveness:i.get([\"lineStyle\",\"curveness\"])}),t.useStyle(i.getModel(\"lineStyle\").getLineStyle()),t.style.strokeNoScale=!0;var r=e.getVisual(\"style\");if(r&&r.stroke&&t.setStyle(\"stroke\",r.stroke),t.setStyle(\"fill\",null),!n){var o=_s(t);o.seriesIndex=i.seriesIndex,t.on(\"mousemove\",(function(e){o.dataIndex=null;var n=t.findDataIndex(e.offsetX,e.offsetY);n>0&&(o.dataIndex=n+t.__startIndex)}))}},t.prototype._clearIncremental=function(){var t=this._incremental;t&&t.clearDisplaybles()},t}(),KL={seriesType:\"lines\",plan:Sf(),reset:function(t){var e=t.coordinateSystem,n=t.get(\"polyline\"),i=t.pipelineContext.large;return{progress:function(r,o){var a=[];if(i){var s=void 0,l=r.end-r.start;if(n){for(var u=0,h=r.start;h<r.end;h++)u+=t.getLineCoordsCount(h);s=new Float32Array(l+2*u)}else s=new Float32Array(4*l);var c=0,p=[];for(h=r.start;h<r.end;h++){var d=t.getLineCoords(h,a);n&&(s[c++]=d);for(var f=0;f<d;f++)p=e.dataToPoint(a[f],!1,p),s[c++]=p[0],s[c++]=p[1]}o.setLayout(\"linesPoints\",s)}else for(h=r.start;h<r.end;h++){var g=o.getItemModel(h),y=(d=t.getLineCoords(h,a),[]);if(n)for(var v=0;v<d;v++)y.push(e.dataToPoint(a[v]));else{y[0]=e.dataToPoint(a[0]),y[1]=e.dataToPoint(a[1]);var m=g.get([\"lineStyle\",\"curveness\"]);+m&&(y[2]=[(y[0][0]+y[1][0])/2-(y[0][1]-y[1][1])*m,(y[0][1]+y[1][1])/2-(y[1][0]-y[0][0])*m])}o.setItemLayout(h,y)}}}}},$L=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this._updateLineDraw(i,t),o=t.get(\"zlevel\"),a=t.get([\"effect\",\"trailLength\"]),s=n.getZr(),l=\"svg\"===s.painter.getType();(l||s.painter.getLayer(o).clear(!0),null==this._lastZlevel||l||s.configLayer(this._lastZlevel,{motionBlur:!1}),this._showEffect(t)&&a)&&(l||s.configLayer(o,{motionBlur:!0,lastFrameAlpha:Math.max(Math.min(a/10+.9,1),0)}));r.updateData(i);var u=t.get(\"clip\",!0)&&Rw(t.coordinateSystem,!1,t);u?this.group.setClipPath(u):this.group.removeClipPath(),this._lastZlevel=o,this._finished=!0},e.prototype.incrementalPrepareRender=function(t,e,n){var i=t.getData();this._updateLineDraw(i,t).incrementalPrepareUpdate(i),this._clearLayer(n),this._finished=!1},e.prototype.incrementalRender=function(t,e,n){this._lineDraw.incrementalUpdate(t,e.getData()),this._finished=t.end===e.getData().count()},e.prototype.updateTransform=function(t,e,n){var i=t.getData(),r=t.pipelineContext;if(!this._finished||r.large||r.progressiveRender)return{update:!0};var o=KL.reset(t,e,n);o.progress&&o.progress({start:0,end:i.count(),count:i.count()},i),this._lineDraw.updateLayout(),this._clearLayer(n)},e.prototype._updateLineDraw=function(t,e){var n=this._lineDraw,i=this._showEffect(e),r=!!e.get(\"polyline\"),o=e.pipelineContext.large;return n&&i===this._hasEffet&&r===this._isPolyline&&o===this._isLargeDraw||(n&&n.remove(),n=this._lineDraw=o?new qL:new VC(r?i?YL:XL:i?UL:EC),this._hasEffet=i,this._isPolyline=r,this._isLargeDraw=o,this.group.removeAll()),this.group.add(n.group),n},e.prototype._showEffect=function(t){return!!t.get([\"effect\",\"show\"])},e.prototype._clearLayer=function(t){var e=t.getZr();\"svg\"===e.painter.getType()||null==this._lastZlevel||e.painter.getLayer(this._lastZlevel).clear(!0)},e.prototype.remove=function(t,e){this._lineDraw&&this._lineDraw.remove(),this._lineDraw=null,this._clearLayer(e)},e.type=\"lines\",e}(Tf),JL=\"undefined\"==typeof Uint32Array?Array:Uint32Array,QL=\"undefined\"==typeof Float64Array?Array:Float64Array;function tk(t){var e=t.data;e&&e[0]&&e[0][0]&&e[0][0].coord&&(t.data=O(e,(function(t){var e={coords:[t[0].coord,t[1].coord]};return t[0].name&&(e.fromName=t[0].name),t[1].name&&(e.toName=t[1].name),M([e,t[0],t[1]])})))}var ek=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.visualStyleAccessPath=\"lineStyle\",n.visualDrawType=\"stroke\",n}return n(e,t),e.prototype.init=function(e){e.data=e.data||[],tk(e);var n=this._processFlatCoordsArray(e.data);this._flatCoords=n.flatCoords,this._flatCoordsOffset=n.flatCoordsOffset,n.flatCoords&&(e.data=new Float32Array(n.count)),t.prototype.init.apply(this,arguments)},e.prototype.mergeOption=function(e){if(tk(e),e.data){var n=this._processFlatCoordsArray(e.data);this._flatCoords=n.flatCoords,this._flatCoordsOffset=n.flatCoordsOffset,n.flatCoords&&(e.data=new Float32Array(n.count))}t.prototype.mergeOption.apply(this,arguments)},e.prototype.appendData=function(t){var e=this._processFlatCoordsArray(t.data);e.flatCoords&&(this._flatCoords?(this._flatCoords=ct(this._flatCoords,e.flatCoords),this._flatCoordsOffset=ct(this._flatCoordsOffset,e.flatCoordsOffset)):(this._flatCoords=e.flatCoords,this._flatCoordsOffset=e.flatCoordsOffset),t.data=new Float32Array(e.count)),this.getRawData().appendData(t.data)},e.prototype._getCoordsFromItemModel=function(t){var e=this.getData().getItemModel(t),n=e.option instanceof Array?e.option:e.getShallow(\"coords\");return n},e.prototype.getLineCoordsCount=function(t){return this._flatCoordsOffset?this._flatCoordsOffset[2*t+1]:this._getCoordsFromItemModel(t).length},e.prototype.getLineCoords=function(t,e){if(this._flatCoordsOffset){for(var n=this._flatCoordsOffset[2*t],i=this._flatCoordsOffset[2*t+1],r=0;r<i;r++)e[r]=e[r]||[],e[r][0]=this._flatCoords[n+2*r],e[r][1]=this._flatCoords[n+2*r+1];return i}var o=this._getCoordsFromItemModel(t);for(r=0;r<o.length;r++)e[r]=e[r]||[],e[r][0]=o[r][0],e[r][1]=o[r][1];return o.length},e.prototype._processFlatCoordsArray=function(t){var e=0;if(this._flatCoords&&(e=this._flatCoords.length),\"number\"==typeof t[0]){for(var n=t.length,i=new JL(n),r=new QL(n),o=0,a=0,s=0,l=0;l<n;){s++;var u=t[l++];i[a++]=o+e,i[a++]=u;for(var h=0;h<u;h++){var c=t[l++],p=t[l++];r[o++]=c,r[o++]=p}}return{flatCoordsOffset:new Uint32Array(i.buffer,0,a),flatCoords:r,count:s}}return{flatCoordsOffset:null,flatCoords:null,count:t.length}},e.prototype.getInitialData=function(t,e){var n=new L_([\"value\"],this);return n.hasItemOption=!1,n.initData(t.data,[],(function(t,e,i,r){if(t instanceof Array)return NaN;n.hasItemOption=!0;var o=t.value;return null!=o?o instanceof Array?o[r]:o:void 0})),n},e.prototype.formatTooltip=function(t,e,n){var i=this.getData().getItemModel(t),r=i.get(\"name\");if(r)return r;var o=i.get(\"fromName\"),a=i.get(\"toName\"),s=[];return null!=o&&s.push(o),null!=a&&s.push(a),tf(\"nameValue\",{name:s.join(\" > \")})},e.prototype.preventIncremental=function(){return!!this.get([\"effect\",\"show\"])},e.prototype.getProgressive=function(){var t=this.option.progressive;return null==t?this.option.large?1e4:this.get(\"progressive\"):t},e.prototype.getProgressiveThreshold=function(){var t=this.option.progressiveThreshold;return null==t?this.option.large?2e4:this.get(\"progressiveThreshold\"):t},e.type=\"series.lines\",e.dependencies=[\"grid\",\"polar\",\"geo\",\"calendar\"],e.defaultOption={coordinateSystem:\"geo\",zlevel:0,z:2,legendHoverLink:!0,xAxisIndex:0,yAxisIndex:0,symbol:[\"none\",\"none\"],symbolSize:[10,10],geoIndex:0,effect:{show:!1,period:4,constantSpeed:0,symbol:\"circle\",symbolSize:3,loop:!0,trailLength:.2},large:!1,largeThreshold:2e3,polyline:!1,clip:!0,label:{show:!1,position:\"end\"},lineStyle:{opacity:.5}},e}(ff);function nk(t){return t instanceof Array||(t=[t,t]),t}var ik={seriesType:\"lines\",reset:function(t){var e=nk(t.get(\"symbol\")),n=nk(t.get(\"symbolSize\")),i=t.getData();return i.setVisual(\"fromSymbol\",e&&e[0]),i.setVisual(\"toSymbol\",e&&e[1]),i.setVisual(\"fromSymbolSize\",n&&n[0]),i.setVisual(\"toSymbolSize\",n&&n[1]),{dataEach:i.hasItemOption?function(t,e){var n=t.getItemModel(e),i=nk(n.getShallow(\"symbol\",!0)),r=nk(n.getShallow(\"symbolSize\",!0));i[0]&&t.setItemVisual(e,\"fromSymbol\",i[0]),i[1]&&t.setItemVisual(e,\"toSymbol\",i[1]),r[0]&&t.setItemVisual(e,\"fromSymbolSize\",r[0]),r[1]&&t.setItemVisual(e,\"toSymbolSize\",r[1])}:null}}};var rk=function(){function t(){this.blurSize=30,this.pointSize=20,this.maxOpacity=1,this.minOpacity=0,this._gradientPixels={inRange:null,outOfRange:null};var t=C();this.canvas=t}return t.prototype.update=function(t,e,n,i,r,o){var a=this._getBrush(),s=this._getGradient(r,\"inRange\"),l=this._getGradient(r,\"outOfRange\"),u=this.pointSize+this.blurSize,h=this.canvas,c=h.getContext(\"2d\"),p=t.length;h.width=e,h.height=n;for(var d=0;d<p;++d){var f=t[d],g=f[0],y=f[1],v=i(f[2]);c.globalAlpha=v,c.drawImage(a,g-u,y-u)}if(!h.width||!h.height)return h;for(var m=c.getImageData(0,0,h.width,h.height),_=m.data,x=0,b=_.length,w=this.minOpacity,S=this.maxOpacity-w;x<b;){v=_[x+3]/256;var M=4*Math.floor(255*v);if(v>0){var I=o(v)?s:l;v>0&&(v=v*S+w),_[x++]=I[M],_[x++]=I[M+1],_[x++]=I[M+2],_[x++]=I[M+3]*v*256}else x+=4}return c.putImageData(m,0,0),h},t.prototype._getBrush=function(){var t=this._brushCanvas||(this._brushCanvas=C()),e=this.pointSize+this.blurSize,n=2*e;t.width=n,t.height=n;var i=t.getContext(\"2d\");return i.clearRect(0,0,n,n),i.shadowOffsetX=n,i.shadowBlur=this.blurSize,i.shadowColor=\"#000\",i.beginPath(),i.arc(-e,e,this.pointSize,0,2*Math.PI,!0),i.closePath(),i.fill(),t},t.prototype._getGradient=function(t,e){for(var n=this._gradientPixels,i=n[e]||(n[e]=new Uint8ClampedArray(1024)),r=[0,0,0,0],o=0,a=0;a<256;a++)t[e](a/255,!0,r),i[o++]=r[0],i[o++]=r[1],i[o++]=r[2],i[o++]=r[3];return i},t}();function ok(t){var e=t.dimensions;return\"lng\"===e[0]&&\"lat\"===e[1]}var ak=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i;e.eachComponent(\"visualMap\",(function(e){e.eachTargetSeries((function(n){n===t&&(i=e)}))})),this.group.removeAll(),this._incrementalDisplayable=null;var r=t.coordinateSystem;\"cartesian2d\"===r.type||\"calendar\"===r.type?this._renderOnCartesianAndCalendar(t,n,0,t.getData().count()):ok(r)&&this._renderOnGeo(r,t,i,n)},e.prototype.incrementalPrepareRender=function(t,e,n){this.group.removeAll()},e.prototype.incrementalRender=function(t,e,n,i){var r=e.coordinateSystem;r&&(ok(r)?this.render(e,n,i):this._renderOnCartesianAndCalendar(e,i,t.start,t.end,!0))},e.prototype._renderOnCartesianAndCalendar=function(t,e,n,i,r){var o,a,s,l,u=t.coordinateSystem;if(Nw(u,\"cartesian2d\")){var h=u.getAxis(\"x\"),c=u.getAxis(\"y\");0,o=h.getBandWidth(),a=c.getBandWidth(),s=h.scale.getExtent(),l=c.scale.getExtent()}for(var p=this.group,d=t.getData(),f=t.getModel([\"emphasis\",\"itemStyle\"]).getItemStyle(),g=t.getModel([\"blur\",\"itemStyle\"]).getItemStyle(),y=t.getModel([\"select\",\"itemStyle\"]).getItemStyle(),v=ch(t),m=t.get([\"emphasis\",\"focus\"]),_=t.get([\"emphasis\",\"blurScope\"]),x=Nw(u,\"cartesian2d\")?[d.mapDimension(\"x\"),d.mapDimension(\"y\"),d.mapDimension(\"value\")]:[d.mapDimension(\"time\"),d.mapDimension(\"value\")],b=n;b<i;b++){var w=void 0,S=d.getItemVisual(b,\"style\");if(Nw(u,\"cartesian2d\")){var M=d.get(x[0],b),I=d.get(x[1],b);if(isNaN(d.get(x[2],b))||M<s[0]||M>s[1]||I<l[0]||I>l[1])continue;var T=u.dataToPoint([M,I]);w=new ls({shape:{x:Math.floor(Math.round(T[0])-o/2),y:Math.floor(Math.round(T[1])-a/2),width:Math.ceil(o),height:Math.ceil(a)},style:S})}else{if(isNaN(d.get(x[1],b)))continue;w=new ls({z2:1,shape:u.dataToRect([d.get(x[0],b)]).contentShape,style:S})}var C=d.getItemModel(b);if(d.hasItemOption){var D=C.getModel(\"emphasis\");f=D.getModel(\"itemStyle\").getItemStyle(),g=C.getModel([\"blur\",\"itemStyle\"]).getItemStyle(),y=C.getModel([\"select\",\"itemStyle\"]).getItemStyle(),m=D.get(\"focus\"),_=D.get(\"blurScope\"),v=ch(C)}var A=t.getRawValue(b),L=\"-\";A&&null!=A[2]&&(L=A[2]+\"\"),hh(w,v,{labelFetcher:t,labelDataIndex:b,defaultOpacity:S.opacity,defaultText:L}),w.ensureState(\"emphasis\").style=f,w.ensureState(\"blur\").style=g,w.ensureState(\"select\").style=y,sl(w,m,_),w.incremental=r,r&&(w.states.emphasis.hoverLayer=!0),p.add(w),d.setItemGraphicEl(b,w)}},e.prototype._renderOnGeo=function(t,e,n,i){var r=n.targetVisuals.inRange,o=n.targetVisuals.outOfRange,a=e.getData(),s=this._hmLayer||this._hmLayer||new rk;s.blurSize=e.get(\"blurSize\"),s.pointSize=e.get(\"pointSize\"),s.minOpacity=e.get(\"minOpacity\"),s.maxOpacity=e.get(\"maxOpacity\");var l=t.getViewRect().clone(),u=t.getRoamTransform();l.applyTransform(u);var h=Math.max(l.x,0),c=Math.max(l.y,0),p=Math.min(l.width+l.x,i.getWidth()),d=Math.min(l.height+l.y,i.getHeight()),f=p-h,g=d-c,y=[a.mapDimension(\"lng\"),a.mapDimension(\"lat\"),a.mapDimension(\"value\")],v=a.mapArray(y,(function(e,n,i){var r=t.dataToPoint([e,n]);return r[0]-=h,r[1]-=c,r.push(i),r})),m=n.getExtent(),_=\"visualMap.continuous\"===n.type?function(t,e){var n=t[1]-t[0];return e=[(e[0]-t[0])/n,(e[1]-t[0])/n],function(t){return t>=e[0]&&t<=e[1]}}(m,n.option.range):function(t,e,n){var i=t[1]-t[0],r=(e=O(e,(function(e){return{interval:[(e.interval[0]-t[0])/i,(e.interval[1]-t[0])/i]}}))).length,o=0;return function(t){var i;for(i=o;i<r;i++)if((a=e[i].interval)[0]<=t&&t<=a[1]){o=i;break}if(i===r)for(i=o-1;i>=0;i--){var a;if((a=e[i].interval)[0]<=t&&t<=a[1]){o=i;break}}return i>=0&&i<r&&n[i]}}(m,n.getPieceList(),n.option.selected);s.update(v,f,g,r.color.getNormalizer(),{inRange:r.color.getColorMapper(),outOfRange:o.color.getColorMapper()},_);var x=new es({style:{width:f,height:g,x:h,y:c,image:s.canvas},silent:!0});this.group.add(x)},e.type=\"heatmap\",e}(Tf),sk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getInitialData=function(t,e){return F_(this.getSource(),this,{generateCoord:\"value\"})},e.prototype.preventIncremental=function(){var t=Ap.get(this.get(\"coordinateSystem\"));if(t&&t.dimensions)return\"lng\"===t.dimensions[0]&&\"lat\"===t.dimensions[1]},e.type=\"series.heatmap\",e.dependencies=[\"grid\",\"geo\",\"calendar\"],e.defaultOption={coordinateSystem:\"cartesian2d\",zlevel:0,z:2,geoIndex:0,blurSize:30,pointSize:20,maxOpacity:1,minOpacity:0,select:{itemStyle:{borderColor:\"#212121\"}}},e}(ff);var lk=[\"itemStyle\",\"borderWidth\"],uk=[{xy:\"x\",wh:\"width\",index:0,posDesc:[\"left\",\"right\"]},{xy:\"y\",wh:\"height\",index:1,posDesc:[\"top\",\"bottom\"]}],hk=new Nl,ck=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=this.group,r=t.getData(),o=this._data,a=t.coordinateSystem,s=a.getBaseAxis().isHorizontal(),l=a.master.getRect(),u={ecSize:{width:n.getWidth(),height:n.getHeight()},seriesModel:t,coordSys:a,coordSysExtent:[[l.x,l.x+l.width],[l.y,l.y+l.height]],isHorizontal:s,valueDim:uk[+s],categoryDim:uk[1-+s]};return r.diff(o).add((function(t){if(r.hasValue(t)){var e=_k(r,t),n=pk(r,t,e,u),o=wk(r,u,n);r.setItemGraphicEl(t,o),i.add(o),Ck(o,u,n)}})).update((function(t,e){var n=o.getItemGraphicEl(e);if(r.hasValue(t)){var a=_k(r,t),s=pk(r,t,a,u),l=Mk(r,s);n&&l!==n.__pictorialShapeStr&&(i.remove(n),r.setItemGraphicEl(t,null),n=null),n?function(t,e,n){var i=n.animationModel,r=n.dataIndex;Hu(t.__pictorialBundle,{x:n.bundlePosition[0],y:n.bundlePosition[1]},i,r),n.symbolRepeat?gk(t,e,n,!0):yk(t,e,n,!0);vk(t,n,!0),mk(t,e,n,!0)}(n,u,s):n=wk(r,u,s,!0),r.setItemGraphicEl(t,n),n.__pictorialSymbolMeta=s,i.add(n),Ck(n,u,s)}else i.remove(n)})).remove((function(t){var e=o.getItemGraphicEl(t);e&&Sk(o,t,e.__pictorialSymbolMeta.animationModel,e)})).execute(),this._data=r,this.group},e.prototype.remove=function(t,e){var n=this.group,i=this._data;t.get(\"animation\")?i&&i.eachItemGraphicEl((function(e){Sk(i,_s(e).dataIndex,t,e)})):n.removeAll()},e.type=\"pictorialBar\",e}(Tf);function pk(t,e,n,i){var r=t.getItemLayout(e),o=n.get(\"symbolRepeat\"),a=n.get(\"symbolClip\"),s=n.get(\"symbolPosition\")||\"start\",l=(n.get(\"symbolRotate\")||0)*Math.PI/180||0,u=n.get(\"symbolPatternSize\")||2,h=n.isAnimationEnabled(),c={dataIndex:e,layout:r,itemModel:n,symbolType:t.getItemVisual(e,\"symbol\")||\"circle\",style:t.getItemVisual(e,\"style\"),symbolClip:a,symbolRepeat:o,symbolRepeatDirection:n.get(\"symbolRepeatDirection\"),symbolPatternSize:u,rotation:l,animationModel:h?n:null,hoverScale:h&&n.get([\"emphasis\",\"scale\"]),z2:n.getShallow(\"z\",!0)||0};!function(t,e,n,i,r){var o,a=i.valueDim,s=t.get(\"symbolBoundingData\"),l=i.coordSys.getOtherAxis(i.coordSys.getBaseAxis()),u=l.toGlobalCoord(l.dataToCoord(0)),h=1-+(n[a.wh]<=0);if(F(s)){var c=[dk(l,s[0])-u,dk(l,s[1])-u];c[1]<c[0]&&c.reverse(),o=c[h]}else o=null!=s?dk(l,s)-u:e?i.coordSysExtent[a.index][h]-u:n[a.wh];r.boundingLength=o,e&&(r.repeatCutLength=n[a.wh]);r.pxSign=o>0?1:o<0?-1:0}(n,o,r,i,c),function(t,e,n,i,r,o,a,s,l,u){var h,c=l.valueDim,p=l.categoryDim,d=Math.abs(n[p.wh]),f=t.getItemVisual(e,\"symbolSize\");h=F(f)?f.slice():null==f?[\"100%\",\"100%\"]:[f,f];h[p.index]=Zi(h[p.index],d),h[c.index]=Zi(h[c.index],i?d:Math.abs(o)),u.symbolSize=h,(u.symbolScale=[h[0]/s,h[1]/s])[c.index]*=(l.isHorizontal?-1:1)*a}(t,e,r,o,0,c.boundingLength,c.pxSign,u,i,c),function(t,e,n,i,r){var o=t.get(lk)||0;o&&(hk.attr({scaleX:e[0],scaleY:e[1],rotation:n}),hk.updateTransform(),o/=hk.getLineScale(),o*=e[i.valueDim.index]);r.valueLineWidth=o}(n,c.symbolScale,l,i,c);var p=c.symbolSize,d=n.get(\"symbolOffset\");return F(d)&&(d=[Zi(d[0],p[0]),Zi(d[1],p[1])]),function(t,e,n,i,r,o,a,s,l,u,h,c){var p=h.categoryDim,d=h.valueDim,f=c.pxSign,g=Math.max(e[d.index]+s,0),y=g;if(i){var v=Math.abs(l),m=Q(t.get(\"symbolMargin\"),\"15%\")+\"\",_=!1;m.lastIndexOf(\"!\")===m.length-1&&(_=!0,m=m.slice(0,m.length-1));var x=Zi(m,e[d.index]),b=Math.max(g+2*x,0),w=_?0:2*x,S=pr(i),M=S?i:Dk((v+w)/b);b=g+2*(x=(v-M*g)/2/(_?M:M-1)),w=_?0:2*x,S||\"fixed\"===i||(M=u?Dk((Math.abs(u)+w)/b):0),y=M*b-w,c.repeatTimes=M,c.symbolMargin=x}var T=f*(y/2),C=c.pathPosition=[];C[p.index]=n[p.wh]/2,C[d.index]=\"start\"===a?T:\"end\"===a?l-T:l/2,o&&(C[0]+=o[0],C[1]+=o[1]);var D=c.bundlePosition=[];D[p.index]=n[p.xy],D[d.index]=n[d.xy];var A=c.barRectShape=I({},n);A[d.wh]=f*Math.max(Math.abs(n[d.wh]),Math.abs(C[d.index]+T)),A[p.wh]=n[p.wh];var L=c.clipShape={};L[p.xy]=-n[p.xy],L[p.wh]=h.ecSize[p.wh],L[d.xy]=0,L[d.wh]=n[d.wh]}(n,p,r,o,0,d,s,c.valueLineWidth,c.boundingLength,c.repeatCutLength,i,c),c}function dk(t,e){return t.toGlobalCoord(t.dataToCoord(t.scale.parse(e)))}function fk(t){var e=t.symbolPatternSize,n=fy(t.symbolType,-e/2,-e/2,e,e);return n.attr({culling:!0}),\"image\"!==n.type&&n.setStyle({strokeNoScale:!0}),n}function gk(t,e,n,i){var r=t.__pictorialBundle,o=n.symbolSize,a=n.valueLineWidth,s=n.pathPosition,l=e.valueDim,u=n.repeatTimes||0,h=0,c=o[e.valueDim.index]+a+2*n.symbolMargin;for(Ik(t,(function(t){t.__pictorialAnimationIndex=h,t.__pictorialRepeatTimes=u,h<u?Tk(t,null,f(h),n,i):Tk(t,null,{scaleX:0,scaleY:0},n,i,(function(){r.remove(t)})),h++}));h<u;h++){var p=fk(n);p.__pictorialAnimationIndex=h,p.__pictorialRepeatTimes=u,r.add(p);var d=f(h);Tk(p,{x:d.x,y:d.y,scaleX:0,scaleY:0},{scaleX:d.scaleX,scaleY:d.scaleY,rotation:d.rotation},n,i)}function f(t){var e=s.slice(),i=n.pxSign,r=t;return(\"start\"===n.symbolRepeatDirection?i>0:i<0)&&(r=u-1-t),e[l.index]=c*(r-u/2+.5)+s[l.index],{x:e[0],y:e[1],scaleX:n.symbolScale[0],scaleY:n.symbolScale[1],rotation:n.rotation}}}function yk(t,e,n,i){var r=t.__pictorialBundle,o=t.__pictorialMainPath;o?Tk(o,null,{x:n.pathPosition[0],y:n.pathPosition[1],scaleX:n.symbolScale[0],scaleY:n.symbolScale[1],rotation:n.rotation},n,i):(o=t.__pictorialMainPath=fk(n),r.add(o),Tk(o,{x:n.pathPosition[0],y:n.pathPosition[1],scaleX:0,scaleY:0,rotation:n.rotation},{scaleX:n.symbolScale[0],scaleY:n.symbolScale[1]},n,i))}function vk(t,e,n){var i=I({},e.barRectShape),r=t.__pictorialBarRect;r?Tk(r,null,{shape:i},e,n):(r=t.__pictorialBarRect=new ls({z2:2,shape:i,silent:!0,style:{stroke:\"transparent\",fill:\"transparent\",lineWidth:0}}),t.add(r))}function mk(t,e,n,i){if(n.symbolClip){var r=t.__pictorialClipPath,o=I({},n.clipShape),a=e.valueDim,s=n.animationModel,l=n.dataIndex;if(r)Hu(r,{shape:o},s,l);else{o[a.wh]=0,r=new ls({shape:o}),t.__pictorialBundle.setClipPath(r),t.__pictorialClipPath=r;var u={};u[a.wh]=n.clipShape[a.wh],ah[i?\"updateProps\":\"initProps\"](r,{shape:u},s,l)}}}function _k(t,e){var n=t.getItemModel(e);return n.getAnimationDelayParams=xk,n.isAnimationEnabled=bk,n}function xk(t){return{index:t.__pictorialAnimationIndex,count:t.__pictorialRepeatTimes}}function bk(){return this.parentModel.isAnimationEnabled()&&!!this.getShallow(\"animation\")}function wk(t,e,n,i){var r=new Ei,o=new Ei;return r.add(o),r.__pictorialBundle=o,o.x=n.bundlePosition[0],o.y=n.bundlePosition[1],n.symbolRepeat?gk(r,e,n):yk(r,0,n),vk(r,n,i),mk(r,e,n,i),r.__pictorialShapeStr=Mk(t,n),r.__pictorialSymbolMeta=n,r}function Sk(t,e,n,i){var r=i.__pictorialBarRect;r&&r.removeTextContent();var o=[];Ik(i,(function(t){o.push(t)})),i.__pictorialMainPath&&o.push(i.__pictorialMainPath),i.__pictorialClipPath&&(n=null),P(o,(function(t){Uu(t,{scaleX:0,scaleY:0},n,e,(function(){i.parent&&i.parent.remove(i)}))})),t.setItemGraphicEl(e,null)}function Mk(t,e){return[t.getItemVisual(e.dataIndex,\"symbol\")||\"none\",!!e.symbolRepeat,!!e.symbolClip].join(\":\")}function Ik(t,e,n){P(t.__pictorialBundle.children(),(function(i){i!==t.__pictorialBarRect&&e.call(n,i)}))}function Tk(t,e,n,i,r,o){e&&t.attr(e),i.symbolClip&&!r?n&&t.attr(n):n&&ah[r?\"updateProps\":\"initProps\"](t,n,i.animationModel,i.dataIndex,o)}function Ck(t,e,n){var i=n.dataIndex,r=n.itemModel,o=r.getModel(\"emphasis\"),a=o.getModel(\"itemStyle\").getItemStyle(),s=r.getModel([\"blur\",\"itemStyle\"]).getItemStyle(),l=r.getModel([\"select\",\"itemStyle\"]).getItemStyle(),u=r.getShallow(\"cursor\"),h=o.get(\"focus\"),c=o.get(\"blurScope\"),p=o.get(\"scale\");Ik(t,(function(t){if(t instanceof es){var e=t.style;t.useStyle(I({image:e.image,x:e.x,y:e.y,width:e.width,height:e.height},n.style))}else t.useStyle(n.style);var i=t.ensureState(\"emphasis\");i.style=a,p&&(i.scaleX=1.1*t.scaleX,i.scaleY=1.1*t.scaleY),t.ensureState(\"blur\").style=s,t.ensureState(\"select\").style=l,u&&(t.cursor=u),t.z2=n.z2}));var d=e.valueDim.posDesc[+(n.boundingLength>0)];hh(t.__pictorialBarRect,ch(r),{labelFetcher:e.seriesModel,labelDataIndex:i,defaultText:cw(e.seriesModel.getData(),i),inheritColor:n.style.fill,defaultOpacity:n.style.opacity,defaultOutsidePosition:d}),sl(t,h,c)}function Dk(t){var e=Math.round(t);return Math.abs(t-e)<1e-4?e:Math.ceil(t)}var Ak=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.hasSymbolVisual=!0,n.defaultSymbol=\"roundRect\",n}return n(e,t),e.prototype.getInitialData=function(e){return e.stack=null,t.prototype.getInitialData.apply(this,arguments)},e.type=\"series.pictorialBar\",e.dependencies=[\"grid\"],e.defaultOption=zh(qw.defaultOption,{symbol:\"circle\",symbolSize:null,symbolRotate:null,symbolPosition:null,symbolOffset:null,symbolMargin:null,symbolRepeat:!1,symbolRepeatDirection:\"end\",symbolClip:!1,symbolBoundingData:null,symbolPatternSize:400,barGap:\"-100%\",progressive:0,emphasis:{scale:!1},select:{itemStyle:{borderColor:\"#212121\"}}}),e}(qw);var Lk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._layers=[],n}return n(e,t),e.prototype.render=function(t,e,n){var i=t.getData(),r=this,o=this.group,a=t.getLayerSeries(),s=i.getLayout(\"layoutInfo\"),l=s.rect,u=s.boundaryGap;function h(t){return t.name}o.x=0,o.y=l.y+u[0];var c=new n_(this._layersSeries||[],a,h,h),p=[];function d(e,n,s){var l=r._layers;if(\"remove\"!==e){for(var u,h,c=[],d=[],f=a[n].indices,g=0;g<f.length;g++){var y=i.getItemLayout(f[g]),v=y.x,m=y.y0,_=y.y;c.push(v,m),d.push(v,m+_),u=i.getItemVisual(f[g],\"style\")}var x=i.getItemLayout(f[0]),b=t.getModel(\"label\").get(\"margin\"),w=t.getModel(\"emphasis\");if(\"add\"===e){var S=p[n]=new Ei;h=new kw({shape:{points:c,stackedOnPoints:d,smooth:.4,stackedOnSmooth:.4,smoothConstraint:!1},z2:0}),S.add(h),o.add(S),t.isAnimationEnabled()&&h.setClipPath(function(t,e,n){var i=new ls({shape:{x:t.x-10,y:t.y-10,width:0,height:t.height+20}});return Wu(i,{shape:{x:t.x-50,width:t.width+100,height:t.height+20}},e,n),i}(h.getBoundingRect(),t,(function(){h.removeClipPath()})))}else{S=l[s];h=S.childAt(0),o.add(S),p[n]=S,Hu(h,{shape:{points:c,stackedOnPoints:d}},t)}hh(h,ch(t),{labelDataIndex:f[g-1],defaultText:i.getName(f[g-1]),inheritColor:u.fill},{normal:{verticalAlign:\"middle\"}}),h.setTextConfig({position:null,local:!0});var M=h.getTextContent();M&&(M.x=x.x-b,M.y=x.y0+x.y/2),h.useStyle(u),i.setItemGraphicEl(n,h),cl(h,t),sl(h,w.get(\"focus\"),w.get(\"blurScope\"))}else o.remove(l[n])}c.add(V(d,this,\"add\")).update(V(d,this,\"update\")).remove(V(d,this,\"remove\")).execute(),this._layersSeries=a,this._layers=p},e.type=\"themeRiver\",e}(Tf);var kk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.useColorPaletteOnData=!0,n}return n(e,t),e.prototype.init=function(e){t.prototype.init.apply(this,arguments),this.legendVisualProvider=new IS(V(this.getData,this),V(this.getRawData,this))},e.prototype.fixData=function(t){var e=t.length,n={},i=Br(t,(function(t){return n.hasOwnProperty(t[0]+\"\")||(n[t[0]+\"\"]=-1),t[2]})),r=[];i.buckets.each((function(t,e){r.push({name:e,dataList:t})}));for(var o=r.length,a=0;a<o;++a){for(var s=r[a].name,l=0;l<r[a].dataList.length;++l){var u=r[a].dataList[l][0]+\"\";n[u]=a}for(var u in n)n.hasOwnProperty(u)&&n[u]!==a&&(n[u]=a,t[e]=[u,0,s],e++)}return t},e.prototype.getInitialData=function(t,e){for(var n=this.getReferringComponents(\"singleAxis\",Nr).models[0].get(\"type\"),i=N(t.data,(function(t){return void 0!==t[2]})),r=this.fixData(i||[]),o=[],a=this.nameMap=ht(),s=0,l=0;l<r.length;++l)o.push(r[l][2]),a.get(r[l][2])||(a.set(r[l][2],s),s++);var u=O_(r,{coordDimensions:[\"single\"],dimensionsDefine:[{name:\"time\",type:r_(n)},{name:\"value\",type:\"float\"},{name:\"name\",type:\"ordinal\"}],encodeDefine:{single:0,value:1,itemName:2}}),h=new L_(u,this);return h.initData(r),h},e.prototype.getLayerSeries=function(){for(var t=this.getData(),e=t.count(),n=[],i=0;i<e;++i)n[i]=i;var r=t.mapDimension(\"single\"),o=Br(n,(function(e){return t.get(\"name\",e)})),a=[];return o.buckets.each((function(e,n){e.sort((function(e,n){return t.get(r,e)-t.get(r,n)})),a.push({name:n,indices:e})})),a},e.prototype.getAxisTooltipData=function(t,e,n){F(t)||(t=t?[t]:[]);for(var i,r=this.getData(),o=this.getLayerSeries(),a=[],s=o.length,l=0;l<s;++l){for(var u=Number.MAX_VALUE,h=-1,c=o[l].indices.length,p=0;p<c;++p){var d=r.get(t[0],o[l].indices[p]),f=Math.abs(d-e);f<=u&&(i=d,u=f,h=o[l].indices[p])}a.push(h)}return{dataIndices:a,nestestValue:i}},e.prototype.formatTooltip=function(t,e,n){var i=this.getData();return tf(\"nameValue\",{name:i.getName(t),value:i.get(i.mapDimension(\"value\"),t)})},e.type=\"series.themeRiver\",e.dependencies=[\"singleAxis\"],e.defaultOption={zlevel:0,z:2,coordinateSystem:\"singleAxis\",boundaryGap:[\"10%\",\"10%\"],singleAxisIndex:0,animationEasing:\"linear\",label:{margin:4,show:!0,position:\"left\",fontSize:11},emphasis:{label:{show:!0}}},e}(ff);function Pk(t,e){t.eachSeriesByType(\"themeRiver\",(function(t){var e=t.getData(),n=t.coordinateSystem,i={},r=n.getRect();i.rect=r;var o=t.get(\"boundaryGap\"),a=n.getAxis();(i.boundaryGap=o,\"horizontal\"===a.orient)?(o[0]=Zi(o[0],r.height),o[1]=Zi(o[1],r.height),Ok(e,t,r.height-o[0]-o[1])):(o[0]=Zi(o[0],r.width),o[1]=Zi(o[1],r.width),Ok(e,t,r.width-o[0]-o[1]));e.setLayout(\"layoutInfo\",i)}))}function Ok(t,e,n){if(t.count())for(var i,r=e.coordinateSystem,o=e.getLayerSeries(),a=t.mapDimension(\"single\"),s=t.mapDimension(\"value\"),l=O(o,(function(e){return O(e.indices,(function(e){var n=r.dataToPoint(t.get(a,e));return n[1]=t.get(s,e),n}))})),u=function(t){for(var e=t.length,n=t[0].length,i=[],r=[],o=0,a=0;a<n;++a){for(var s=0,l=0;l<e;++l)s+=t[l][a][1];s>o&&(o=s),i.push(s)}for(var u=0;u<n;++u)r[u]=(o-i[u])/2;o=0;for(var h=0;h<n;++h){var c=i[h]+r[h];c>o&&(o=c)}return{y0:r,max:o}}(l),h=u.y0,c=n/u.max,p=o.length,d=o[0].indices.length,f=0;f<d;++f){i=h[f]*c,t.setItemLayout(o[0].indices[f],{layerIndex:0,x:l[0][f][0],y0:i,y:l[0][f][1]*c});for(var g=1;g<p;++g)i+=l[g-1][f][1]*c,t.setItemLayout(o[g].indices[f],{layerIndex:g,x:l[g][f][0],y0:i,y:l[g][f][1]*c})}}var Rk=function(t){function e(e,n,i,r){var o=t.call(this)||this;o.z2=2,o.textConfig={inside:!0},_s(o).seriesIndex=n.seriesIndex;var a=new cs({z2:4,silent:e.getModel().get([\"label\",\"silent\"])});return o.setTextContent(a),o.updateData(!0,e,n,i,r),o}return n(e,t),e.prototype.updateData=function(t,e,n,i,r){this.node=e,e.piece=this,n=n||this._seriesModel,i=i||this._ecModel;var o=this;_s(o).dataIndex=e.dataIndex;var a=e.getModel(),s=a.getModel(\"emphasis\"),l=e.getLayout(),u=I({},l);u.label=null;var h=e.getVisual(\"style\");h.lineJoin=\"bevel\";var c=e.getVisual(\"decal\");c&&(h.decal=Ey(c,r));var p=bS(a.getModel(\"itemStyle\"),u);I(u,p),P(Ss,(function(t){var e=o.ensureState(t),n=a.getModel([t,\"itemStyle\"]);e.style=n.getItemStyle();var i=bS(n,u);i&&(e.shape=i)})),t?(o.setShape(u),o.shape.r=l.r0,Hu(o,{shape:{r:l.r}},n,e.dataIndex)):Hu(o,{shape:u},n),o.useStyle(h),this._updateLabel(n);var d=a.getShallow(\"cursor\");d&&o.attr(\"cursor\",d),this._seriesModel=n||this._seriesModel,this._ecModel=i||this._ecModel;var f=s.get(\"focus\");sl(this,\"ancestor\"===f?e.getAncestorsIndices():\"descendant\"===f?e.getDescendantIndices():f,s.get(\"blurScope\"))},e.prototype._updateLabel=function(t){var e=this,n=this.node.getModel(),i=n.getModel(\"label\"),r=this.node.getLayout(),o=r.endAngle-r.startAngle,a=(r.startAngle+r.endAngle)/2,s=Math.cos(a),l=Math.sin(a),u=this,h=u.getTextContent(),c=this.node.dataIndex,p=i.get(\"minAngle\")/180*Math.PI,d=i.get(\"show\")&&!(null!=p&&Math.abs(o)<p);function f(t,e){var n=t.get(e);return null==n?i.get(e):n}h.ignore=!d,P(Ms,(function(i){var o=\"normal\"===i?n.getModel(\"label\"):n.getModel([i,\"label\"]),p=\"normal\"===i,d=p?h:h.ensureState(i),g=t.getFormattedLabel(c,i);p&&(g=g||e.node.name),d.style=ph(o,{},null,\"normal\"!==i,!0),g&&(d.style.text=g);var y=o.get(\"show\");null==y||p||(d.ignore=!y);var v,m=f(o,\"position\"),_=p?u:u.states[i],x=_.style.fill;_.textConfig={outsideFill:\"inherit\"===o.get(\"color\")?x:null,inside:\"outside\"!==m};var b=f(o,\"distance\")||0,w=f(o,\"align\");\"outside\"===m?(v=r.r+b,w=a>Math.PI/2?\"right\":\"left\"):w&&\"center\"!==w?\"left\"===w?(v=r.r0+b,a>Math.PI/2&&(w=\"right\")):\"right\"===w&&(v=r.r-b,a>Math.PI/2&&(w=\"left\")):(v=(r.r+r.r0)/2,w=\"center\"),d.style.align=w,d.style.verticalAlign=f(o,\"verticalAlign\")||\"middle\",d.x=v*s+r.cx,d.y=v*l+r.cy;var S=f(o,\"rotate\"),M=0;\"radial\"===S?(M=-a)<-Math.PI/2&&(M+=Math.PI):\"tangential\"===S?(M=Math.PI/2-a)>Math.PI/2?M-=Math.PI:M<-Math.PI/2&&(M+=Math.PI):\"number\"==typeof S&&(M=S*Math.PI/180),d.rotation=M})),h.dirtyStyle()},e}(Jl),Nk=\"sunburstRootToNode\",zk=\"sunburstHighlight\";var Ek=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){var r=this;this.seriesModel=t,this.api=n,this.ecModel=e;var o=t.getData(),a=o.tree.root,s=t.getViewRoot(),l=this.group,u=t.get(\"renderLabelForZeroData\"),h=[];s.eachNode((function(t){h.push(t)}));var c=this._oldChildren||[];!function(i,r){if(0===i.length&&0===r.length)return;function s(t){return t.getId()}function h(s,h){!function(i,r){u||!i||i.getValue()||(i=null);if(i!==a&&r!==a)if(r&&r.piece)i?(r.piece.updateData(!1,i,t,e,n),o.setItemGraphicEl(i.dataIndex,r.piece)):function(t){if(!t)return;t.piece&&(l.remove(t.piece),t.piece=null)}(r);else if(i){var s=new Rk(i,t,e,n);l.add(s),o.setItemGraphicEl(i.dataIndex,s)}}(null==s?null:i[s],null==h?null:r[h])}new n_(r,i,s,s).add(h).update(h).remove(B(h,null)).execute()}(h,c),function(i,o){o.depth>0?(r.virtualPiece?r.virtualPiece.updateData(!1,i,t,e,n):(r.virtualPiece=new Rk(i,t,e,n),l.add(r.virtualPiece)),o.piece.off(\"click\"),r.virtualPiece.on(\"click\",(function(t){r._rootToNode(o.parentNode)}))):r.virtualPiece&&(l.remove(r.virtualPiece),r.virtualPiece=null)}(a,s),this._initEvents(),this._oldChildren=h},e.prototype._initEvents=function(){var t=this;this.group.off(\"click\"),this.group.on(\"click\",(function(e){var n=!1;t.seriesModel.getViewRoot().eachNode((function(i){if(!n&&i.piece&&i.piece===e.target){var r=i.getModel().get(\"nodeClick\");if(\"rootToNode\"===r)t._rootToNode(i);else if(\"link\"===r){var o=i.getModel(),a=o.get(\"link\");if(a)Pc(a,o.get(\"target\",!0)||\"_blank\")}n=!0}}))}))},e.prototype._rootToNode=function(t){t!==this.seriesModel.getViewRoot()&&this.api.dispatchAction({type:Nk,from:this.uid,seriesId:this.seriesModel.id,targetNode:t})},e.prototype.containPoint=function(t,e){var n=e.getData().getItemLayout(0);if(n){var i=t[0]-n.cx,r=t[1]-n.cy,o=Math.sqrt(i*i+r*r);return o<=n.r&&o>=n.r0}},e.type=\"sunburst\",e}(Tf),Vk=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.ignoreStyleOnData=!0,n}return n(e,t),e.prototype.getInitialData=function(t,e){var n={name:t.name,children:t.data};Bk(n);var i=O(t.levels||[],(function(t){return new Oh(t,this,e)}),this),r=$I.createTree(n,this,(function(t){t.wrapMethod(\"getItemModel\",(function(t,e){var n=r.getNodeByDataIndex(e),o=i[n.depth];return o&&(t.parentModel=o),t}))}));return r.data},e.prototype.optionUpdated=function(){this.resetViewRoot()},e.prototype.getDataParams=function(e){var n=t.prototype.getDataParams.apply(this,arguments),i=this.getData().tree.getNodeByDataIndex(e);return n.treePathInfo=eT(i,this),n},e.prototype.getViewRoot=function(){return this._viewRoot},e.prototype.resetViewRoot=function(t){t?this._viewRoot=t:t=this._viewRoot;var e=this.getRawData().tree.root;t&&(t===e||e.contains(t))||(this._viewRoot=e)},e.prototype.enableAriaDecal=function(){lT(this)},e.type=\"series.sunburst\",e.defaultOption={zlevel:0,z:2,center:[\"50%\",\"50%\"],radius:[0,\"75%\"],clockwise:!0,startAngle:90,minAngle:0,stillShowZeroSum:!0,nodeClick:\"rootToNode\",renderLabelForZeroData:!1,label:{rotate:\"radial\",show:!0,opacity:1,align:\"center\",position:\"inside\",distance:5,silent:!0},itemStyle:{borderWidth:1,borderColor:\"white\",borderType:\"solid\",shadowBlur:0,shadowColor:\"rgba(0, 0, 0, 0.2)\",shadowOffsetX:0,shadowOffsetY:0,opacity:1},emphasis:{focus:\"descendant\"},blur:{itemStyle:{opacity:.2},label:{opacity:.1}},animationType:\"expansion\",animationDuration:1e3,animationDurationUpdate:500,data:[],levels:[],sort:\"desc\"},e}(ff);function Bk(t){var e=0;P(t.children,(function(t){Bk(t);var n=t.value;F(n)&&(n=n[0]),e+=n}));var n=t.value;F(n)&&(n=n[0]),(null==n||isNaN(n))&&(n=e),n<0&&(n=0),F(t.value)?t.value[0]=n:t.value=n}var Fk=Math.PI/180;function Gk(t,e,n){e.eachSeriesByType(t,(function(t){var e=t.get(\"center\"),i=t.get(\"radius\");F(i)||(i=[0,i]),F(e)||(e=[e,e]);var r=n.getWidth(),o=n.getHeight(),a=Math.min(r,o),s=Zi(e[0],r),l=Zi(e[1],o),u=Zi(i[0],a/2),h=Zi(i[1],a/2),c=-t.get(\"startAngle\")*Fk,p=t.get(\"minAngle\")*Fk,d=t.getData().tree.root,f=t.getViewRoot(),g=f.depth,y=t.get(\"sort\");null!=y&&Hk(f,y);var v=0;P(f.children,(function(t){!isNaN(t.getValue())&&v++}));var m=f.getValue(),_=Math.PI/(m||v)*2,x=f.depth>0,b=f.height-(x?-1:1),w=(h-u)/(b||1),S=t.get(\"clockwise\"),M=t.get(\"stillShowZeroSum\"),I=S?1:-1,T=function(t,e){if(t){var n=e;if(t!==d){var i=t.getValue(),r=0===m&&M?_:i*_;r<p&&(r=p),n=e+I*r;var o=t.depth-g-(x?-1:1),h=u+w*o,c=u+w*(o+1),f=t.getModel();null!=f.get(\"r0\")&&(h=Zi(f.get(\"r0\"),a/2)),null!=f.get(\"r\")&&(c=Zi(f.get(\"r\"),a/2)),t.setLayout({angle:r,startAngle:e,endAngle:n,clockwise:S,cx:s,cy:l,r0:h,r:c})}if(t.children&&t.children.length){var y=0;P(t.children,(function(t){y+=T(t,e+y)}))}return n-e}};if(x){var C=u,D=u+w,A=2*Math.PI;d.setLayout({angle:A,startAngle:c,endAngle:c+A,clockwise:S,cx:s,cy:l,r0:C,r:D})}T(f,c)}))}function Hk(t,e){var n=t.children||[];t.children=function(t,e){if(\"function\"==typeof e){var n=O(t,(function(t,e){var n=t.getValue();return{params:{depth:t.depth,height:t.height,dataIndex:t.dataIndex,getValue:function(){return n}},index:e}}));return n.sort((function(t,n){return e(t.params,n.params)})),O(n,(function(e){return t[e.index]}))}var i=\"asc\"===e;return t.sort((function(t,e){var n=(t.getValue()-e.getValue())*(i?1:-1);return 0===n?(t.dataIndex-e.dataIndex)*(i?-1:1):n}))}(n,e),n.length&&P(t.children,(function(t){Hk(t,e)}))}function Wk(t){var e={};t.eachSeriesByType(\"sunburst\",(function(t){var n=t.getData(),i=n.tree;i.eachNode((function(r){var o=r.getModel().getModel(\"itemStyle\").getItemStyle();o.fill||(o.fill=function(t,n,i){for(var r=t;r&&r.depth>1;)r=r.parentNode;var o=n.getColorFromPalette(r.name||r.dataIndex+\"\",e);return t.depth>1&&\"string\"==typeof o&&(o=Ue(o,(t.depth-1)/(i-1)*.5)),o}(r,t,i.root.height)),I(n.ensureUniqueItemVisual(r.dataIndex,\"style\"),o)}))}))}function Uk(t,e){return e=e||[0,0],O([\"x\",\"y\"],(function(n,i){var r=this.getAxis(n),o=e[i],a=t[i]/2;return\"category\"===r.type?r.getBandWidth():Math.abs(r.dataToCoord(o-a)-r.dataToCoord(o+a))}),this)}function Xk(t,e){return e=e||[0,0],O([0,1],(function(n){var i=e[n],r=t[n]/2,o=[],a=[];return o[n]=i-r,a[n]=i+r,o[1-n]=a[1-n]=e[1-n],Math.abs(this.dataToPoint(o)[n]-this.dataToPoint(a)[n])}),this)}function Yk(t,e){var n=this.getAxis(),i=e instanceof Array?e[0]:e,r=(t instanceof Array?t[0]:t)/2;return\"category\"===n.type?n.getBandWidth():Math.abs(n.dataToCoord(i-r)-n.dataToCoord(i+r))}function Zk(t,e){return e=e||[0,0],O([\"Radius\",\"Angle\"],(function(n,i){var r=this[\"get\"+n+\"Axis\"](),o=e[i],a=t[i]/2,s=\"category\"===r.type?r.getBandWidth():Math.abs(r.dataToCoord(o-a)-r.dataToCoord(o+a));return\"Angle\"===n&&(s=s*Math.PI/180),s}),this)}function jk(t,e,n,i){return t&&(t.legacy||!1!==t.legacy&&!n&&!i&&\"tspan\"!==e&&(\"text\"===e||dt(t,\"text\")))}function qk(t,e,n){var i,r,o,a=t;if(\"text\"===e)o=a;else{o={},dt(a,\"text\")&&(o.text=a.text),dt(a,\"rich\")&&(o.rich=a.rich),dt(a,\"textFill\")&&(o.fill=a.textFill),dt(a,\"textStroke\")&&(o.stroke=a.textStroke),r={type:\"text\",style:o,silent:!0},i={};var s=dt(a,\"textPosition\");n?i.position=s?a.textPosition:\"inside\":s&&(i.position=a.textPosition),dt(a,\"textPosition\")&&(i.position=a.textPosition),dt(a,\"textOffset\")&&(i.offset=a.textOffset),dt(a,\"textRotation\")&&(i.rotation=a.textRotation),dt(a,\"textDistance\")&&(i.distance=a.textDistance)}return Kk(o,t),P(o.rich,(function(t){Kk(t,t)})),{textConfig:i,textContent:r}}function Kk(t,e){e&&(e.font=e.textFont||e.font,dt(e,\"textStrokeWidth\")&&(t.lineWidth=e.textStrokeWidth),dt(e,\"textAlign\")&&(t.align=e.textAlign),dt(e,\"textVerticalAlign\")&&(t.verticalAlign=e.textVerticalAlign),dt(e,\"textLineHeight\")&&(t.lineHeight=e.textLineHeight),dt(e,\"textWidth\")&&(t.width=e.textWidth),dt(e,\"textHeight\")&&(t.height=e.textHeight),dt(e,\"textBackgroundColor\")&&(t.backgroundColor=e.textBackgroundColor),dt(e,\"textPadding\")&&(t.padding=e.textPadding),dt(e,\"textBorderColor\")&&(t.borderColor=e.textBorderColor),dt(e,\"textBorderWidth\")&&(t.borderWidth=e.textBorderWidth),dt(e,\"textBorderRadius\")&&(t.borderRadius=e.textBorderRadius),dt(e,\"textBoxShadowColor\")&&(t.shadowColor=e.textBoxShadowColor),dt(e,\"textBoxShadowBlur\")&&(t.shadowBlur=e.textBoxShadowBlur),dt(e,\"textBoxShadowOffsetX\")&&(t.shadowOffsetX=e.textBoxShadowOffsetX),dt(e,\"textBoxShadowOffsetY\")&&(t.shadowOffsetY=e.textBoxShadowOffsetY))}function $k(t,e,n){var i=t;i.textPosition=i.textPosition||n.position||\"inside\",null!=n.offset&&(i.textOffset=n.offset),null!=n.rotation&&(i.textRotation=n.rotation),null!=n.distance&&(i.textDistance=n.distance);var r=i.textPosition.indexOf(\"inside\")>=0,o=t.fill||\"#000\";Jk(i,e);var a=null==i.textFill;return r?a&&(i.textFill=n.insideFill||\"#fff\",!i.textStroke&&n.insideStroke&&(i.textStroke=n.insideStroke),!i.textStroke&&(i.textStroke=o),null==i.textStrokeWidth&&(i.textStrokeWidth=2)):(a&&(i.textFill=t.fill||n.outsideFill||\"#000\"),!i.textStroke&&n.outsideStroke&&(i.textStroke=n.outsideStroke)),i.text=e.text,i.rich=e.rich,P(e.rich,(function(t){Jk(t,t)})),i}function Jk(t,e){e&&(dt(e,\"fill\")&&(t.textFill=e.fill),dt(e,\"stroke\")&&(t.textStroke=e.fill),dt(e,\"lineWidth\")&&(t.textStrokeWidth=e.lineWidth),dt(e,\"font\")&&(t.font=e.font),dt(e,\"fontStyle\")&&(t.fontStyle=e.fontStyle),dt(e,\"fontWeight\")&&(t.fontWeight=e.fontWeight),dt(e,\"fontSize\")&&(t.fontSize=e.fontSize),dt(e,\"fontFamily\")&&(t.fontFamily=e.fontFamily),dt(e,\"align\")&&(t.textAlign=e.align),dt(e,\"verticalAlign\")&&(t.textVerticalAlign=e.verticalAlign),dt(e,\"lineHeight\")&&(t.textLineHeight=e.lineHeight),dt(e,\"width\")&&(t.textWidth=e.width),dt(e,\"height\")&&(t.textHeight=e.height),dt(e,\"backgroundColor\")&&(t.textBackgroundColor=e.backgroundColor),dt(e,\"padding\")&&(t.textPadding=e.padding),dt(e,\"borderColor\")&&(t.textBorderColor=e.borderColor),dt(e,\"borderWidth\")&&(t.textBorderWidth=e.borderWidth),dt(e,\"borderRadius\")&&(t.textBorderRadius=e.borderRadius),dt(e,\"shadowColor\")&&(t.textBoxShadowColor=e.shadowColor),dt(e,\"shadowBlur\")&&(t.textBoxShadowBlur=e.shadowBlur),dt(e,\"shadowOffsetX\")&&(t.textBoxShadowOffsetX=e.shadowOffsetX),dt(e,\"shadowOffsetY\")&&(t.textBoxShadowOffsetY=e.shadowOffsetY),dt(e,\"textShadowColor\")&&(t.textShadowColor=e.textShadowColor),dt(e,\"textShadowBlur\")&&(t.textShadowBlur=e.textShadowBlur),dt(e,\"textShadowOffsetX\")&&(t.textShadowOffsetX=e.textShadowOffsetX),dt(e,\"textShadowOffsetY\")&&(t.textShadowOffsetY=e.textShadowOffsetY))}var Qk=La.CMD,tP=2*Math.PI,eP=[\"x\",\"y\"],nP=[\"width\",\"height\"],iP=[];function rP(t,e){return Math.abs(t-e)<1e-5}function oP(t){var e,n,i,r,o,a=t.data,s=t.len(),l=[],u=0,h=0,c=0,p=0;function d(t,n){e&&e.length>2&&l.push(e),e=[t,n]}function f(t,n,i,r){rP(t,i)&&rP(n,r)||e.push(t,n,i,r,i,r)}function g(t,n,i,r,o,a){var s=Math.abs(n-t),l=4*Math.tan(s/4)/3,u=n<t?-1:1,h=Math.cos(t),c=Math.sin(t),p=Math.cos(n),d=Math.sin(n),f=h*o+i,g=c*a+r,y=p*o+i,v=d*a+r,m=o*l*u,_=a*l*u;e.push(f-m*c,g+_*h,y+m*d,v-_*p,y,v)}for(var y=0;y<s;){var v=a[y++],m=1===y;switch(m&&(c=u=a[y],p=h=a[y+1],v!==Qk.L&&v!==Qk.C&&v!==Qk.Q||(e=[c,p])),v){case Qk.M:u=c=a[y++],h=p=a[y++],d(c,p);break;case Qk.L:f(u,h,n=a[y++],i=a[y++]),u=n,h=i;break;case Qk.C:e.push(a[y++],a[y++],a[y++],a[y++],u=a[y++],h=a[y++]);break;case Qk.Q:n=a[y++],i=a[y++],r=a[y++],o=a[y++],e.push(u+2/3*(n-u),h+2/3*(i-h),r+2/3*(n-r),o+2/3*(i-o),r,o),u=r,h=o;break;case Qk.A:var _=a[y++],x=a[y++],b=a[y++],w=a[y++],S=a[y++],M=a[y++]+S;y+=1;var I=!a[y++];n=Math.cos(S)*b+_,i=Math.sin(S)*w+x,m?d(c=n,p=i):f(u,h,n,i),u=Math.cos(M)*b+_,h=Math.sin(M)*w+x;for(var T=(I?-1:1)*Math.PI/2,C=S;I?C>M:C<M;C+=T){g(C,I?Math.max(C+T,M):Math.min(C+T,M),_,x,b,w)}break;case Qk.R:c=u=a[y++],p=h=a[y++],n=c+a[y++],i=p+a[y++],d(n,p),f(n,p,n,i),f(n,i,c,i),f(c,i,c,p),f(c,p,n,p);break;case Qk.Z:e&&f(u,h,c,p),u=c,h=p}}return e&&e.length>2&&l.push(e),l}function aP(t,e){var n=t.length,i=e.length;if(n===i)return[t,e];for(var r=n<i?t:e,o=Math.min(n,i),a=Math.abs(i-n)/6,s=(o-2)/6,l=Math.ceil(a/s)+1,u=[r[0],r[1]],h=a,c=[],p=[],d=2;d<o;){var f=r[d-2],g=r[d-1],y=r[d++],v=r[d++],m=r[d++],_=r[d++],x=r[d++],b=r[d++];if(h<=0)u.push(y,v,m,_,x,b);else{for(var w=Math.min(h,l-1)+1,S=1;S<=w;S++){var M=S/w;Go(f,y,m,x,M,c),Go(g,v,_,b,M,p),f=c[3],g=p[3],u.push(c[1],p[1],c[2],p[2],f,g),y=c[5],v=p[5],m=c[6],_=p[6]}h-=w-1}}return r===t?[u,e]:[t,u]}function sP(t,e){for(var n=t.length,i=t[n-2],r=t[n-1],o=[],a=0;a<e.length;)o[a++]=i,o[a++]=r;return o}function lP(t){for(var e=0,n=0,i=0,r=t.length,o=0,a=r-2;o<r;a=o,o+=2){var s=t[a],l=t[a+1],u=t[o],h=t[o+1],c=s*h-u*l;e+=c,n+=(s+u)*c,i+=(l+h)*c}return 0===e?[t[0]||0,t[1]||0]:[n/e/3,i/e/3,e]}function uP(t,e,n,i){for(var r=(t.length-2)/6,o=1/0,a=0,s=t.length,l=s-2,u=0;u<r;u++){for(var h=6*u,c=0,p=0;p<s;p+=2){var d=0===p?h:(h+p-2)%l+2,f=t[d]-n[0],g=t[d+1]-n[1],y=e[p]-i[0]-f,v=e[p+1]-i[1]-g;c+=y*y+v*v}c<o&&(o=c,a=u)}return a}function hP(t){for(var e=[],n=t.length,i=0;i<n;i+=2)e[i]=t[n-i-2],e[i+1]=t[n-i-1];return e}function cP(t,e,n){var i,r;if(!t||!e)return e;!t.path&&t.createPathProxy(),(i=t.path).beginPath(),t.buildPath(i,t.shape),!e.path&&e.createPathProxy(),(r=e.path)===i&&(r=new La(!1)),r.beginPath(),fP(e)?e.__oldBuildPath(r,e.shape):e.buildPath(r,e.shape);var o=function(t,e){for(var n,i,r,o=[],a=[],s=0;s<Math.max(t.length,e.length);s++){var l=t[s],u=e[s],h=void 0,c=void 0;l?u?(i=h=(n=aP(l,u))[0],r=c=n[1]):(c=sP(r||l,l),h=l):(h=sP(i||u,u),c=u),o.push(h),a.push(c)}return[o,a]}(oP(i),oP(r)),a=function(t,e,n,i){for(var r,o=[],a=0;a<t.length;a++){var s=t[a],l=e[a],u=lP(s),h=lP(l);null==r&&(r=u[2]<0!=h[2]<0);var c=[],p=[],d=0,f=1/0,g=[],y=s.length;r&&(s=hP(s));for(var v=6*uP(s,l,u,h),m=y-2,_=0;_<m;_+=2){var x=(v+_)%m+2;c[_+2]=s[x]-u[0],c[_+3]=s[x+1]-u[1]}if(c[0]=s[v]-u[0],c[1]=s[v+1]-u[1],n>0)for(var b=i/n,w=-i/2;w<=i/2;w+=b){var S=Math.sin(w),M=Math.cos(w),I=0;for(_=0;_<s.length;_+=2){var T=c[_],C=c[_+1],D=l[_]-h[0],A=l[_+1]-h[1],L=D*M-A*S,k=D*S+A*M;g[_]=L,g[_+1]=k;var P=L-T,O=k-C;I+=P*P+O*O}if(I<f){f=I,d=w;for(var R=0;R<g.length;R++)p[R]=g[R]}}else for(var N=0;N<y;N+=2)p[N]=l[N]-h[0],p[N+1]=l[N+1]-h[1];o.push({from:c,to:p,fromCp:u,toCp:h,rotation:-d})}return o}(o[0],o[1],10,Math.PI);!function(t,e,n){if(fP(t))return void dP(t,e,n);var i=t;i.__oldBuildPath=i.buildPath,i.buildPath=pP,dP(i,e,n)}(e,a,0);var s=n&&n.done,l=n&&n.aborted,u=n&&n.during;return e.animateTo({__morphT:1},T({during:function(t){e.dirtyShape(),u&&u(t)},done:function(){var t;fP(t=e)&&(t.buildPath=t.__oldBuildPath,t.__oldBuildPath=t.__morphingData=null),e.createPathProxy(),e.dirtyShape(),s&&s()},aborted:function(){l&&l()}},n)),e}function pP(t){for(var e=this.__morphingData,n=this.__morphT,i=1-n,r=[],o=0;o<e.length;o++){var a=e[o],s=a.from,l=a.to,u=a.rotation*n,h=a.fromCp,c=a.toCp,p=Math.sin(u),d=Math.cos(u);Ot(r,h,c,n);for(var f=0;f<s.length;f+=2){var g=s[f],y=s[f+1],v=g*i+l[f]*n,m=y*i+l[f+1]*n;iP[f]=v*d-m*p+r[0],iP[f+1]=v*p+m*d+r[1]}for(f=0;f<s.length;)0===f&&t.moveTo(iP[f++],iP[f++]),t.bezierCurveTo(iP[f++],iP[f++],iP[f++],iP[f++],iP[f++],iP[f++])}}function dP(t,e,n){t.__morphingData=e,t.__morphT=n}function fP(t){return null!=t.__oldBuildPath}function gP(t){return!!t.__combiningSubList}function yP(t,e,n,i){for(var r=[],o=0,a=0;a<t.length;a++){var s=t[a];if(gP(s)){for(var l=s.__combiningSubList,u=0;u<l.length;u++)r.push(l[u]);o+=l.length}else r.push(s),o++}if(o){var h=n?n.dividingMethod:null,c=wP(e,o,h);rt(c.length===o);var p=n&&n.done,d=n&&n.aborted,f=n&&n.during,g=0,y=!1,v=T({during:function(t){f&&f(t)},done:function(){++g===c.length&&(!function(t){if(!gP(t))return;var e=t;vP(e,null),e.addSelfToZr=e.__oldAddSelfToZr,e.removeSelfFromZr=e.__oldRemoveSelfFromZr,e.buildPath=e.__oldBuildPath,e.childrenRef=e.__combiningSubList=e.__oldAddSelfToZr=e.__oldRemoveSelfFromZr=e.__oldBuildPath=null}(e),p&&p())},aborted:function(){y||(y=!0,d&&d())}},n);for(a=0;a<o;a++){var m=r[a],_=c[a];i&&i(e,_,!0),cP(m,_,v)}return function(t,e){if(gP(t))return void vP(t,e);var n=t;vP(n,e),n.__oldAddSelfToZr=t.addSelfToZr,n.__oldRemoveSelfFromZr=t.removeSelfFromZr,n.addSelfToZr=mP,n.removeSelfFromZr=xP,n.__oldBuildPath=n.buildPath,n.buildPath=ft,n.childrenRef=bP}(e,c),{fromIndividuals:r,toIndividuals:c,count:o}}}function vP(t,e){if(t.__combiningSubList!==e){if(_P(t,\"removeSelfFromZr\"),t.__combiningSubList=e,e)for(var n=0;n<e.length;n++)e[n].parent=t;_P(t,\"addSelfToZr\")}}function mP(t){this.__oldAddSelfToZr(t),_P(this,\"addSelfToZr\")}function _P(t,e){var n=t.__combiningSubList,i=t.__zr;if(n&&i)for(var r=0;r<n.length;r++){n[r][e](i)}}function xP(t){this.__oldRemoveSelfFromZr(t);for(var e=this.__combiningSubList,n=0;n<e.length;n++){e[n].removeSelfFromZr(t)}}function bP(){return this.__combiningSubList}function wP(t,e,n){return\"duplicate\"===n?SP(t,e):function(t,e){var n=[];if(e<=0)return n;if(1===e)return SP(t,e);if(t instanceof ls)for(var i=(c=t.shape).height>c.width?1:0,r=nP[i],o=eP[i],a=c[r]/e,s=c[o],l=0;l<e;l++,s+=a){var u={x:c.x,y:c.y,width:c.width,height:c.height};u[o]=s,u[r]=l<e-1?a:c[o]+c[r]-s;var h=new ls({shape:u});n.push(h)}else{if(!(t instanceof Jl))return SP(t,e);var c,p=(c=t.shape).clockwise,d=c.startAngle,f=c.endAngle,g=(function(t,e,n){return e+tP*Math[n?\"ceil\":\"floor\"]((t-e)/tP)}(d,c.endAngle,p)-d)/e,y=d;for(l=0;l<e;l++,y+=g){h=new Jl({shape:{cx:c.cx,cy:c.cy,r:c.r,r0:c.r0,clockwise:p,startAngle:y,endAngle:l===e-1?f:y+g}});n.push(h)}}return n}(t,e)}function SP(t,e){var n=[];if(e<=0)return n;for(var i=t.constructor,r=0;r<e;r++){var o=new i({shape:w(t.shape)});n.push(o)}return n}var MP=kr(),IP={x:1,y:1,scaleX:1,scaleY:1,originX:1,originY:1,rotation:1},TP=(E(IP).join(\", \"),{color:\"fill\",borderColor:\"stroke\"}),CP={symbol:1,symbolSize:1,symbolKeepAspect:1,legendIcon:1,visualMeta:1,liftZ:1,decal:1},DP=\"emphasis\",AP=\"normal\",LP=\"blur\",kP=\"select\",PP=[AP,DP,LP,kP],OP={normal:[\"itemStyle\"],emphasis:[DP,\"itemStyle\"],blur:[LP,\"itemStyle\"],select:[kP,\"itemStyle\"]},RP={normal:[\"label\"],emphasis:[DP,\"label\"],blur:[LP,\"label\"],select:[kP,\"label\"]},NP={normal:{},emphasis:{},blur:{},select:{}},zP={position:[\"x\",\"y\"],scale:[\"scaleX\",\"scaleY\"],origin:[\"originX\",\"originY\"]},EP=new oi,VP={cartesian2d:function(t){var e=t.master.getRect();return{coordSys:{type:\"cartesian2d\",x:e.x,y:e.y,width:e.width,height:e.height},api:{coord:function(e){return t.dataToPoint(e)},size:V(Uk,t)}}},geo:function(t){var e=t.getBoundingRect();return{coordSys:{type:\"geo\",x:e.x,y:e.y,width:e.width,height:e.height,zoom:t.getZoom()},api:{coord:function(e){return t.dataToPoint(e)},size:V(Xk,t)}}},singleAxis:function(t){var e=t.getRect();return{coordSys:{type:\"singleAxis\",x:e.x,y:e.y,width:e.width,height:e.height},api:{coord:function(e){return t.dataToPoint(e)},size:V(Yk,t)}}},polar:function(t){var e=t.getRadiusAxis(),n=t.getAngleAxis(),i=e.getExtent();return i[0]>i[1]&&i.reverse(),{coordSys:{type:\"polar\",cx:t.cx,cy:t.cy,r:i[1],r0:i[0]},api:{coord:function(i){var r=e.dataToRadius(i[0]),o=n.dataToAngle(i[1]),a=t.coordToPoint([r,o]);return a.push(r,o*Math.PI/180),a},size:V(Zk,t)}}},calendar:function(t){var e=t.getRect(),n=t.getRangeInfo();return{coordSys:{type:\"calendar\",x:e.x,y:e.y,width:e.width,height:e.height,cellWidth:t.getCellWidth(),cellHeight:t.getCellHeight(),rangeInfo:{start:n.start,end:n.end,weeks:n.weeks,dayCount:n.allDay}},api:{coord:function(e,n){return t.dataToPoint(e,n)}}}}},BP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){this.currentZLevel=this.get(\"zlevel\",!0),this.currentZ=this.get(\"z\",!0)},e.prototype.getInitialData=function(t,e){return F_(this.getSource(),this)},e.prototype.getDataParams=function(e,n,i){var r=t.prototype.getDataParams.call(this,e,n);return i&&(r.info=MP(i).info),r},e.type=\"series.custom\",e.dependencies=[\"grid\",\"polar\",\"geo\",\"singleAxis\",\"calendar\"],e.defaultOption={coordinateSystem:\"cartesian2d\",zlevel:0,z:2,legendHoverLink:!0,clip:!1},e}(ff),FP=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){var r=this._data,o=t.getData(),a=this.group,s=aO(t,o,e,n);r||a.removeAll();var l=t.__transientTransitionOpt;if(!l||null!=l.from&&null!=l.to){var u=new bO(t,l),h=l?\"multiple\":\"oneToOne\";new n_(r?r.getIndices():[],o.getIndices(),GP(r,h,l&&l.from),GP(o,h,l&&l.to),null,h).add((function(e){lO(n,null,e,s(e,i),t,a,o,null)})).remove((function(e){vO(r.getItemGraphicEl(e),t,a)})).update((function(e,l){u.reset(\"oneToOne\");var h=r.getItemGraphicEl(l);u.findAndAddFrom(h),u.hasFrom()&&(xO(h,a),h=null),lO(n,h,e,s(e,i),t,a,o,u),u.applyMorphing()})).updateManyToOne((function(e,l){u.reset(\"manyToOne\");for(var h=0;h<l.length;h++){var c=r.getItemGraphicEl(l[h]);u.findAndAddFrom(c),xO(c,a)}lO(n,null,e,s(e,i),t,a,o,u),u.applyMorphing()})).updateOneToMany((function(e,l){u.reset(\"oneToMany\");var h=e.length,c=r.getItemGraphicEl(l);u.findAndAddFrom(c),xO(c,a);for(var p=0;p<h;p++)lO(n,null,e[p],s(e[p],i),t,a,o,u);u.applyMorphing()})).execute()}else r&&r.each((function(e){vO(r.getItemGraphicEl(e),t,a)})),o.each((function(e){lO(n,null,e,s(e,i),t,a,o,null)}));var c=t.get(\"clip\",!0)?Rw(t.coordinateSystem,!1,t):null;c?a.setClipPath(c):a.removeClipPath(),this._data=o},e.prototype.incrementalPrepareRender=function(t,e,n){this.group.removeAll(),this._data=null},e.prototype.incrementalRender=function(t,e,n,i,r){var o=e.getData(),a=aO(e,o,n,i);function s(t){t.isGroup||(t.incremental=!0,t.ensureState(\"emphasis\").hoverLayer=!0)}for(var l=t.start;l<t.end;l++){var u=lO(null,null,l,a(l,r),e,this.group,o,null);u&&u.traverse(s)}},e.prototype.filterForExposedEvent=function(t,e,n,i){var r=e.element;if(null==r||n.name===r)return!0;for(;(n=n.__hostTarget||n.parent)&&n!==this.group;)if(n.name===r)return!0;return!1},e.type=\"custom\",e}(Tf);function GP(t,e,n){if(t){if(\"oneToOne\"===e)return function(e,n){return t.getId(n)};var i=t.getDimension(n),r=t.getDimensionInfo(i);if(!r){var o=\"\";0,vr(o)}var a=r.ordinalMeta;return function(e,n){var r=t.get(i,n);return a&&(r=a.categories[r]),null==r||J(r)?e+\"\":\"_ec_\"+r}}}function HP(t){var e,n=t.type;if(\"path\"===n){var i=t.shape,r=null!=i.width&&null!=i.height?{x:i.x||0,y:i.y||0,width:i.width,height:i.height}:null,o=mO(i);e=Nu(o,null,r,i.layout||\"center\"),MP(e).customPathData=o}else if(\"image\"===n)e=new es({}),MP(e).customImagePath=t.style.image;else if(\"text\"===n)e=new cs({});else if(\"group\"===n)e=new Ei;else{if(\"compoundPath\"===n)throw new Error('\"compoundPath\" is not supported yet.');var a=Ru(n);if(!a){var s=\"\";0,vr(s)}e=new a}return MP(e).customGraphicType=n,e.name=t.name,e.z2EmphasisLift=1,e.z2SelectLift=1,e}function WP(t,e,n,i,r,o,a,s,l,u){var h={},c={},p=e.isGroup?null:e;!n&&YP(\"shape\",e,null,r,h,l),ZP(\"shape\",r,c),!n&&YP(\"extra\",e,null,r,h,l),ZP(\"extra\",r,c),!n&&jP(e,null,r,h,l),function(t,e){iO(t,e,\"position\"),iO(t,e,\"scale\"),iO(t,e,\"origin\"),rO(t,e,\"x\"),rO(t,e,\"y\"),rO(t,e,\"scaleX\"),rO(t,e,\"scaleY\"),rO(t,e,\"originX\"),rO(t,e,\"originY\"),rO(t,e,\"rotation\")}(r,c);var d=a&&a.normal.cfg;if(d&&e.setTextConfig(d),\"text\"===e.type&&o){var f=o;dt(f,\"textFill\")&&(f.fill=f.textFill),dt(f,\"textStroke\")&&(f.stroke=f.textStroke)}if(o){var g=void 0,y=_O(e)?o.decal:null;t&&y&&(y.dirty=!0,g=Ey(y,t)),o.__decalPattern=g}return!n&&qP(e,null,r,o,h,l),p&&dt(r,\"invisible\")&&(p.invisible=r.invisible),n||(UP(e,c,o),XP(e,i,r,s,h,l)),dt(r,\"silent\")&&(e.silent=r.silent),dt(r,\"ignore\")&&(e.ignore=r.ignore),u||dt(r,\"info\")&&(MP(e).info=r.info),o?e.dirty():e.markRedraw(),n?c:null}function UP(t,e,n){var i=t.isGroup?null:t;if(i&&n){var r=n.__decalPattern,o=void 0;r&&(o=n.decal,n.decal=r),i.useStyle(n),r&&(n.decal=o);for(var a=i.animators,s=0;s<a.length;s++){var l=a[s];\"style\"===l.targetName&&l.changeTarget(i.style)}}e&&t.attr(e)}function XP(t,e,n,i,r,o){if(r){var a=n.during;MP(t).userDuring=a;var s={dataIndex:e,isFrom:!0,during:a?V(tO,{el:t,userDuring:a}):null};o?Wu(t,r,i,s):Hu(t,r,i,s)}}function YP(t,e,n,i,r,o){var a=i[t];if(a){var s,l=e[t],u=a.enterFrom;if(o&&u){!s&&(s=r[t]={});for(var h=E(u),c=0;c<h.length;c++){s[_=h[c]]=u[_]}}if(!o&&l&&(null==n||\"shape\"!==t))if(a.transition){!s&&(s=r[t]={});var p=xr(a.transition);for(c=0;c<p.length;c++){var d=l[_=p[c]];0,s[_]=d}}else if(D(i.transition,t)>=0){!s&&(s=r[t]={});var f=E(l);for(c=0;c<f.length;c++){d=l[_=f[c]];KP(a[_],d)&&(s[_]=d)}}var g=a.leaveTo;if(g){var y=$P(e),v=y[t]||(y[t]={}),m=E(g);for(c=0;c<m.length;c++){var _;v[_=m[c]]=g[_]}}}}function ZP(t,e,n){var i=e[t];if(i)for(var r=n[t]={},o=E(i),a=0;a<o.length;a++){var s=o[a];r[s]=cn(i[s])}}function jP(t,e,n,i,r){var o=n.enterFrom;if(r&&o)for(var a=E(o),s=0;s<a.length;s++){0,i[f=a[s]]=o[f]}if(!r)if(e){var l=function(t,e){if(!t||t===e||t.parent===e.parent)return t;var n=EP.transform||(EP.transform=Gn([])),i=t.getComputedTransform();i?Hn(n,i):Gn(n);var r=e.parent;r&&r.getComputedTransform();return EP.originX=t.originX,EP.originY=t.originY,EP.parent=r,EP.decomposeTransform(),EP}(e,t);oO(i,\"x\",l),oO(i,\"y\",l),oO(i,\"scaleX\",l),oO(i,\"scaleY\",l),oO(i,\"originX\",l),oO(i,\"originY\",l),oO(i,\"rotation\",l)}else if(n.transition){var u=xr(n.transition);for(s=0;s<u.length;s++){if(\"style\"!==(f=u[s])&&\"shape\"!==f&&\"extra\"!==f){var h=t[f];0,i[f]=h}}}else oO(i,\"x\",t),oO(i,\"y\",t);var c=n.leaveTo;if(c){var p=$P(t),d=E(c);for(s=0;s<d.length;s++){var f;0,p[f=d[s]]=c[f]}}}function qP(t,e,n,i,r,o){if(i){var a,s=(e||t).style,l=i.enterFrom;if(o&&l){var u=E(l);!a&&(a=r.style={});for(var h=0;h<u.length;h++){a[x=u[h]]=l[x]}}if(!o&&s)if(i.transition){var c=xr(i.transition);!a&&(a=r.style={});for(h=0;h<c.length;h++){var p=s[x=c[h]];a[x]=p}}else if(t.getAnimationStyleProps&&D(n.transition,\"style\")>=0){var d=t.getAnimationStyleProps(),f=d?d.style:null;if(f){!a&&(a=r.style={});var g=E(i);for(h=0;h<g.length;h++){if(f[x=g[h]]){p=s[x];a[x]=p}}}}var y=i.leaveTo;if(y){var v=E(y),m=$P(t),_=m.style||(m.style={});for(h=0;h<v.length;h++){var x;_[x=v[h]]=y[x]}}}}function KP(t,e){return k(t)?t!==e:null!=t&&isFinite(t)}function $P(t){var e=MP(t);return e.leaveToProps||(e.leaveToProps={})}var JP={},QP={setTransform:function(t,e){return JP.el[t]=e,this},getTransform:function(t){return JP.el[t]},setShape:function(t,e){return(JP.el.shape||(JP.el.shape={}))[t]=e,JP.isShapeDirty=!0,this},getShape:function(t){var e=JP.el.shape;if(e)return e[t]},setStyle:function(t,e){var n=JP.el.style;return n&&(n[t]=e,JP.isStyleDirty=!0),this},getStyle:function(t){var e=JP.el.style;if(e)return e[t]},setExtra:function(t,e){return(JP.el.extra||(JP.el.extra={}))[t]=e,this},getExtra:function(t){var e=JP.el.extra;if(e)return e[t]}};function tO(){var t=this,e=t.el;if(e){var n=MP(e).userDuring,i=t.userDuring;n===i?(JP.el=e,JP.isShapeDirty=!1,JP.isStyleDirty=!1,i(QP),JP.isShapeDirty&&e.dirtyShape&&e.dirtyShape(),JP.isStyleDirty&&e.dirtyStyle&&e.dirtyStyle()):t.el=t.userDuring=null}}function eO(t,e,n,i,r,o,a){var s=e.isGroup?null:e,l=r&&r[t].cfg;if(s){var u=s.ensureState(t);if(!1===i){var h=s.getState(t);h&&(h.style=null)}else u.style=i||null;l&&(u.textConfig=l),Xs(s)}}function nO(t,e,n){var i=n===AP,r=i?e:pO(e,n),o=r?r.z2:null;null!=o&&((i?t:t.ensureState(n)).z2=o||0)}function iO(t,e,n,i){var r=t[n],o=zP[n];r&&(i?(e[o[0]]=i[o[0]],e[o[1]]=i[o[1]]):(e[o[0]]=r[0],e[o[1]]=r[1]))}function rO(t,e,n,i){null!=t[n]&&(e[n]=i?i[n]:t[n])}function oO(t,e,n){n&&(t[e]=n[e])}function aO(t,e,n,i){var r=t.get(\"renderItem\"),o=t.coordinateSystem,a={};o&&(a=o.prepareCustoms?o.prepareCustoms(o):VP[o.type](o));for(var s,l,u=T({getWidth:i.getWidth,getHeight:i.getHeight,getZr:i.getZr,getDevicePixelRatio:i.getDevicePixelRatio,value:function(t,n){return null==n&&(n=s),e.get(e.getDimension(t||0),n)},style:function(n,i){0;null==i&&(i=s);var r=e.getItemVisual(i,\"style\"),o=r&&r.fill,a=r&&r.opacity,l=m(i,AP).getItemStyle();null!=o&&(l.fill=o),null!=a&&(l.opacity=a);var u={inheritColor:H(o)?o:\"#000\"},h=_(i,AP),c=ph(h,null,u,!1,!0);c.text=h.getShallow(\"show\")?tt(t.getFormattedLabel(i,AP),cw(e,i)):null;var p=dh(h,u,!1);return b(n,l),l=$k(l,c,p),n&&x(l,n),l.legacy=!0,l},ordinalRawValue:function(t,n){null==n&&(n=s);var i=e.getDimensionInfo(t||0);if(!i)return;var r=e.get(i.name,n),o=i&&i.ordinalMeta;return o?o.categories[r]:r},styleEmphasis:function(n,i){0;null==i&&(i=s);var r=m(i,DP).getItemStyle(),o=_(i,DP),a=ph(o,null,null,!0,!0);a.text=o.getShallow(\"show\")?et(t.getFormattedLabel(i,DP),t.getFormattedLabel(i,AP),cw(e,i)):null;var l=dh(o,null,!0);return b(n,r),r=$k(r,a,l),n&&x(r,n),r.legacy=!0,r},visual:function(t,n){if(null==n&&(n=s),dt(TP,t)){var i=e.getItemVisual(n,\"style\");return i?i[TP[t]]:null}if(dt(CP,t))return e.getItemVisual(n,t)},barLayout:function(t){if(\"cartesian2d\"===o.type){return function(t){var e=[],n=t.axis,i=\"axis0\";if(\"category\"===n.type){for(var r=n.getBandWidth(),o=0;o<t.count;o++)e.push(T({bandWidth:r,axisKey:i,stackId:tx+o},t));var a=ax(e),s=[];for(o=0;o<t.count;o++){var l=a.axis0[tx+o];l.offsetCenter=l.offset+l.width/2,s.push(l)}return s}}(T({axis:o.getBaseAxis()},t))}},currentSeriesIndices:function(){return n.getCurrentSeriesIndices()},font:function(t){return mh(t,n)}},a.api||{}),h={context:{},seriesId:t.id,seriesName:t.name,seriesIndex:t.seriesIndex,coordSys:a.coordSys,dataInsideLength:e.count(),encode:sO(t.getData())},c={},p={},d={},f={},g=0;g<PP.length;g++){var y=PP[g];d[y]=t.getModel(OP[y]),f[y]=t.getModel(RP[y])}function v(t){return t===s?l||(l=e.getItemModel(t)):e.getItemModel(t)}function m(t,n){return e.hasItemOption?t===s?c[n]||(c[n]=v(t).getModel(OP[n])):v(t).getModel(OP[n]):d[n]}function _(t,n){return e.hasItemOption?t===s?p[n]||(p[n]=v(t).getModel(RP[n])):v(t).getModel(RP[n]):f[n]}return function(t,n){return s=t,l=null,c={},p={},r&&r(T({dataIndexInside:t,dataIndex:e.getRawIndex(t),actionType:n?n.type:null},h),u)};function x(t,e){for(var n in e)dt(e,n)&&(t[n]=e[n])}function b(t,e){t&&(t.textFill&&(e.textFill=t.textFill),t.textPosition&&(e.textPosition=t.textPosition))}}function sO(t){var e={};return P(t.dimensions,(function(n,i){var r=t.getDimensionInfo(n);if(!r.isExtraCoord){var o=r.coordDim;(e[o]=e[o]||[])[r.coordDimIndex]=i}})),e}function lO(t,e,n,i,r,o,a,s){if(i)return(e=uO(t,e,n,i,r,o,!0,s))&&a.setItemGraphicEl(n,e),e&&sl(e,i.focus,i.blurScope),e;xO(e,o)}function uO(t,e,n,i,r,o,a,s){var l=-1;e&&hO(e,i)&&(l=o.childrenRef().indexOf(e),e=null);var u=!e;e?e.clearStates():e=HP(i);var h=(MP(e).canMorph=i.morph&&_O(e))&&s&&s.hasFrom(),c=u&&!h;NP.normal.cfg=NP.normal.conOpt=NP.emphasis.cfg=NP.emphasis.conOpt=NP.blur.cfg=NP.blur.conOpt=NP.select.cfg=NP.select.conOpt=null,NP.isLegacy=!1,function(t,e,n,i,r,o){if(t.isGroup)return;cO(n,null,o),cO(n,DP,o);var a=o.normal.conOpt,s=o.emphasis.conOpt,l=o.blur.conOpt,u=o.select.conOpt;if(null!=a||null!=s||null!=u||null!=l){var h=t.getTextContent();if(!1===a)h&&t.removeTextContent();else{a=o.normal.conOpt=a||{type:\"text\"},h?h.clearStates():(h=HP(a),t.setTextContent(h));var c=a&&a.style;WP(null,h,null,e,a,c,null,i,r,!0);for(var p=0;p<PP.length;p++){var d=PP[p];if(d!==AP){var f=o[d].conOpt;eO(d,h,0,dO(a,f,d),null)}}c?h.dirty():h.markRedraw()}}}(e,n,i,r,c,NP),function(t,e,n,i,r){var o=n.clipPath;if(!1===o)t&&t.getClipPath()&&t.removeClipPath();else if(o){var a=t.getClipPath();a&&hO(a,o)&&(a=null),a||(a=HP(o),t.setClipPath(a)),WP(null,a,null,e,o,null,null,i,r,!1)}}(e,n,i,r,c);var p=WP(t,e,h,n,i,i.style,NP,r,c,!1);h&&s.addTo(e,i,n,p);for(var d=0;d<PP.length;d++){var f=PP[d];if(f!==AP){var g=pO(i,f);eO(f,e,0,dO(i,g,f),NP)}}return function(t,e,n,i){if(!t.isGroup){var r=t,o=n.currentZ,a=n.currentZLevel;r.z=o,r.zlevel=a;var s=e.z2;null!=s&&(r.z2=s||0);for(var l=0;l<PP.length;l++)nO(r,e,PP[l])}}(e,i,r),\"group\"===i.type&&function(t,e,n,i,r,o){var a=i.children,s=a?a.length:0,l=i.$mergeChildren,u=\"byName\"===l||i.diffChildrenByName,h=!1===l;if(!s&&!u&&!h)return;if(u)return c={api:t,oldChildren:e.children()||[],newChildren:a||[],dataIndex:n,seriesModel:r,group:e,morphPreparation:o},void new n_(c.oldChildren,c.newChildren,fO,fO,c).add(gO).update(gO).remove(yO).execute();var c;h&&e.removeAll();for(var p=0;p<s;p++)a[p]&&uO(t,e.childAt(p),n,a[p],r,e,!1,o);for(var d=e.childCount()-1;d>=p;d--)vO(e.childAt(d),r,e)}(t,e,n,i,r,s),l>=0?o.replaceAt(e,l):o.add(e),e}function hO(t,e){var n,i=MP(t),r=e.type,o=e.shape,a=e.style;return null!=r&&r!==i.customGraphicType||\"path\"===r&&((n=o)&&(dt(n,\"pathData\")||dt(n,\"d\")))&&mO(o)!==i.customPathData||\"image\"===r&&dt(a,\"image\")&&a.image!==i.customImagePath}function cO(t,e,n){var i=e?pO(t,e):t,r=e?dO(t,i,DP):t.style,o=t.type,a=i?i.textConfig:null,s=t.textContent,l=s?e?pO(s,e):s:null;if(r&&(n.isLegacy||jk(r,o,!!a,!!l))){n.isLegacy=!0;var u=qk(r,o,!e);!a&&u.textConfig&&(a=u.textConfig),!l&&u.textContent&&(l=u.textContent)}if(!e&&l){var h=l;!h.type&&(h.type=\"text\")}var c=e?n[e]:n.normal;c.cfg=a,c.conOpt=l}function pO(t,e){return e?t?t[e]:null:t}function dO(t,e,n){var i=e&&e.style;return null==i&&n===DP&&t&&(i=t.styleEmphasis),i}function fO(t,e){var n=t&&t.name;return null!=n?n:\"e\\0\\0\"+e}function gO(t,e){var n=this.context,i=null!=t?n.newChildren[t]:null,r=null!=e?n.oldChildren[e]:null;uO(n.api,r,n.dataIndex,i,n.seriesModel,n.group,0,n.morphPreparation)}function yO(t){var e=this.context;vO(e.oldChildren[t],e.seriesModel,e.group)}function vO(t,e,n){if(t){var i=MP(t).leaveToProps;i?Hu(t,i,e,{cb:function(){n.remove(t)}}):n.remove(t)}}function mO(t){return t&&(t.pathData||t.d)}function _O(t){return t&&t instanceof Ka}function xO(t,e){t&&e.remove(t)}var bO=function(){function t(t,e){this._fromList=[],this._toList=[],this._toElOptionList=[],this._allPropsFinalList=[],this._toDataIndices=[],this._morphConfigList=[],this._seriesModel=t,this._transOpt=e}return t.prototype.hasFrom=function(){return!!this._fromList.length},t.prototype.findAndAddFrom=function(t){if(t&&(MP(t).canMorph&&this._fromList.push(t),t.isGroup))for(var e=t.childrenRef(),n=0;n<e.length;n++)this.findAndAddFrom(e[n])},t.prototype.addTo=function(t,e,n,i){t&&(this._toList.push(t),this._toElOptionList.push(e),this._toDataIndices.push(n),this._allPropsFinalList.push(i))},t.prototype.applyMorphing=function(){var t=this._type,e=this._fromList,n=this._toList.length,i=e.length;if(i&&n)if(\"oneToOne\"===t)for(var r=0;r<n;r++)this._oneToOneForSingleTo(r,r);else if(\"manyToOne\"===t)for(var o=Math.max(1,Math.floor(i/n)),a=(r=0,0);r<n;r++,a+=o){var s=r+1>=n?i-a:o;this._manyToOneForSingleTo(r,a>=i?null:a,s)}else if(\"oneToMany\"===t)for(var l=Math.max(1,Math.floor(n/i)),u=0,h=0;u<n;u+=l,h++){var c=u+l>=n?n-u:l;this._oneToManyForSingleFrom(u,c,h>=i?null:h)}},t.prototype._oneToOneForSingleTo=function(t,e){var n,i=this._toList[t],r=this._toElOptionList[t],o=this._toDataIndices[t],a=this._allPropsFinalList[t],s=this._fromList[e],l=this._getOrCreateMorphConfig(o),u=l.duration;if(s&&gP(s)){if(UP(i,a,r.style),u){var h=yP([s],i,l,wO);this._processResultIndividuals(h,t,null)}}else{var c=u&&s&&(s!==i||(fP(n=s)||gP(n)))?s:null,p={};YP(\"shape\",i,c,r,p,!1),YP(\"extra\",i,c,r,p,!1),jP(i,c,r,p,!1),qP(i,c,r,r.style,p,!1),UP(i,a,r.style),c&&cP(c,i,l),XP(i,o,r,this._seriesModel,p,!1)}},t.prototype._manyToOneForSingleTo=function(t,e,n){var i=this._toList[t],r=this._toElOptionList[t];UP(i,this._allPropsFinalList[t],r.style);var o=this._getOrCreateMorphConfig(this._toDataIndices[t]);if(o.duration&&null!=e){for(var a=[],s=e;s<n;s++)a.push(this._fromList[s]);var l=yP(a,i,o,wO);this._processResultIndividuals(l,t,null)}},t.prototype._oneToManyForSingleFrom=function(t,e,n){for(var i=null==n?null:this._fromList[n],r=this._toList,o=[],a=t;a<e;a++){var s=r[a];UP(s,this._allPropsFinalList[a],this._toElOptionList[a].style),o.push(s)}var l=this._getOrCreateMorphConfig(this._toDataIndices[t]);if(l.duration&&i){var u=function(t,e,n,i){var r,o=e.length,a=n?n.dividingMethod:null,s=!1;if(gP(t)){var l=t.__combiningSubList;l.length===o?r=l:(r=wP(t,o,a),s=!0)}else r=wP(t,o,a),s=!0;rt(r.length===o);for(var u=0;u<o;u++)s&&i&&i(t,r[u],!1),cP(r[u],e[u],n);return{fromIndividuals:r,toIndividuals:e,count:o}}(i,o,l,wO);this._processResultIndividuals(u,t,e)}},t.prototype._processResultIndividuals=function(t,e,n){for(var i=null!=n,r=0;r<t.count;r++){var o=t.fromIndividuals[r],a=t.toIndividuals[r],s=e+(i?r:0),l=this._toElOptionList[s],u=this._toDataIndices[s],h={};jP(a,o,l,h,!1),qP(a,o,l,l.style,h,!1),XP(a,u,l,this._seriesModel,h,!1)}},t.prototype._getOrCreateMorphConfig=function(t){var e,n,i,r=this._morphConfigList,o=r[t];if(o)return o;var a=this._seriesModel,s=this._transOpt;if(a.isAnimationEnabled()){var l=void 0;if(a&&a.ecModel){var u=a.ecModel.getUpdatePayload();l=u&&u.animation}if(l)e=l.duration||0,n=l.easing||\"cubicOut\",i=l.delay||0;else{n=a.get(\"animationEasingUpdate\");var h=a.get(\"animationDelayUpdate\");i=G(h)?h(t):h;var c=a.get(\"animationDurationUpdate\");e=G(c)?c(t):c}}return o={duration:e||0,delay:i,easing:n,dividingMethod:s?s.dividingMethod:null},r[t]=o,o},t.prototype.reset=function(t){this._type=t,this._fromList.length=this._toList.length=this._toElOptionList.length=this._allPropsFinalList.length=this._toDataIndices.length=0},t}();function wO(t,e,n){e.style=n?w(t.style):t.style,e.zlevel=t.zlevel,e.z=t.z,e.z2=t.z2}var SO=kr(),MO=w,IO=V,TO=function(){function t(){this._dragging=!1,this.animationThreshold=15}return t.prototype.render=function(t,e,n,i){var r=e.get(\"value\"),o=e.get(\"status\");if(this._axisModel=t,this._axisPointerModel=e,this._api=n,i||this._lastValue!==r||this._lastStatus!==o){this._lastValue=r,this._lastStatus=o;var a=this._group,s=this._handle;if(!o||\"hide\"===o)return a&&a.hide(),void(s&&s.hide());a&&a.show(),s&&s.show();var l={};this.makeElOption(l,r,t,e,n);var u=l.graphicKey;u!==this._lastGraphicKey&&this.clear(n),this._lastGraphicKey=u;var h=this._moveAnimation=this.determineAnimation(t,e);if(a){var c=B(CO,e,h);this.updatePointerEl(a,l,c),this.updateLabelEl(a,l,c,e)}else a=this._group=new Ei,this.createPointerEl(a,l,t,e),this.createLabelEl(a,l,t,e),n.getZr().add(a);kO(a,e,!0),this._renderHandle(r)}},t.prototype.remove=function(t){this.clear(t)},t.prototype.dispose=function(t){this.clear(t)},t.prototype.determineAnimation=function(t,e){var n=e.get(\"animation\"),i=t.axis,r=\"category\"===i.type,o=e.get(\"snap\");if(!o&&!r)return!1;if(\"auto\"===n||null==n){var a=this.animationThreshold;if(r&&i.getBandWidth()>a)return!0;if(o){var s=uM(t).seriesDataCount,l=i.getExtent();return Math.abs(l[0]-l[1])/s>a}return!1}return!0===n},t.prototype.makeElOption=function(t,e,n,i,r){},t.prototype.createPointerEl=function(t,e,n,i){var r=e.pointer;if(r){var o=SO(t).pointerEl=new ah[r.type](MO(e.pointer));t.add(o)}},t.prototype.createLabelEl=function(t,e,n,i){if(e.label){var r=SO(t).labelEl=new cs(MO(e.label));t.add(r),AO(r,i)}},t.prototype.updatePointerEl=function(t,e,n){var i=SO(t).pointerEl;i&&e.pointer&&(i.setStyle(e.pointer.style),n(i,{shape:e.pointer.shape}))},t.prototype.updateLabelEl=function(t,e,n,i){var r=SO(t).labelEl;r&&(r.setStyle(e.label.style),n(r,{x:e.label.x,y:e.label.y}),AO(r,i))},t.prototype._renderHandle=function(t){if(!this._dragging&&this.updateHandleTransform){var e,n=this._axisPointerModel,i=this._api.getZr(),r=this._handle,o=n.getModel(\"handle\"),a=n.get(\"status\");if(!o.get(\"show\")||!a||\"hide\"===a)return r&&i.remove(r),void(this._handle=null);this._handle||(e=!0,r=this._handle=eh(o.get(\"icon\"),{cursor:\"move\",draggable:!0,onmousemove:function(t){ee(t.event)},onmousedown:IO(this._onHandleDragMove,this,0,0),drift:IO(this._onHandleDragMove,this),ondragend:IO(this._onHandleDragEnd,this)}),i.add(r)),kO(r,n,!1),r.setStyle(o.getItemStyle(null,[\"color\",\"borderColor\",\"borderWidth\",\"opacity\",\"shadowColor\",\"shadowBlur\",\"shadowOffsetX\",\"shadowOffsetY\"]));var s=o.get(\"size\");F(s)||(s=[s,s]),r.scaleX=s[0]/2,r.scaleY=s[1]/2,zf(this,\"_doDispatchAxisPointer\",o.get(\"throttle\")||0,\"fixRate\"),this._moveHandleToValue(t,e)}},t.prototype._moveHandleToValue=function(t,e){CO(this._axisPointerModel,!e&&this._moveAnimation,this._handle,LO(this.getHandleTransform(t,this._axisModel,this._axisPointerModel)))},t.prototype._onHandleDragMove=function(t,e){var n=this._handle;if(n){this._dragging=!0;var i=this.updateHandleTransform(LO(n),[t,e],this._axisModel,this._axisPointerModel);this._payloadInfo=i,n.stopAnimation(),n.attr(LO(i)),SO(n).lastProp=null,this._doDispatchAxisPointer()}},t.prototype._doDispatchAxisPointer=function(){if(this._handle){var t=this._payloadInfo,e=this._axisModel;this._api.dispatchAction({type:\"updateAxisPointer\",x:t.cursorPoint[0],y:t.cursorPoint[1],tooltipOption:t.tooltipOption,axesInfo:[{axisDim:e.axis.dim,axisIndex:e.componentIndex}]})}},t.prototype._onHandleDragEnd=function(){if(this._dragging=!1,this._handle){var t=this._axisPointerModel.get(\"value\");this._moveHandleToValue(t),this._api.dispatchAction({type:\"hideTip\"})}},t.prototype.clear=function(t){this._lastValue=null,this._lastStatus=null;var e=t.getZr(),n=this._group,i=this._handle;e&&n&&(this._lastGraphicKey=null,n&&e.remove(n),i&&e.remove(i),this._group=null,this._handle=null,this._payloadInfo=null)},t.prototype.doClear=function(){},t.prototype.buildLabel=function(t,e,n){return{x:t[n=n||0],y:t[1-n],width:e[n],height:e[1-n]}},t}();function CO(t,e,n,i){DO(SO(n).lastProp,i)||(SO(n).lastProp=i,e?Hu(n,i,t):(n.stopAnimation(),n.attr(i)))}function DO(t,e){if(X(t)&&X(e)){var n=!0;return P(e,(function(e,i){n=n&&DO(t[i],e)})),!!n}return t===e}function AO(t,e){t[e.get([\"label\",\"show\"])?\"show\":\"hide\"]()}function LO(t){return{x:t.x||0,y:t.y||0,rotation:t.rotation||0}}function kO(t,e,n){var i=e.get(\"z\"),r=e.get(\"zlevel\");t&&t.traverse((function(t){\"group\"!==t.type&&(null!=i&&(t.z=i),null!=r&&(t.zlevel=r),t.silent=n)}))}function PO(t){var e,n=t.get(\"type\"),i=t.getModel(n+\"Style\");return\"line\"===n?(e=i.getLineStyle()).fill=null:\"shadow\"===n&&((e=i.getAreaStyle()).stroke=null),e}function OO(t,e,n,i,r){var o=RO(n.get(\"value\"),e.axis,e.ecModel,n.get(\"seriesDataIndices\"),{precision:n.get([\"label\",\"precision\"]),formatter:n.get([\"label\",\"formatter\"])}),a=n.getModel(\"label\"),s=wc(a.get(\"padding\")||0),l=a.getFont(),u=bi(o,l),h=r.position,c=u.width+s[1]+s[3],p=u.height+s[0]+s[2],d=r.align;\"right\"===d&&(h[0]-=c),\"center\"===d&&(h[0]-=c/2);var f=r.verticalAlign;\"bottom\"===f&&(h[1]-=p),\"middle\"===f&&(h[1]-=p/2),function(t,e,n,i){var r=i.getWidth(),o=i.getHeight();t[0]=Math.min(t[0]+e,r)-e,t[1]=Math.min(t[1]+n,o)-n,t[0]=Math.max(t[0],0),t[1]=Math.max(t[1],0)}(h,c,p,i);var g=a.get(\"backgroundColor\");g&&\"auto\"!==g||(g=e.get([\"axisLine\",\"lineStyle\",\"color\"])),t.label={x:h[0],y:h[1],style:ph(a,{text:o,font:l,fill:a.getTextColor(),padding:s,backgroundColor:g}),z2:10}}function RO(t,e,n,i,r){t=e.scale.parse(t);var o=e.scale.getLabel({value:t},{precision:r.precision}),a=r.formatter;if(a){var s={value:Gx(e,{value:t}),axisDimension:e.dim,axisIndex:e.index,seriesData:[]};P(i,(function(t){var e=n.getSeriesByIndex(t.seriesIndex),i=t.dataIndexInside,r=e&&e.getDataParams(i);r&&s.seriesData.push(r)})),H(a)?o=a.replace(\"{value}\",o):G(a)&&(o=a(s))}return o}function NO(t,e,n){var i=[1,0,0,1,0,0];return Xn(i,i,n.rotation),Un(i,i,n.position),qu([t.dataToCoord(e),(n.labelOffset||0)+(n.labelDirection||1)*(n.labelMargin||0)],i)}function zO(t,e,n,i,r,o){var a=tM.innerTextLayout(n.rotation,0,n.labelDirection);n.labelMargin=r.get([\"label\",\"margin\"]),OO(e,i,r,o,{position:NO(i.axis,t,n),align:a.textAlign,verticalAlign:a.textVerticalAlign})}function EO(t,e,n){return{x1:t[n=n||0],y1:t[1-n],x2:e[n],y2:e[1-n]}}function VO(t,e,n){return{x:t[n=n||0],y:t[1-n],width:e[n],height:e[1-n]}}function BO(t,e,n,i,r,o){return{cx:t,cy:e,r0:n,r:i,startAngle:r,endAngle:o,clockwise:!0}}var FO=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis,a=o.grid,s=i.get(\"type\"),l=GO(a,o).getOtherAxis(o).getGlobalExtent(),u=o.toGlobalCoord(o.dataToCoord(e,!0));if(s&&\"none\"!==s){var h=PO(i),c=HO[s](o,u,l);c.style=h,t.graphicKey=c.type,t.pointer=c}zO(e,t,YS(a.model,n),n,i,r)},e.prototype.getHandleTransform=function(t,e,n){var i=YS(e.axis.grid.model,e,{labelInside:!1});i.labelMargin=n.get([\"handle\",\"margin\"]);var r=NO(e.axis,t,i);return{x:r[0],y:r[1],rotation:i.rotation+(i.labelDirection<0?Math.PI:0)}},e.prototype.updateHandleTransform=function(t,e,n,i){var r=n.axis,o=r.grid,a=r.getGlobalExtent(!0),s=GO(o,r).getOtherAxis(r).getGlobalExtent(),l=\"x\"===r.dim?0:1,u=[t.x,t.y];u[l]+=e[l],u[l]=Math.min(a[1],u[l]),u[l]=Math.max(a[0],u[l]);var h=(s[1]+s[0])/2,c=[h,h];c[l]=u[l];return{x:u[0],y:u[1],rotation:t.rotation,cursorPoint:c,tooltipOption:[{verticalAlign:\"middle\"},{align:\"center\"}][l]}},e}(TO);function GO(t,e){var n={};return n[e.dim+\"AxisIndex\"]=e.index,t.getCartesian(n)}var HO={line:function(t,e,n){return{type:\"Line\",subPixelOptimize:!0,shape:EO([e,n[0]],[e,n[1]],WO(t))}},shadow:function(t,e,n){var i=Math.max(1,t.getBandWidth()),r=n[1]-n[0];return{type:\"Rect\",shape:VO([e-i/2,n[0]],[i,r],WO(t))}}};function WO(t){return\"x\"===t.dim?0:1}var UO=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"axisPointer\",e.defaultOption={show:\"auto\",zlevel:0,z:50,type:\"line\",snap:!1,triggerTooltip:!0,value:null,status:null,link:[],animation:null,animationDurationUpdate:200,lineStyle:{color:\"#B9BEC9\",width:1,type:\"dashed\"},shadowStyle:{color:\"rgba(210,219,238,0.2)\"},label:{show:!0,formatter:null,precision:\"auto\",margin:3,color:\"#fff\",padding:[5,7,5,7],backgroundColor:\"auto\",borderColor:null,borderWidth:0,borderRadius:3},handle:{show:!1,icon:\"M10.7,11.9v-1.3H9.3v1.3c-4.9,0.3-8.8,4.4-8.8,9.4c0,5,3.9,9.1,8.8,9.4h1.3c4.9-0.3,8.8-4.4,8.8-9.4C19.5,16.3,15.6,12.2,10.7,11.9z M13.3,24.4H6.7v-1.2h6.6z M13.3,22H6.7v-1.2h6.6z M13.3,19.6H6.7v-1.2h6.6z\",size:45,margin:50,color:\"#333\",shadowBlur:3,shadowColor:\"#aaa\",shadowOffsetX:0,shadowOffsetY:2,throttle:40}},e}(Xc),XO=kr(),YO=P;function ZO(t,e,n){if(!a.node){var i=e.getZr();XO(i).records||(XO(i).records={}),function(t,e){if(XO(t).initialized)return;function n(n,i){t.on(n,(function(n){var r=function(t){var e={showTip:[],hideTip:[]},n=function(i){var r=e[i.type];r?r.push(i):(i.dispatchAction=n,t.dispatchAction(i))};return{dispatchAction:n,pendings:e}}(e);YO(XO(t).records,(function(t){t&&i(t,n,r.dispatchAction)})),function(t,e){var n,i=t.showTip.length,r=t.hideTip.length;i?n=t.showTip[i-1]:r&&(n=t.hideTip[r-1]);n&&(n.dispatchAction=null,e.dispatchAction(n))}(r.pendings,e)}))}XO(t).initialized=!0,n(\"click\",B(qO,\"click\")),n(\"mousemove\",B(qO,\"mousemove\")),n(\"globalout\",jO)}(i,e),(XO(i).records[t]||(XO(i).records[t]={})).handler=n}}function jO(t,e,n){t.handler(\"leave\",null,n)}function qO(t,e,n,i){e.handler(t,n,i)}function KO(t,e){if(!a.node){var n=e.getZr();(XO(n).records||{})[t]&&(XO(n).records[t]=null)}}var $O=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=e.getComponent(\"tooltip\"),r=t.get(\"triggerOn\")||i&&i.get(\"triggerOn\")||\"mousemove|click\";ZO(\"axisPointer\",n,(function(t,e,n){\"none\"!==r&&(\"leave\"===t||r.indexOf(t)>=0)&&n({type:\"updateAxisPointer\",currTrigger:t,x:e&&e.offsetX,y:e&&e.offsetY})}))},e.prototype.remove=function(t,e){KO(\"axisPointer\",e)},e.prototype.dispose=function(t,e){KO(\"axisPointer\",e)},e.type=\"axisPointer\",e}(wf);function JO(t,e){var n,i=[],r=t.seriesIndex;if(null==r||!(n=e.getSeriesByIndex(r)))return{point:[]};var o=n.getData(),a=Lr(o,t);if(null==a||a<0||F(a))return{point:[]};var s=o.getItemGraphicEl(a),l=n.coordinateSystem;if(n.getTooltipPosition)i=n.getTooltipPosition(a)||[];else if(l&&l.dataToPoint)if(t.isStacked){var u=l.getBaseAxis(),h=l.getOtherAxis(u).dim,c=u.dim,p=\"x\"===h||\"radius\"===h?1:0,d=o.mapDimension(c),f=[];f[p]=o.get(d,a),f[1-p]=o.get(o.getCalculationInfo(\"stackResultDimension\"),a),i=l.dataToPoint(f)||[]}else i=l.dataToPoint(o.getValues(O(l.dimensions,(function(t){return o.mapDimension(t)})),a))||[];else if(s){var g=s.getBoundingRect().clone();g.applyTransform(s.transform),i=[g.x+g.width/2,g.y+g.height/2]}return{point:i,el:s}}var QO=kr();function tR(t,e,n){var i=t.currTrigger,r=[t.x,t.y],o=t,a=t.dispatchAction||V(n.dispatchAction,n),s=e.getComponent(\"axisPointer\").coordSysAxesInfo;if(s){oR(r)&&(r=JO({seriesIndex:o.seriesIndex,dataIndex:o.dataIndex},e).point);var l=oR(r),u=o.axesInfo,h=s.axesInfo,c=\"leave\"===i||oR(r),p={},d={},f={list:[],map:{}},g={showPointer:B(nR,d),showTooltip:B(iR,f)};P(s.coordSysMap,(function(t,e){var n=l||t.containPoint(r);P(s.coordSysAxesInfo[e],(function(t,e){var i=t.axis,o=function(t,e){for(var n=0;n<(t||[]).length;n++){var i=t[n];if(e.axis.dim===i.axisDim&&e.axis.model.componentIndex===i.axisIndex)return i}}(u,t);if(!c&&n&&(!u||o)){var a=o&&o.value;null!=a||l||(a=i.pointToData(r)),null!=a&&eR(t,a,g,!1,p)}}))}));var y={};return P(h,(function(t,e){var n=t.linkGroup;n&&!d[e]&&P(n.axesInfo,(function(e,i){var r=d[i];if(e!==t&&r){var o=r.value;n.mapper&&(o=t.axis.scale.parse(n.mapper(o,rR(e),rR(t)))),y[t.key]=o}}))})),P(y,(function(t,e){eR(h[e],t,g,!0,p)})),function(t,e,n){var i=n.axesInfo=[];P(e,(function(e,n){var r=e.axisPointerModel.option,o=t[n];o?(!e.useHandle&&(r.status=\"show\"),r.value=o.value,r.seriesDataIndices=(o.payloadBatch||[]).slice()):!e.useHandle&&(r.status=\"hide\"),\"show\"===r.status&&i.push({axisDim:e.axis.dim,axisIndex:e.axis.model.componentIndex,value:r.value})}))}(d,h,p),function(t,e,n,i){if(oR(e)||!t.list.length)return void i({type:\"hideTip\"});var r=((t.list[0].dataByAxis[0]||{}).seriesDataIndices||[])[0]||{};i({type:\"showTip\",escapeConnect:!0,x:e[0],y:e[1],tooltipOption:n.tooltipOption,position:n.position,dataIndexInside:r.dataIndexInside,dataIndex:r.dataIndex,seriesIndex:r.seriesIndex,dataByCoordSys:t.list})}(f,r,t,a),function(t,e,n){var i=n.getZr(),r=\"axisPointerLastHighlights\",o=QO(i)[r]||{},a=QO(i)[r]={};P(t,(function(t,e){var n=t.axisPointerModel.option;\"show\"===n.status&&P(n.seriesDataIndices,(function(t){var e=t.seriesIndex+\" | \"+t.dataIndex;a[e]=t}))}));var s=[],l=[];P(o,(function(t,e){!a[e]&&l.push(t)})),P(a,(function(t,e){!o[e]&&s.push(t)})),l.length&&n.dispatchAction({type:\"downplay\",escapeConnect:!0,notBlur:!0,batch:l}),s.length&&n.dispatchAction({type:\"highlight\",escapeConnect:!0,notBlur:!0,batch:s})}(h,0,n),p}}function eR(t,e,n,i,r){var o=t.axis;if(!o.scale.isBlank()&&o.containData(e))if(t.involveSeries){var a=function(t,e){var n=e.axis,i=n.dim,r=t,o=[],a=Number.MAX_VALUE,s=-1;return P(e.seriesModels,(function(e,l){var u,h,c=e.getData().mapDimensionsAll(i);if(e.getAxisTooltipData){var p=e.getAxisTooltipData(c,t,n);h=p.dataIndices,u=p.nestestValue}else{if(!(h=e.getData().indicesOfNearest(c[0],t,\"category\"===n.type?.5:null)).length)return;u=e.getData().get(c[0],h[0])}if(null!=u&&isFinite(u)){var d=t-u,f=Math.abs(d);f<=a&&((f<a||d>=0&&s<0)&&(a=f,s=d,r=u,o.length=0),P(h,(function(t){o.push({seriesIndex:e.seriesIndex,dataIndexInside:t,dataIndex:e.getData().getRawIndex(t)})})))}})),{payloadBatch:o,snapToValue:r}}(e,t),s=a.payloadBatch,l=a.snapToValue;s[0]&&null==r.seriesIndex&&I(r,s[0]),!i&&t.snap&&o.containData(l)&&null!=l&&(e=l),n.showPointer(t,e,s),n.showTooltip(t,a,l)}else n.showPointer(t,e)}function nR(t,e,n,i){t[e.key]={value:n,payloadBatch:i}}function iR(t,e,n,i){var r=n.payloadBatch,o=e.axis,a=o.model,s=e.axisPointerModel;if(e.triggerTooltip&&r.length){var l=e.coordSys.model,u=cM(l),h=t.map[u];h||(h=t.map[u]={coordSysId:l.id,coordSysIndex:l.componentIndex,coordSysType:l.type,coordSysMainType:l.mainType,dataByAxis:[]},t.list.push(h)),h.dataByAxis.push({axisDim:o.dim,axisIndex:a.componentIndex,axisType:a.type,axisId:a.id,value:i,valueLabelOpt:{precision:s.get([\"label\",\"precision\"]),formatter:s.get([\"label\",\"formatter\"])},seriesDataIndices:r.slice()})}}function rR(t){var e=t.axis.model,n={},i=n.axisDim=t.axis.dim;return n.axisIndex=n[i+\"AxisIndex\"]=e.componentIndex,n.axisName=n[i+\"AxisName\"]=e.name,n.axisId=n[i+\"AxisId\"]=e.id,n}function oR(t){return!t||null==t[0]||isNaN(t[0])||null==t[1]||isNaN(t[1])}function aR(t){dM.registerAxisPointerClass(\"CartesianAxisPointer\",FO),t.registerComponentModel(UO),t.registerComponentView($O),t.registerPreprocessor((function(t){if(t){(!t.axisPointer||0===t.axisPointer.length)&&(t.axisPointer={});var e=t.axisPointer.link;e&&!F(e)&&(t.axisPointer.link=[e])}})),t.registerProcessor(t.PRIORITY.PROCESSOR.STATISTIC,(function(t,e){t.getComponent(\"axisPointer\").coordSysAxesInfo=aM(t,e)})),t.registerAction({type:\"updateAxisPointer\",event:\"updateAxisPointer\",update:\":updateAxisPointer\"},tR)}var sR=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis;\"angle\"===o.dim&&(this.animationThreshold=Math.PI/18);var a=o.polar,s=a.getOtherAxis(o).getExtent(),l=o.dataToCoord(e),u=i.get(\"type\");if(u&&\"none\"!==u){var h=PO(i),c=lR[u](o,a,l,s);c.style=h,t.graphicKey=c.type,t.pointer=c}var p=function(t,e,n,i,r){var o=e.axis,a=o.dataToCoord(t),s=i.getAngleAxis().getExtent()[0];s=s/180*Math.PI;var l,u,h,c=i.getRadiusAxis().getExtent();if(\"radius\"===o.dim){var p=[1,0,0,1,0,0];Xn(p,p,s),Un(p,p,[i.cx,i.cy]),l=qu([a,-r],p);var d=e.getModel(\"axisLabel\").get(\"rotate\")||0,f=tM.innerTextLayout(s,d*Math.PI/180,-1);u=f.textAlign,h=f.textVerticalAlign}else{var g=c[1];l=i.coordToPoint([g+r,a]);var y=i.cx,v=i.cy;u=Math.abs(l[0]-y)/g<.3?\"center\":l[0]>y?\"left\":\"right\",h=Math.abs(l[1]-v)/g<.3?\"middle\":l[1]>v?\"top\":\"bottom\"}return{position:l,align:u,verticalAlign:h}}(e,n,0,a,i.get([\"label\",\"margin\"]));OO(t,n,i,r,p)},e}(TO);var lR={line:function(t,e,n,i){return\"angle\"===t.dim?{type:\"Line\",shape:EO(e.coordToPoint([i[0],n]),e.coordToPoint([i[1],n]))}:{type:\"Circle\",shape:{cx:e.cx,cy:e.cy,r:n}}},shadow:function(t,e,n,i){var r=Math.max(1,t.getBandWidth()),o=Math.PI/180;return\"angle\"===t.dim?{type:\"Sector\",shape:BO(e.cx,e.cy,i[0],i[1],(-n-r/2)*o,(r/2-n)*o)}:{type:\"Sector\",shape:BO(e.cx,e.cy,n-r/2,n+r/2,0,2*Math.PI)}}},uR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.findAxisModel=function(t){var e;return this.ecModel.eachComponent(t,(function(t){t.getCoordSysModel()===this&&(e=t)}),this),e},e.type=\"polar\",e.dependencies=[\"radiusAxis\",\"angleAxis\"],e.defaultOption={zlevel:0,z:0,center:[\"50%\",\"50%\"],radius:\"80%\"},e}(Xc),hR=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getCoordSysModel=function(){return this.getReferringComponents(\"polar\",Nr).models[0]},e.type=\"polarAxis\",e}(Xc);L(hR,Yx);var cR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"angleAxis\",e}(hR),pR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"radiusAxis\",e}(hR),dR=function(t){function e(e,n){return t.call(this,\"radius\",e,n)||this}return n(e,t),e.prototype.pointToData=function(t,e){return this.polar.pointToData(t,e)[\"radius\"===this.dim?0:1]},e}(hb);dR.prototype.dataToRadius=hb.prototype.dataToCoord,dR.prototype.radiusToData=hb.prototype.coordToData;var fR=kr(),gR=function(t){function e(e,n){return t.call(this,\"angle\",e,n||[0,360])||this}return n(e,t),e.prototype.pointToData=function(t,e){return this.polar.pointToData(t,e)[\"radius\"===this.dim?0:1]},e.prototype.calculateCategoryInterval=function(){var t=this,e=t.getLabelModel(),n=t.scale,i=n.getExtent(),r=n.count();if(i[1]-i[0]<1)return 0;var o=i[0],a=t.dataToCoord(o+1)-t.dataToCoord(o),s=Math.abs(a),l=bi(null==o?\"\":o+\"\",e.getFont(),\"center\",\"top\"),u=Math.max(l.height,7)/s;isNaN(u)&&(u=1/0);var h=Math.max(0,Math.floor(u)),c=fR(t.model),p=c.lastAutoInterval,d=c.lastTickCount;return null!=p&&null!=d&&Math.abs(p-h)<=1&&Math.abs(d-r)<=1&&p>h?h=p:(c.lastTickCount=r,c.lastAutoInterval=h),h},e}(hb);gR.prototype.dataToAngle=hb.prototype.dataToCoord,gR.prototype.angleToData=hb.prototype.coordToData;var yR=function(){function t(t){this.dimensions=[\"radius\",\"angle\"],this.type=\"polar\",this.cx=0,this.cy=0,this._radiusAxis=new dR,this._angleAxis=new gR,this.axisPointerEnabled=!0,this.name=t||\"\",this._radiusAxis.polar=this._angleAxis.polar=this}return t.prototype.containPoint=function(t){var e=this.pointToCoord(t);return this._radiusAxis.contain(e[0])&&this._angleAxis.contain(e[1])},t.prototype.containData=function(t){return this._radiusAxis.containData(t[0])&&this._angleAxis.containData(t[1])},t.prototype.getAxis=function(t){return this[\"_\"+t+\"Axis\"]},t.prototype.getAxes=function(){return[this._radiusAxis,this._angleAxis]},t.prototype.getAxesByScale=function(t){var e=[],n=this._angleAxis,i=this._radiusAxis;return n.scale.type===t&&e.push(n),i.scale.type===t&&e.push(i),e},t.prototype.getAngleAxis=function(){return this._angleAxis},t.prototype.getRadiusAxis=function(){return this._radiusAxis},t.prototype.getOtherAxis=function(t){var e=this._angleAxis;return t===e?this._radiusAxis:e},t.prototype.getBaseAxis=function(){return this.getAxesByScale(\"ordinal\")[0]||this.getAxesByScale(\"time\")[0]||this.getAngleAxis()},t.prototype.getTooltipAxes=function(t){var e=null!=t&&\"auto\"!==t?this.getAxis(t):this.getBaseAxis();return{baseAxes:[e],otherAxes:[this.getOtherAxis(e)]}},t.prototype.dataToPoint=function(t,e){return this.coordToPoint([this._radiusAxis.dataToRadius(t[0],e),this._angleAxis.dataToAngle(t[1],e)])},t.prototype.pointToData=function(t,e){var n=this.pointToCoord(t);return[this._radiusAxis.radiusToData(n[0],e),this._angleAxis.angleToData(n[1],e)]},t.prototype.pointToCoord=function(t){var e=t[0]-this.cx,n=t[1]-this.cy,i=this.getAngleAxis(),r=i.getExtent(),o=Math.min(r[0],r[1]),a=Math.max(r[0],r[1]);i.inverse?o=a-360:a=o+360;var s=Math.sqrt(e*e+n*n);e/=s,n/=s;for(var l=Math.atan2(-n,e)/Math.PI*180,u=l<o?1:-1;l<o||l>a;)l+=360*u;return[s,l]},t.prototype.coordToPoint=function(t){var e=t[0],n=t[1]/180*Math.PI;return[Math.cos(n)*e+this.cx,-Math.sin(n)*e+this.cy]},t.prototype.getArea=function(){var t=this.getAngleAxis(),e=this.getRadiusAxis().getExtent().slice();e[0]>e[1]&&e.reverse();var n=t.getExtent(),i=Math.PI/180;return{cx:this.cx,cy:this.cy,r0:e[0],r:e[1],startAngle:-n[0]*i,endAngle:-n[1]*i,clockwise:t.inverse,contain:function(t,e){var n=t-this.cx,i=e-this.cy,r=n*n+i*i,o=this.r,a=this.r0;return r<=o*o&&r>=a*a}}},t.prototype.convertToPixel=function(t,e,n){return vR(e)===this?this.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){return vR(e)===this?this.pointToData(n):null},t}();function vR(t){var e=t.seriesModel,n=t.polarModel;return n&&n.coordinateSystem||e&&e.coordinateSystem}function mR(t,e){var n=this,i=n.getAngleAxis(),r=n.getRadiusAxis();if(i.scale.setExtent(1/0,-1/0),r.scale.setExtent(1/0,-1/0),t.eachSeries((function(t){if(t.coordinateSystem===n){var e=t.getData();P(Xx(e,\"radius\"),(function(t){r.scale.unionExtentFromData(e,t)})),P(Xx(e,\"angle\"),(function(t){i.scale.unionExtentFromData(e,t)}))}})),Vx(i.scale,i.model),Vx(r.scale,r.model),\"category\"===i.type&&!i.onBand){var o=i.getExtent(),a=360/i.scale.count();i.inverse?o[1]+=a:o[1]-=a,i.setExtent(o[0],o[1])}}function _R(t,e){if(t.type=e.get(\"type\"),t.scale=Bx(e),t.onBand=e.get(\"boundaryGap\")&&\"category\"===t.type,t.inverse=e.get(\"inverse\"),function(t){return\"angleAxis\"===t.mainType}(e)){t.inverse=t.inverse!==e.get(\"clockwise\");var n=e.get(\"startAngle\");t.setExtent(n,n+(t.inverse?-360:360))}e.axis=t,t.model=e}var xR={dimensions:yR.prototype.dimensions,create:function(t,e){var n=[];return t.eachComponent(\"polar\",(function(t,i){var r=new yR(i+\"\");r.update=mR;var o=r.getRadiusAxis(),a=r.getAngleAxis(),s=t.findAxisModel(\"radiusAxis\"),l=t.findAxisModel(\"angleAxis\");_R(o,s),_R(a,l),function(t,e,n){var i=e.get(\"center\"),r=n.getWidth(),o=n.getHeight();t.cx=Zi(i[0],r),t.cy=Zi(i[1],o);var a=t.getRadiusAxis(),s=Math.min(r,o)/2,l=e.get(\"radius\");null==l?l=[0,\"100%\"]:F(l)||(l=[0,l]);var u=[Zi(l[0],s),Zi(l[1],s)];a.inverse?a.setExtent(u[1],u[0]):a.setExtent(u[0],u[1])}(r,t,e),n.push(r),t.coordinateSystem=r,r.model=t})),t.eachSeries((function(t){if(\"polar\"===t.get(\"coordinateSystem\")){var e=t.getReferringComponents(\"polar\",Nr).models[0];0,t.coordinateSystem=e.coordinateSystem}})),n}},bR=[\"axisLine\",\"axisLabel\",\"axisTick\",\"minorTick\",\"splitLine\",\"minorSplitLine\",\"splitArea\"];function wR(t,e,n){e[1]>e[0]&&(e=e.slice().reverse());var i=t.coordToPoint([e[0],n]),r=t.coordToPoint([e[1],n]);return{x1:i[0],y1:i[1],x2:r[0],y2:r[1]}}function SR(t){return t.getRadiusAxis().inverse?0:1}function MR(t){var e=t[0],n=t[t.length-1];e&&n&&Math.abs(Math.abs(e.coord-n.coord)-360)<1e-4&&t.pop()}var IR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass=\"PolarAxisPointer\",n}return n(e,t),e.prototype.render=function(t,e){if(this.group.removeAll(),t.get(\"show\")){var n=t.axis,i=n.polar,r=i.getRadiusAxis().getExtent(),o=n.getTicksCoords(),a=n.getMinorTicksCoords(),s=O(n.getViewLabels(),(function(t){t=w(t);var e=n.scale,i=\"ordinal\"===e.type?e.getRawOrdinalNumber(t.tickValue):t.tickValue;return t.coord=n.dataToCoord(i),t}));MR(s),MR(o),P(bR,(function(e){!t.get([e,\"show\"])||n.scale.isBlank()&&\"axisLine\"!==e||TR[e](this.group,t,i,o,a,r,s)}),this)}},e.type=\"angleAxis\",e}(dM),TR={axisLine:function(t,e,n,i,r,o){var a,s=e.getModel([\"axisLine\",\"lineStyle\"]),l=SR(n),u=l?0:1;(a=0===o[u]?new Nl({shape:{cx:n.cx,cy:n.cy,r:o[l]},style:s.getLineStyle(),z2:1,silent:!0}):new tu({shape:{cx:n.cx,cy:n.cy,r:o[l],r0:o[u]},style:s.getLineStyle(),z2:1,silent:!0})).style.fill=null,t.add(a)},axisTick:function(t,e,n,i,r,o){var a=e.getModel(\"axisTick\"),s=(a.get(\"inside\")?-1:1)*a.get(\"length\"),l=o[SR(n)],u=O(i,(function(t){return new uu({shape:wR(n,[l,l+s],t.coord)})}));t.add(Vu(u,{style:T(a.getModel(\"lineStyle\").getLineStyle(),{stroke:e.get([\"axisLine\",\"lineStyle\",\"color\"])})}))},minorTick:function(t,e,n,i,r,o){if(r.length){for(var a=e.getModel(\"axisTick\"),s=e.getModel(\"minorTick\"),l=(a.get(\"inside\")?-1:1)*s.get(\"length\"),u=o[SR(n)],h=[],c=0;c<r.length;c++)for(var p=0;p<r[c].length;p++)h.push(new uu({shape:wR(n,[u,u+l],r[c][p].coord)}));t.add(Vu(h,{style:T(s.getModel(\"lineStyle\").getLineStyle(),T(a.getLineStyle(),{stroke:e.get([\"axisLine\",\"lineStyle\",\"color\"])}))}))}},axisLabel:function(t,e,n,i,r,o,a){var s=e.getCategories(!0),l=e.getModel(\"axisLabel\"),u=l.get(\"margin\"),h=e.get(\"triggerEvent\");P(a,(function(i,r){var a=l,c=i.tickValue,p=o[SR(n)],d=n.coordToPoint([p+u,i.coord]),f=n.cx,g=n.cy,y=Math.abs(d[0]-f)/p<.3?\"center\":d[0]>f?\"left\":\"right\",v=Math.abs(d[1]-g)/p<.3?\"middle\":d[1]>g?\"top\":\"bottom\";if(s&&s[c]){var m=s[c];X(m)&&m.textStyle&&(a=new Oh(m.textStyle,l,l.ecModel))}var _=new cs({silent:tM.isLabelSilent(e),style:ph(a,{x:d[0],y:d[1],fill:a.getTextColor()||e.get([\"axisLine\",\"lineStyle\",\"color\"]),text:i.formattedLabel,align:y,verticalAlign:v})});if(t.add(_),h){var x=tM.makeAxisEventDataBase(e);x.targetType=\"axisLabel\",x.value=i.rawLabel,_s(_).eventData=x}}),this)},splitLine:function(t,e,n,i,r,o){var a=e.getModel(\"splitLine\").getModel(\"lineStyle\"),s=a.get(\"color\"),l=0;s=s instanceof Array?s:[s];for(var u=[],h=0;h<i.length;h++){var c=l++%s.length;u[c]=u[c]||[],u[c].push(new uu({shape:wR(n,o,i[h].coord)}))}for(h=0;h<u.length;h++)t.add(Vu(u[h],{style:T({stroke:s[h%s.length]},a.getLineStyle()),silent:!0,z:e.get(\"z\")}))},minorSplitLine:function(t,e,n,i,r,o){if(r.length){for(var a=e.getModel(\"minorSplitLine\").getModel(\"lineStyle\"),s=[],l=0;l<r.length;l++)for(var u=0;u<r[l].length;u++)s.push(new uu({shape:wR(n,o,r[l][u].coord)}));t.add(Vu(s,{style:a.getLineStyle(),silent:!0,z:e.get(\"z\")}))}},splitArea:function(t,e,n,i,r,o){if(i.length){var a=e.getModel(\"splitArea\").getModel(\"areaStyle\"),s=a.get(\"color\"),l=0;s=s instanceof Array?s:[s];for(var u=[],h=Math.PI/180,c=-i[0].coord*h,p=Math.min(o[0],o[1]),d=Math.max(o[0],o[1]),f=e.get(\"clockwise\"),g=1,y=i.length;g<=y;g++){var v=g===y?i[0].coord:i[g].coord,m=l++%s.length;u[m]=u[m]||[],u[m].push(new Jl({shape:{cx:n.cx,cy:n.cy,r0:p,r:d,startAngle:c,endAngle:-v*h,clockwise:f},silent:!0})),c=-v*h}for(g=0;g<u.length;g++)t.add(Vu(u[g],{style:T({fill:s[g%s.length]},a.getAreaStyle()),silent:!0}))}}},CR=[\"axisLine\",\"axisTickLabel\",\"axisName\"],DR=[\"splitLine\",\"splitArea\",\"minorSplitLine\"],AR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass=\"PolarAxisPointer\",n}return n(e,t),e.prototype.render=function(t,e){if(this.group.removeAll(),t.get(\"show\")){var n=this._axisGroup,i=this._axisGroup=new Ei;this.group.add(i);var r=t.axis,o=r.polar,a=o.getAngleAxis(),s=r.getTicksCoords(),l=r.getMinorTicksCoords(),u=a.getExtent()[0],h=r.getExtent(),c=function(t,e,n){return{position:[t.cx,t.cy],rotation:n/180*Math.PI,labelDirection:-1,tickDirection:-1,nameDirection:1,labelRotate:e.getModel(\"axisLabel\").get(\"rotate\"),z2:1}}(o,t,u),p=new tM(t,c);P(CR,p.add,p),i.add(p.getGroup()),Ju(n,i,t),P(DR,(function(e){t.get([e,\"show\"])&&!r.scale.isBlank()&&LR[e](this.group,t,o,u,h,s,l)}),this)}},e.type=\"radiusAxis\",e}(dM),LR={splitLine:function(t,e,n,i,r,o){var a=e.getModel(\"splitLine\").getModel(\"lineStyle\"),s=a.get(\"color\"),l=0;s=s instanceof Array?s:[s];for(var u=[],h=0;h<o.length;h++){var c=l++%s.length;u[c]=u[c]||[],u[c].push(new Nl({shape:{cx:n.cx,cy:n.cy,r:o[h].coord}}))}for(h=0;h<u.length;h++)t.add(Vu(u[h],{style:T({stroke:s[h%s.length],fill:null},a.getLineStyle()),silent:!0}))},minorSplitLine:function(t,e,n,i,r,o,a){if(a.length){for(var s=e.getModel(\"minorSplitLine\").getModel(\"lineStyle\"),l=[],u=0;u<a.length;u++)for(var h=0;h<a[u].length;h++)l.push(new Nl({shape:{cx:n.cx,cy:n.cy,r:a[u][h].coord}}));t.add(Vu(l,{style:T({fill:null},s.getLineStyle()),silent:!0}))}},splitArea:function(t,e,n,i,r,o){if(o.length){var a=e.getModel(\"splitArea\").getModel(\"areaStyle\"),s=a.get(\"color\"),l=0;s=s instanceof Array?s:[s];for(var u=[],h=o[0].coord,c=1;c<o.length;c++){var p=l++%s.length;u[p]=u[p]||[],u[p].push(new Jl({shape:{cx:n.cx,cy:n.cy,r0:h,r:o[c].coord,startAngle:0,endAngle:2*Math.PI},silent:!0})),h=o[c].coord}for(c=0;c<u.length;c++)t.add(Vu(u[c],{style:T({fill:s[c%s.length]},a.getAreaStyle()),silent:!0}))}}};function kR(t){return t.get(\"stack\")||\"__ec_stack_\"+t.seriesIndex}function PR(t,e){return e.dim+t.model.componentIndex}function OR(t,e,n){var i={},r=function(t){var e={};P(t,(function(t,n){var i=t.getData(),r=t.coordinateSystem,o=r.getBaseAxis(),a=PR(r,o),s=o.getExtent(),l=\"category\"===o.type?o.getBandWidth():Math.abs(s[1]-s[0])/i.count(),u=e[a]||{bandWidth:l,remainedWidth:l,autoWidthCount:0,categoryGap:\"20%\",gap:\"30%\",stacks:{}},h=u.stacks;e[a]=u;var c=kR(t);h[c]||u.autoWidthCount++,h[c]=h[c]||{width:0,maxWidth:0};var p=Zi(t.get(\"barWidth\"),l),d=Zi(t.get(\"barMaxWidth\"),l),f=t.get(\"barGap\"),g=t.get(\"barCategoryGap\");p&&!h[c].width&&(p=Math.min(u.remainedWidth,p),h[c].width=p,u.remainedWidth-=p),d&&(h[c].maxWidth=d),null!=f&&(u.gap=f),null!=g&&(u.categoryGap=g)}));var n={};return P(e,(function(t,e){n[e]={};var i=t.stacks,r=t.bandWidth,o=Zi(t.categoryGap,r),a=Zi(t.gap,1),s=t.remainedWidth,l=t.autoWidthCount,u=(s-o)/(l+(l-1)*a);u=Math.max(u,0),P(i,(function(t,e){var n=t.maxWidth;n&&n<u&&(n=Math.min(n,s),t.width&&(n=Math.min(n,t.width)),s-=n,t.width=n,l--)})),u=(s-o)/(l+(l-1)*a),u=Math.max(u,0);var h,c=0;P(i,(function(t,e){t.width||(t.width=u),h=t,c+=t.width*(1+a)})),h&&(c-=h.width*a);var p=-c/2;P(i,(function(t,i){n[e][i]=n[e][i]||{offset:p,width:t.width},p+=t.width*(1+a)}))})),n}(N(e.getSeriesByType(t),(function(t){return!e.isSeriesFiltered(t)&&t.coordinateSystem&&\"polar\"===t.coordinateSystem.type})));e.eachSeriesByType(t,(function(t){if(\"polar\"===t.coordinateSystem.type){var e=t.getData(),n=t.coordinateSystem,o=n.getBaseAxis(),a=PR(n,o),s=kR(t),l=r[a][s],u=l.offset,h=l.width,c=n.getOtherAxis(o),p=t.coordinateSystem.cx,d=t.coordinateSystem.cy,f=t.get(\"barMinHeight\")||0,g=t.get(\"barMinAngle\")||0;i[s]=i[s]||[];for(var y=e.mapDimension(c.dim),v=e.mapDimension(o.dim),m=V_(e,y),_=\"radius\"!==o.dim||!t.get(\"roundCap\",!0),x=c.dataToCoord(0),b=0,w=e.count();b<w;b++){var S=e.get(y,b),M=e.get(v,b),I=S>=0?\"p\":\"n\",T=x;m&&(i[s][M]||(i[s][M]={p:x,n:x}),T=i[s][M][I]);var C=void 0,D=void 0,A=void 0,L=void 0;if(\"radius\"===c.dim){var k=c.dataToCoord(S)-x,P=o.dataToCoord(M);Math.abs(k)<f&&(k=(k<0?-1:1)*f),C=T,D=T+k,L=(A=P-u)-h,m&&(i[s][M][I]=D)}else{var O=c.dataToCoord(S,_)-x,R=o.dataToCoord(M);Math.abs(O)<g&&(O=(O<0?-1:1)*g),D=(C=R+u)+h,A=T,L=T+O,m&&(i[s][M][I]=L)}e.setItemLayout(b,{cx:p,cy:d,r0:C,r:D,startAngle:-A*Math.PI/180,endAngle:-L*Math.PI/180})}}}))}var RR={startAngle:90,clockwise:!0,splitNumber:12,axisLabel:{rotate:0}},NR={splitNumber:5},zR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"polar\",e}(wf);function ER(t,e){e=e||{};var n=t.coordinateSystem,i=t.axis,r={},o=i.position,a=i.orient,s=n.getRect(),l=[s.x,s.x+s.width,s.y,s.y+s.height],u={horizontal:{top:l[2],bottom:l[3]},vertical:{left:l[0],right:l[1]}};r.position=[\"vertical\"===a?u.vertical[o]:l[0],\"horizontal\"===a?u.horizontal[o]:l[3]];r.rotation=Math.PI/2*{horizontal:0,vertical:1}[a];r.labelDirection=r.tickDirection=r.nameDirection={top:-1,bottom:1,right:1,left:-1}[o],t.get([\"axisTick\",\"inside\"])&&(r.tickDirection=-r.tickDirection),Q(e.labelInside,t.get([\"axisLabel\",\"inside\"]))&&(r.labelDirection=-r.labelDirection);var h=e.rotate;return null==h&&(h=t.get([\"axisLabel\",\"rotate\"])),r.labelRotation=\"top\"===o?-h:h,r.z2=1,r}var VR=[\"axisLine\",\"axisTickLabel\",\"axisName\"],BR=[\"splitArea\",\"splitLine\"],FR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.axisPointerClass=\"SingleAxisPointer\",n}return n(e,t),e.prototype.render=function(e,n,i,r){var o=this.group;o.removeAll();var a=this._axisGroup;this._axisGroup=new Ei;var s=ER(e),l=new tM(e,s);P(VR,l.add,l),o.add(this._axisGroup),o.add(l.getGroup()),P(BR,(function(t){e.get([t,\"show\"])&&GR[t](this,this.group,this._axisGroup,e)}),this),Ju(a,this._axisGroup,e),t.prototype.render.call(this,e,n,i,r)},e.prototype.remove=function(){yM(this)},e.type=\"singleAxis\",e}(dM),GR={splitLine:function(t,e,n,i){var r=i.axis;if(!r.scale.isBlank()){var o=i.getModel(\"splitLine\"),a=o.getModel(\"lineStyle\"),s=a.get(\"color\");s=s instanceof Array?s:[s];for(var l=i.coordinateSystem.getRect(),u=r.isHorizontal(),h=[],c=0,p=r.getTicksCoords({tickModel:o}),d=[],f=[],g=0;g<p.length;++g){var y=r.toGlobalCoord(p[g].coord);u?(d[0]=y,d[1]=l.y,f[0]=y,f[1]=l.y+l.height):(d[0]=l.x,d[1]=y,f[0]=l.x+l.width,f[1]=y);var v=c++%s.length;h[v]=h[v]||[],h[v].push(new uu({subPixelOptimize:!0,shape:{x1:d[0],y1:d[1],x2:f[0],y2:f[1]},silent:!0}))}var m=a.getLineStyle([\"color\"]);for(g=0;g<h.length;++g)e.add(Vu(h[g],{style:T({stroke:s[g%s.length]},m),silent:!0}))}},splitArea:function(t,e,n,i){gM(t,n,i,i)}},HR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.getCoordSysModel=function(){return this},e.type=\"singleAxis\",e.layoutMode=\"box\",e.defaultOption={left:\"5%\",top:\"5%\",right:\"5%\",bottom:\"5%\",type:\"value\",position:\"bottom\",orient:\"horizontal\",axisLine:{show:!0,lineStyle:{width:1,type:\"solid\"}},tooltip:{show:!0},axisTick:{show:!0,length:6,lineStyle:{width:1}},axisLabel:{show:!0,interval:\"auto\"},splitLine:{show:!0,lineStyle:{type:\"dashed\",opacity:.2}}},e}(Xc);L(HR,Yx.prototype);var WR=function(t){function e(e,n,i,r,o){var a=t.call(this,e,n,i)||this;return a.type=r||\"value\",a.position=o||\"bottom\",a}return n(e,t),e.prototype.isHorizontal=function(){var t=this.position;return\"top\"===t||\"bottom\"===t},e.prototype.pointToData=function(t,e){return this.coordinateSystem.pointToData(t)[0]},e}(hb),UR=function(){function t(t,e,n){this.type=\"single\",this.dimension=\"single\",this.dimensions=[\"single\"],this.axisPointerEnabled=!0,this.model=t,this._init(t,e,n)}return t.prototype._init=function(t,e,n){var i=this.dimension,r=new WR(i,Bx(t),[0,0],t.get(\"type\"),t.get(\"position\")),o=\"category\"===r.type;r.onBand=o&&t.get(\"boundaryGap\"),r.inverse=t.get(\"inverse\"),r.orient=t.get(\"orient\"),t.axis=r,r.model=t,r.coordinateSystem=this,this._axis=r},t.prototype.update=function(t,e){t.eachSeries((function(t){if(t.coordinateSystem===this){var e=t.getData();P(e.mapDimensionsAll(this.dimension),(function(t){this._axis.scale.unionExtentFromData(e,t)}),this),Vx(this._axis.scale,this._axis.model)}}),this)},t.prototype.resize=function(t,e){this._rect=Vc({left:t.get(\"left\"),top:t.get(\"top\"),right:t.get(\"right\"),bottom:t.get(\"bottom\"),width:t.get(\"width\"),height:t.get(\"height\")},{width:e.getWidth(),height:e.getHeight()}),this._adjustAxis()},t.prototype.getRect=function(){return this._rect},t.prototype._adjustAxis=function(){var t=this._rect,e=this._axis,n=e.isHorizontal(),i=n?[0,t.width]:[0,t.height],r=e.reverse?1:0;e.setExtent(i[r],i[1-r]),this._updateAxisTransform(e,n?t.x:t.y)},t.prototype._updateAxisTransform=function(t,e){var n=t.getExtent(),i=n[0]+n[1],r=t.isHorizontal();t.toGlobalCoord=r?function(t){return t+e}:function(t){return i-t+e},t.toLocalCoord=r?function(t){return t-e}:function(t){return i-t+e}},t.prototype.getAxis=function(){return this._axis},t.prototype.getBaseAxis=function(){return this._axis},t.prototype.getAxes=function(){return[this._axis]},t.prototype.getTooltipAxes=function(){return{baseAxes:[this.getAxis()],otherAxes:[]}},t.prototype.containPoint=function(t){var e=this.getRect(),n=this.getAxis();return\"horizontal\"===n.orient?n.contain(n.toLocalCoord(t[0]))&&t[1]>=e.y&&t[1]<=e.y+e.height:n.contain(n.toLocalCoord(t[1]))&&t[0]>=e.y&&t[0]<=e.y+e.height},t.prototype.pointToData=function(t){var e=this.getAxis();return[e.coordToData(e.toLocalCoord(t[\"horizontal\"===e.orient?0:1]))]},t.prototype.dataToPoint=function(t){var e=this.getAxis(),n=this.getRect(),i=[],r=\"horizontal\"===e.orient?0:1;return t instanceof Array&&(t=t[0]),i[r]=e.toGlobalCoord(e.dataToCoord(+t)),i[1-r]=0===r?n.y+n.height/2:n.x+n.width/2,i},t.prototype.convertToPixel=function(t,e,n){return XR(e)===this?this.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){return XR(e)===this?this.pointToData(n):null},t}();function XR(t){var e=t.seriesModel,n=t.singleAxisModel;return n&&n.coordinateSystem||e&&e.coordinateSystem}var YR={create:function(t,e){var n=[];return t.eachComponent(\"singleAxis\",(function(i,r){var o=new UR(i,t,e);o.name=\"single_\"+r,o.resize(i,e),i.coordinateSystem=o,n.push(o)})),t.eachSeries((function(t){if(\"singleAxis\"===t.get(\"coordinateSystem\")){var e=t.getReferringComponents(\"singleAxis\",Nr).models[0];t.coordinateSystem=e&&e.coordinateSystem}})),n},dimensions:UR.prototype.dimensions},ZR=[\"x\",\"y\"],jR=[\"width\",\"height\"],qR=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.makeElOption=function(t,e,n,i,r){var o=n.axis,a=o.coordinateSystem,s=JR(a,1-$R(o)),l=a.dataToPoint(e)[0],u=i.get(\"type\");if(u&&\"none\"!==u){var h=PO(i),c=KR[u](o,l,s);c.style=h,t.graphicKey=c.type,t.pointer=c}zO(e,t,ER(n),n,i,r)},e.prototype.getHandleTransform=function(t,e,n){var i=ER(e,{labelInside:!1});i.labelMargin=n.get([\"handle\",\"margin\"]);var r=NO(e.axis,t,i);return{x:r[0],y:r[1],rotation:i.rotation+(i.labelDirection<0?Math.PI:0)}},e.prototype.updateHandleTransform=function(t,e,n,i){var r=n.axis,o=r.coordinateSystem,a=$R(r),s=JR(o,a),l=[t.x,t.y];l[a]+=e[a],l[a]=Math.min(s[1],l[a]),l[a]=Math.max(s[0],l[a]);var u=JR(o,1-a),h=(u[1]+u[0])/2,c=[h,h];return c[a]=l[a],{x:l[0],y:l[1],rotation:t.rotation,cursorPoint:c,tooltipOption:{verticalAlign:\"middle\"}}},e}(TO),KR={line:function(t,e,n){return{type:\"Line\",subPixelOptimize:!0,shape:EO([e,n[0]],[e,n[1]],$R(t))}},shadow:function(t,e,n){var i=t.getBandWidth(),r=n[1]-n[0];return{type:\"Rect\",shape:VO([e-i/2,n[0]],[i,r],$R(t))}}};function $R(t){return t.isHorizontal()?0:1}function JR(t,e){var n=t.getRect();return[n[ZR[e]],n[ZR[e]]+n[jR[e]]]}var QR=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"single\",e}(wf);var tN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(e,n,i){var r=Hc(e);t.prototype.init.apply(this,arguments),eN(e,r)},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),eN(this.option,e)},e.prototype.getCellSize=function(){return this.option.cellSize},e.type=\"calendar\",e.defaultOption={zlevel:0,z:2,left:80,top:60,cellSize:20,orient:\"horizontal\",splitLine:{show:!0,lineStyle:{color:\"#000\",width:1,type:\"solid\"}},itemStyle:{color:\"#fff\",borderWidth:1,borderColor:\"#ccc\"},dayLabel:{show:!0,firstDay:0,position:\"start\",margin:\"50%\",nameMap:\"en\",color:\"#000\"},monthLabel:{show:!0,position:\"start\",margin:5,align:\"center\",nameMap:\"en\",formatter:null,color:\"#000\"},yearLabel:{show:!0,position:null,margin:30,formatter:null,color:\"#ccc\",fontFamily:\"sans-serif\",fontWeight:\"bolder\",fontSize:20}},e}(Xc);function eN(t,e){var n,i=t.cellSize;1===(n=F(i)?i:t.cellSize=[i,i]).length&&(n[1]=n[0]);var r=O([0,1],(function(t){return function(t,e){return null!=t[Nc[e][0]]||null!=t[Nc[e][1]]&&null!=t[Nc[e][2]]}(e,t)&&(n[t]=\"auto\"),null!=n[t]&&\"auto\"!==n[t]}));Gc(t,e,{type:\"box\",ignoreSize:r})}var nN={EN:[\"Jan\",\"Feb\",\"Mar\",\"Apr\",\"May\",\"Jun\",\"Jul\",\"Aug\",\"Sep\",\"Oct\",\"Nov\",\"Dec\"],CN:[\"一月\",\"二月\",\"三月\",\"四月\",\"五月\",\"六月\",\"七月\",\"八月\",\"九月\",\"十月\",\"十一月\",\"十二月\"]},iN={EN:[\"S\",\"M\",\"T\",\"W\",\"T\",\"F\",\"S\"],CN:[\"日\",\"一\",\"二\",\"三\",\"四\",\"五\",\"六\"]},rN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){var i=this.group;i.removeAll();var r=t.coordinateSystem,o=r.getRangeInfo(),a=r.getOrient();this._renderDayRect(t,o,i),this._renderLines(t,o,a,i),this._renderYearText(t,o,a,i),this._renderMonthText(t,a,i),this._renderWeekText(t,o,a,i)},e.prototype._renderDayRect=function(t,e,n){for(var i=t.coordinateSystem,r=t.getModel(\"itemStyle\").getItemStyle(),o=i.getCellWidth(),a=i.getCellHeight(),s=e.start.time;s<=e.end.time;s=i.getNextNDay(s,1).time){var l=i.dataToRect([s],!1).tl,u=new ls({shape:{x:l[0],y:l[1],width:o,height:a},cursor:\"default\",style:r});n.add(u)}},e.prototype._renderLines=function(t,e,n,i){var r=this,o=t.coordinateSystem,a=t.getModel([\"splitLine\",\"lineStyle\"]).getLineStyle(),s=t.get([\"splitLine\",\"show\"]),l=a.lineWidth;this._tlpoints=[],this._blpoints=[],this._firstDayOfMonth=[],this._firstDayPoints=[];for(var u=e.start,h=0;u.time<=e.end.time;h++){p(u.formatedDate),0===h&&(u=o.getDateInfo(e.start.y+\"-\"+e.start.m));var c=u.date;c.setMonth(c.getMonth()+1),u=o.getDateInfo(c)}function p(e){r._firstDayOfMonth.push(o.getDateInfo(e)),r._firstDayPoints.push(o.dataToRect([e],!1).tl);var l=r._getLinePointsOfOneWeek(t,e,n);r._tlpoints.push(l[0]),r._blpoints.push(l[l.length-1]),s&&r._drawSplitline(l,a,i)}p(o.getNextNDay(e.end.time,1).formatedDate),s&&this._drawSplitline(r._getEdgesPoints(r._tlpoints,l,n),a,i),s&&this._drawSplitline(r._getEdgesPoints(r._blpoints,l,n),a,i)},e.prototype._getEdgesPoints=function(t,e,n){var i=[t[0].slice(),t[t.length-1].slice()],r=\"horizontal\"===n?0:1;return i[0][r]=i[0][r]-e/2,i[1][r]=i[1][r]+e/2,i},e.prototype._drawSplitline=function(t,e,n){var i=new au({z2:20,shape:{points:t},style:e});n.add(i)},e.prototype._getLinePointsOfOneWeek=function(t,e,n){for(var i=t.coordinateSystem,r=i.getDateInfo(e),o=[],a=0;a<7;a++){var s=i.getNextNDay(r.time,a),l=i.dataToRect([s.time],!1);o[2*s.day]=l.tl,o[2*s.day+1]=l[\"horizontal\"===n?\"bl\":\"tr\"]}return o},e.prototype._formatterLabel=function(t,e){return\"string\"==typeof t&&t?(n=t,P(e,(function(t,e){n=n.replace(\"{\"+e+\"}\",i?Ic(t):t)})),n):\"function\"==typeof t?t(e):e.nameMap;var n,i},e.prototype._yearTextPositionControl=function(t,e,n,i,r){var o=e[0],a=e[1],s=[\"center\",\"bottom\"];\"bottom\"===i?(a+=r,s=[\"center\",\"top\"]):\"left\"===i?o-=r:\"right\"===i?(o+=r,s=[\"center\",\"top\"]):a-=r;var l=0;return\"left\"!==i&&\"right\"!==i||(l=Math.PI/2),{rotation:l,x:o,y:a,style:{align:s[0],verticalAlign:s[1]}}},e.prototype._renderYearText=function(t,e,n,i){var r=t.getModel(\"yearLabel\");if(r.get(\"show\")){var o=r.get(\"margin\"),a=r.get(\"position\");a||(a=\"horizontal\"!==n?\"top\":\"left\");var s=[this._tlpoints[this._tlpoints.length-1],this._blpoints[0]],l=(s[0][0]+s[1][0])/2,u=(s[0][1]+s[1][1])/2,h=\"horizontal\"===n?0:1,c={top:[l,s[h][1]],bottom:[l,s[1-h][1]],left:[s[1-h][0],u],right:[s[h][0],u]},p=e.start.y;+e.end.y>+e.start.y&&(p=p+\"-\"+e.end.y);var d=r.get(\"formatter\"),f={start:e.start.y,end:e.end.y,nameMap:p},g=this._formatterLabel(d,f),y=new cs({z2:30,style:ph(r,{text:g})});y.attr(this._yearTextPositionControl(y,c[a],n,a,o)),i.add(y)}},e.prototype._monthTextPositionControl=function(t,e,n,i,r){var o=\"left\",a=\"top\",s=t[0],l=t[1];return\"horizontal\"===n?(l+=r,e&&(o=\"center\"),\"start\"===i&&(a=\"bottom\")):(s+=r,e&&(a=\"middle\"),\"start\"===i&&(o=\"right\")),{x:s,y:l,align:o,verticalAlign:a}},e.prototype._renderMonthText=function(t,e,n){var i=t.getModel(\"monthLabel\");if(i.get(\"show\")){var r=i.get(\"nameMap\"),o=i.get(\"margin\"),a=i.get(\"position\"),s=i.get(\"align\"),l=[this._tlpoints,this._blpoints];H(r)&&(r=nN[r.toUpperCase()]||[]);var u=\"start\"===a?0:1,h=\"horizontal\"===e?0:1;o=\"start\"===a?-o:o;for(var c=\"center\"===s,p=0;p<l[u].length-1;p++){var d=l[u][p].slice(),f=this._firstDayOfMonth[p];if(c){var g=this._firstDayPoints[p];d[h]=(g[h]+l[0][p+1][h])/2}var y=i.get(\"formatter\"),v=r[+f.m-1],m={yyyy:f.y,yy:(f.y+\"\").slice(2),MM:f.m,M:+f.m,nameMap:v},_=this._formatterLabel(y,m),x=new cs({z2:30,style:I(ph(i,{text:_}),this._monthTextPositionControl(d,c,e,a,o))});n.add(x)}}},e.prototype._weekTextPositionControl=function(t,e,n,i,r){var o=\"center\",a=\"middle\",s=t[0],l=t[1],u=\"start\"===n;return\"horizontal\"===e?(s=s+i+(u?1:-1)*r[0]/2,o=u?\"right\":\"left\"):(l=l+i+(u?1:-1)*r[1]/2,a=u?\"bottom\":\"top\"),{x:s,y:l,align:o,verticalAlign:a}},e.prototype._renderWeekText=function(t,e,n,i){var r=t.getModel(\"dayLabel\");if(r.get(\"show\")){var o=t.coordinateSystem,a=r.get(\"position\"),s=r.get(\"nameMap\"),l=r.get(\"margin\"),u=o.getFirstDayOfWeek();H(s)&&(s=iN[s.toUpperCase()]||[]);var h=o.getNextNDay(e.end.time,7-e.lweek).time,c=[o.getCellWidth(),o.getCellHeight()];l=Zi(l,Math.min(c[1],c[0])),\"start\"===a&&(h=o.getNextNDay(e.start.time,-(7+e.fweek)).time,l=-l);for(var p=0;p<7;p++){var d,f=o.getNextNDay(h,p),g=o.dataToRect([f.time],!1).center;d=Math.abs((p+u)%7);var y=new cs({z2:30,style:I(ph(r,{text:s[d]}),this._weekTextPositionControl(g,n,a,l,c))});i.add(y)}}},e.type=\"calendar\",e}(wf),oN=864e5,aN=function(){function t(e,n,i){this.type=\"calendar\",this.dimensions=t.dimensions,this.getDimensionsInfo=t.getDimensionsInfo,this._model=e}return t.getDimensionsInfo=function(){return[{name:\"time\",type:\"time\"},\"value\"]},t.prototype.getRangeInfo=function(){return this._rangeInfo},t.prototype.getModel=function(){return this._model},t.prototype.getRect=function(){return this._rect},t.prototype.getCellWidth=function(){return this._sw},t.prototype.getCellHeight=function(){return this._sh},t.prototype.getOrient=function(){return this._orient},t.prototype.getFirstDayOfWeek=function(){return this._firstDayOfWeek},t.prototype.getDateInfo=function(t){var e=(t=or(t)).getFullYear(),n=t.getMonth()+1,i=n<10?\"0\"+n:\"\"+n,r=t.getDate(),o=r<10?\"0\"+r:\"\"+r,a=t.getDay();return{y:e+\"\",m:i,d:o,day:a=Math.abs((a+7-this.getFirstDayOfWeek())%7),time:t.getTime(),formatedDate:e+\"-\"+i+\"-\"+o,date:t}},t.prototype.getNextNDay=function(t,e){return 0===(e=e||0)||(t=new Date(this.getDateInfo(t).time)).setDate(t.getDate()+e),this.getDateInfo(t)},t.prototype.update=function(t,e){this._firstDayOfWeek=+this._model.getModel(\"dayLabel\").get(\"firstDay\"),this._orient=this._model.get(\"orient\"),this._lineWidth=this._model.getModel(\"itemStyle\").getItemStyle().lineWidth||0,this._rangeInfo=this._getRangeInfo(this._initRangeOption());var n=this._rangeInfo.weeks||1,i=[\"width\",\"height\"],r=this._model.getCellSize().slice(),o=this._model.getBoxLayoutParams(),a=\"horizontal\"===this._orient?[n,7]:[7,n];P([0,1],(function(t){u(r,t)&&(o[i[t]]=r[t]*a[t])}));var s={width:e.getWidth(),height:e.getHeight()},l=this._rect=Vc(o,s);function u(t,e){return null!=t[e]&&\"auto\"!==t[e]}P([0,1],(function(t){u(r,t)||(r[t]=l[i[t]]/a[t])})),this._sw=r[0],this._sh=r[1]},t.prototype.dataToPoint=function(t,e){F(t)&&(t=t[0]),null==e&&(e=!0);var n=this.getDateInfo(t),i=this._rangeInfo,r=n.formatedDate;if(e&&!(n.time>=i.start.time&&n.time<i.end.time+oN))return[NaN,NaN];var o=n.day,a=this._getRangeInfo([i.start.time,r]).nthWeek;return\"vertical\"===this._orient?[this._rect.x+o*this._sw+this._sw/2,this._rect.y+a*this._sh+this._sh/2]:[this._rect.x+a*this._sw+this._sw/2,this._rect.y+o*this._sh+this._sh/2]},t.prototype.pointToData=function(t){var e=this.pointToDate(t);return e&&e.time},t.prototype.dataToRect=function(t,e){var n=this.dataToPoint(t,e);return{contentShape:{x:n[0]-(this._sw-this._lineWidth)/2,y:n[1]-(this._sh-this._lineWidth)/2,width:this._sw-this._lineWidth,height:this._sh-this._lineWidth},center:n,tl:[n[0]-this._sw/2,n[1]-this._sh/2],tr:[n[0]+this._sw/2,n[1]-this._sh/2],br:[n[0]+this._sw/2,n[1]+this._sh/2],bl:[n[0]-this._sw/2,n[1]+this._sh/2]}},t.prototype.pointToDate=function(t){var e=Math.floor((t[0]-this._rect.x)/this._sw)+1,n=Math.floor((t[1]-this._rect.y)/this._sh)+1,i=this._rangeInfo.range;return\"vertical\"===this._orient?this._getDateByWeeksAndDay(n,e-1,i):this._getDateByWeeksAndDay(e,n-1,i)},t.prototype.convertToPixel=function(t,e,n){var i=sN(e);return i===this?i.dataToPoint(n):null},t.prototype.convertFromPixel=function(t,e,n){var i=sN(e);return i===this?i.pointToData(n):null},t.prototype.containPoint=function(t){return console.warn(\"Not implemented.\"),!1},t.prototype._initRangeOption=function(){var t,e=this._model.get(\"range\");if(F(e)&&1===e.length&&(e=e[0]),F(e))t=e;else{var n=e.toString();if(/^\\d{4}$/.test(n)&&(t=[n+\"-01-01\",n+\"-12-31\"]),/^\\d{4}[\\/|-]\\d{1,2}$/.test(n)){var i=this.getDateInfo(n),r=i.date;r.setMonth(r.getMonth()+1);var o=this.getNextNDay(r,-1);t=[i.formatedDate,o.formatedDate]}/^\\d{4}[\\/|-]\\d{1,2}[\\/|-]\\d{1,2}$/.test(n)&&(t=[n,n])}if(!t)return e;var a=this._getRangeInfo(t);return a.start.time>a.end.time&&t.reverse(),t},t.prototype._getRangeInfo=function(t){var e,n=[this.getDateInfo(t[0]),this.getDateInfo(t[1])];n[0].time>n[1].time&&(e=!0,n.reverse());var i=Math.floor(n[1].time/oN)-Math.floor(n[0].time/oN)+1,r=new Date(n[0].time),o=r.getDate(),a=n[1].date.getDate();r.setDate(o+i-1);var s=r.getDate();if(s!==a)for(var l=r.getTime()-n[1].time>0?1:-1;(s=r.getDate())!==a&&(r.getTime()-n[1].time)*l>0;)i-=l,r.setDate(s-l);var u=Math.floor((i+n[0].day+6)/7),h=e?1-u:u-1;return e&&n.reverse(),{range:[n[0].formatedDate,n[1].formatedDate],start:n[0],end:n[1],allDay:i,weeks:u,nthWeek:h,fweek:n[0].day,lweek:n[1].day}},t.prototype._getDateByWeeksAndDay=function(t,e,n){var i=this._getRangeInfo(n);if(t>i.weeks||0===t&&e<i.fweek||t===i.weeks&&e>i.lweek)return null;var r=7*(t-1)-i.fweek+e,o=new Date(i.start.time);return o.setDate(+i.start.d+r),this.getDateInfo(o)},t.create=function(e,n){var i=[];return e.eachComponent(\"calendar\",(function(r){var o=new t(r,e,n);i.push(o),r.coordinateSystem=o})),e.eachSeries((function(t){\"calendar\"===t.get(\"coordinateSystem\")&&(t.coordinateSystem=i[t.get(\"calendarIndex\")||0])})),i},t.dimensions=[\"time\",\"value\"],t}();function sN(t){var e=t.calendarModel,n=t.seriesModel;return e?e.coordinateSystem:n?n.coordinateSystem:null}var lN=kr(),uN={path:null,compoundPath:null,group:Ei,image:es,text:cs},hN=function(t){var e=t.graphic;F(e)?e[0]&&e[0].elements?t.graphic=[t.graphic[0]]:t.graphic=[{elements:e}]:e&&!e.elements&&(t.graphic=[{elements:[e]}])},cN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.preventAutoZ=!0,n}return n(e,t),e.prototype.mergeOption=function(e,n){var i=this.option.elements;this.option.elements=null,t.prototype.mergeOption.call(this,e,n),this.option.elements=i},e.prototype.optionUpdated=function(t,e){var n=this.option,i=(e?n:t).elements,r=n.elements=e?[]:n.elements,o=[];this._flatten(i,o,null);var a=Mr(r,o,\"normalMerge\"),s=this._elOptionsToUpdate=[];P(a,(function(t,e){var n=t.newOption;n&&(s.push(n),function(t,e){var n=t.existing;if(e.id=t.keyInfo.id,!e.type&&n&&(e.type=n.type),null==e.parentId){var i=e.parentOption;i?e.parentId=i.id:n&&(e.parentId=n.parentId)}e.parentOption=null}(t,n),function(t,e,n){var i=I({},n),r=t[e],o=n.$action||\"merge\";if(\"merge\"===o){if(r)S(r,i,!0),Gc(r,i,{ignoreSize:!0}),Wc(n,r);else t[e]=i}else\"replace\"===o?t[e]=i:\"remove\"===o&&r&&(t[e]=null)}(r,e,n),function(t,e){if(!t)return;if(t.hv=e.hv=[gN(e,[\"left\",\"right\"]),gN(e,[\"top\",\"bottom\"])],\"group\"===t.type){var n=t,i=e;null==n.width&&(n.width=i.width=0),null==n.height&&(n.height=i.height=0)}}(r[e],n))}),this);for(var l=r.length-1;l>=0;l--)null==r[l]?r.splice(l,1):delete r[l].$action},e.prototype._flatten=function(t,e,n){P(t,(function(t){if(t){n&&(t.parentOption=n),e.push(t);var i=t.children;\"group\"===t.type&&i&&this._flatten(i,e,t),delete t.children}}),this)},e.prototype.useElOptionsToUpdate=function(){var t=this._elOptionsToUpdate;return this._elOptionsToUpdate=null,t},e.type=\"graphic\",e.defaultOption={elements:[]},e}(Xc),pN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){this._elMap=ht()},e.prototype.render=function(t,e,n){t!==this._lastGraphicModel&&this._clear(),this._lastGraphicModel=t,this._updateElements(t),this._relocate(t,n)},e.prototype._updateElements=function(t){var e=t.useElOptionsToUpdate();if(e){var n=this._elMap,i=this.group;P(e,(function(e){var r=Cr(e.id,null),o=null!=r?n.get(r):null,a=Cr(e.parentId,null),s=null!=a?n.get(a):i,l=e.type,u=e.style;\"text\"===l&&u&&e.hv&&e.hv[1]&&(u.textVerticalAlign=u.textBaseline=u.verticalAlign=u.align=null);var h=e.textContent,c=e.textConfig;if(u&&jk(u,l,!!c,!!h)){var p=qk(u,l,!0);!c&&p.textConfig&&(c=e.textConfig=p.textConfig),!h&&p.textContent&&(h=p.textContent)}var d=function(t){return t=I({},t),P([\"id\",\"parentId\",\"$action\",\"hv\",\"bounding\",\"textContent\"].concat(Rc),(function(e){delete t[e]})),t}(e);var f=e.$action||\"merge\";\"merge\"===f?o?o.attr(d):dN(r,s,d,n):\"replace\"===f?(fN(o,n),dN(r,s,d,n)):\"remove\"===f&&fN(o,n);var g=n.get(r);if(g&&h)if(\"merge\"===f){var y=g.getTextContent();y?y.attr(h):g.setTextContent(new cs(h))}else\"replace\"===f&&g.setTextContent(new cs(h));if(g){var v=lN(g);v.__ecGraphicWidthOption=e.width,v.__ecGraphicHeightOption=e.height,function(t,e,n){var i=_s(t).eventData;t.silent||t.ignore||i||(i=_s(t).eventData={componentType:\"graphic\",componentIndex:e.componentIndex,name:t.name});i&&(i.info=n.info)}(g,t,e),oh({el:g,componentModel:t,itemName:g.name,itemTooltipOption:e.tooltip})}}))}},e.prototype._relocate=function(t,e){for(var n=t.option.elements,i=this.group,r=this._elMap,o=e.getWidth(),a=e.getHeight(),s=0;s<n.length;s++){if((d=null!=(p=Cr((c=n[s]).id,null))?r.get(p):null)&&d.isGroup){var l=(f=d.parent)===i,u=lN(d),h=lN(f);u.__ecGraphicWidth=Zi(u.__ecGraphicWidthOption,l?o:h.__ecGraphicWidth)||0,u.__ecGraphicHeight=Zi(u.__ecGraphicHeightOption,l?a:h.__ecGraphicHeight)||0}}for(s=n.length-1;s>=0;s--){var c,p,d;if(d=null!=(p=Cr((c=n[s]).id,null))?r.get(p):null){var f=d.parent;h=lN(f);Bc(d,c,f===i?{width:o,height:a}:{width:h.__ecGraphicWidth,height:h.__ecGraphicHeight},null,{hv:c.hv,boundingMode:c.bounding})}}},e.prototype._clear=function(){var t=this._elMap;t.each((function(e){fN(e,t)})),this._elMap=ht()},e.prototype.dispose=function(){this._clear()},e.type=\"graphic\",e}(wf);function dN(t,e,n,i){var r=n.type;var o=dt(uN,r)?uN[r]:Ru(r);var a=new o(n);e.add(a),i.set(t,a),lN(a).__ecGraphicId=t}function fN(t,e){var n=t&&t.parent;n&&(\"group\"===t.type&&t.traverse((function(t){fN(t,e)})),e.removeKey(lN(t).__ecGraphicId),n.remove(t))}function gN(t,e){var n;return P(e,(function(e){null!=t[e]&&\"auto\"!==t[e]&&(n=!0)})),n}var yN=[\"x\",\"y\",\"radius\",\"angle\",\"single\"],vN=[\"cartesian2d\",\"polar\",\"singleAxis\"];function mN(t){return t+\"Axis\"}function _N(t,e){var n,i=ht(),r=[],o=ht();t.eachComponent({mainType:\"dataZoom\",query:e},(function(t){o.get(t.uid)||s(t)}));do{n=!1,t.eachComponent(\"dataZoom\",a)}while(n);function a(t){!o.get(t.uid)&&function(t){var e=!1;return t.eachTargetAxis((function(t,n){var r=i.get(t);r&&r[n]&&(e=!0)})),e}(t)&&(s(t),n=!0)}function s(t){o.set(t.uid,!0),r.push(t),t.eachTargetAxis((function(t,e){(i.get(t)||i.set(t,[]))[e]=!0}))}return r}function xN(t){var e=t.ecModel,n={infoList:[],infoMap:ht()};return t.eachTargetAxis((function(t,i){var r=e.getComponent(mN(t),i);if(r){var o=r.getCoordSysModel();if(o){var a=o.uid,s=n.infoMap.get(a);s||(s={model:o,axisModels:[]},n.infoList.push(s),n.infoMap.set(a,s)),s.axisModels.push(r)}}})),n}var bN=function(){function t(){this.indexList=[],this.indexMap=[]}return t.prototype.add=function(t){this.indexMap[t]||(this.indexList.push(t),this.indexMap[t]=!0)},t}(),wN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._autoThrottle=!0,n._noTarget=!0,n._rangePropMode=[\"percent\",\"percent\"],n}return n(e,t),e.prototype.init=function(t,e,n){var i=SN(t);this.settledOption=i,this.mergeDefaultAndTheme(t,n),this._doInit(i)},e.prototype.mergeOption=function(t){var e=SN(t);S(this.option,t,!0),S(this.settledOption,e,!0),this._doInit(e)},e.prototype._doInit=function(t){var e=this.option;this._setDefaultThrottle(t),this._updateRangeUse(t);var n=this.settledOption;P([[\"start\",\"startValue\"],[\"end\",\"endValue\"]],(function(t,i){\"value\"===this._rangePropMode[i]&&(e[t[0]]=n[t[0]]=null)}),this),this._resetTarget()},e.prototype._resetTarget=function(){var t=this.get(\"orient\",!0),e=this._targetAxisInfoMap=ht();this._fillSpecifiedTargetAxis(e)?this._orient=t||this._makeAutoOrientByTargetAxis():(this._orient=t||\"horizontal\",this._fillAutoTargetAxisByOrient(e,this._orient)),this._noTarget=!0,e.each((function(t){t.indexList.length&&(this._noTarget=!1)}),this)},e.prototype._fillSpecifiedTargetAxis=function(t){var e=!1;return P(yN,(function(n){var i=this.getReferringComponents(mN(n),zr);if(i.specified){e=!0;var r=new bN;P(i.models,(function(t){r.add(t.componentIndex)})),t.set(n,r)}}),this),e},e.prototype._fillAutoTargetAxisByOrient=function(t,e){var n=this.ecModel,i=!0;if(i){var r=\"vertical\"===e?\"y\":\"x\";o(n.findComponents({mainType:r+\"Axis\"}),r)}i&&o(n.findComponents({mainType:\"singleAxis\",filter:function(t){return t.get(\"orient\",!0)===e}}),\"single\");function o(e,n){var r=e[0];if(r){var o=new bN;if(o.add(r.componentIndex),t.set(n,o),i=!1,\"x\"===n||\"y\"===n){var a=r.getReferringComponents(\"grid\",Nr).models[0];a&&P(e,(function(t){r.componentIndex!==t.componentIndex&&a===t.getReferringComponents(\"grid\",Nr).models[0]&&o.add(t.componentIndex)}))}}}i&&P(yN,(function(e){if(i){var r=n.findComponents({mainType:mN(e),filter:function(t){return\"category\"===t.get(\"type\",!0)}});if(r[0]){var o=new bN;o.add(r[0].componentIndex),t.set(e,o),i=!1}}}),this)},e.prototype._makeAutoOrientByTargetAxis=function(){var t;return this.eachTargetAxis((function(e){!t&&(t=e)}),this),\"y\"===t?\"vertical\":\"horizontal\"},e.prototype._setDefaultThrottle=function(t){if(t.hasOwnProperty(\"throttle\")&&(this._autoThrottle=!1),this._autoThrottle){var e=this.ecModel.option;this.option.throttle=e.animation&&e.animationDurationUpdate>0?100:20}},e.prototype._updateRangeUse=function(t){var e=this._rangePropMode,n=this.get(\"rangeMode\");P([[\"start\",\"startValue\"],[\"end\",\"endValue\"]],(function(i,r){var o=null!=t[i[0]],a=null!=t[i[1]];o&&!a?e[r]=\"percent\":!o&&a?e[r]=\"value\":n?e[r]=n[r]:o&&(e[r]=\"percent\")}))},e.prototype.noTarget=function(){return this._noTarget},e.prototype.getFirstTargetAxisModel=function(){var t;return this.eachTargetAxis((function(e,n){null==t&&(t=this.ecModel.getComponent(mN(e),n))}),this),t},e.prototype.eachTargetAxis=function(t,e){this._targetAxisInfoMap.each((function(n,i){P(n.indexList,(function(n){t.call(e,i,n)}))}))},e.prototype.getAxisProxy=function(t,e){var n=this.getAxisModel(t,e);if(n)return n.__dzAxisProxy},e.prototype.getAxisModel=function(t,e){var n=this._targetAxisInfoMap.get(t);if(n&&n.indexMap[e])return this.ecModel.getComponent(mN(t),e)},e.prototype.setRawRange=function(t){var e=this.option,n=this.settledOption;P([[\"start\",\"startValue\"],[\"end\",\"endValue\"]],(function(i){null==t[i[0]]&&null==t[i[1]]||(e[i[0]]=n[i[0]]=t[i[0]],e[i[1]]=n[i[1]]=t[i[1]])}),this),this._updateRangeUse(t)},e.prototype.setCalculatedRange=function(t){var e=this.option;P([\"start\",\"startValue\",\"end\",\"endValue\"],(function(n){e[n]=t[n]}))},e.prototype.getPercentRange=function(){var t=this.findRepresentativeAxisProxy();if(t)return t.getDataPercentWindow()},e.prototype.getValueRange=function(t,e){if(null!=t||null!=e)return this.getAxisProxy(t,e).getDataValueWindow();var n=this.findRepresentativeAxisProxy();return n?n.getDataValueWindow():void 0},e.prototype.findRepresentativeAxisProxy=function(t){if(t)return t.__dzAxisProxy;for(var e,n=this._targetAxisInfoMap.keys(),i=0;i<n.length;i++)for(var r=n[i],o=this._targetAxisInfoMap.get(r),a=0;a<o.indexList.length;a++){var s=this.getAxisProxy(r,o.indexList[a]);if(s.hostedBy(this))return s;e||(e=s)}return e},e.prototype.getRangePropMode=function(){return this._rangePropMode.slice()},e.prototype.getOrient=function(){return this._orient},e.type=\"dataZoom\",e.dependencies=[\"xAxis\",\"yAxis\",\"radiusAxis\",\"angleAxis\",\"singleAxis\",\"series\",\"toolbox\"],e.defaultOption={zlevel:0,z:4,filterMode:\"filter\",start:0,end:100},e}(Xc);function SN(t){var e={};return P([\"start\",\"end\",\"startValue\",\"endValue\",\"throttle\"],(function(n){t.hasOwnProperty(n)&&(e[n]=t[n])})),e}var MN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"dataZoom.select\",e}(wN),IN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n,i){this.dataZoomModel=t,this.ecModel=e,this.api=n},e.type=\"dataZoom\",e}(wf),TN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"dataZoom.select\",e}(IN),CN=P,DN=qi,AN=function(){function t(t,e,n,i){this._dimName=t,this._axisIndex=e,this.ecModel=i,this._dataZoomModel=n}return t.prototype.hostedBy=function(t){return this._dataZoomModel===t},t.prototype.getDataValueWindow=function(){return this._valueWindow.slice()},t.prototype.getDataPercentWindow=function(){return this._percentWindow.slice()},t.prototype.getTargetSeriesModels=function(){var t=[];return this.ecModel.eachSeries((function(e){if(function(t){var e=t.get(\"coordinateSystem\");return D(vN,e)>=0}(e)){var n=mN(this._dimName),i=e.getReferringComponents(n,Nr).models[0];i&&this._axisIndex===i.componentIndex&&t.push(e)}}),this),t},t.prototype.getAxisModel=function(){return this.ecModel.getComponent(this._dimName+\"Axis\",this._axisIndex)},t.prototype.getMinMaxSpan=function(){return w(this._minMaxSpan)},t.prototype.calculateDataWindow=function(t){var e,n=this._dataExtent,i=this.getAxisModel().axis.scale,r=this._dataZoomModel.getRangePropMode(),o=[0,100],a=[],s=[];CN([\"start\",\"end\"],(function(l,u){var h=t[l],c=t[l+\"Value\"];\"percent\"===r[u]?(null==h&&(h=o[u]),c=i.parse(Yi(h,o,n))):(e=!0,h=Yi(c=null==c?n[u]:i.parse(c),n,o)),s[u]=c,a[u]=h})),DN(s),DN(a);var l=this._minMaxSpan;function u(t,e,n,r,o){var a=o?\"Span\":\"ValueSpan\";PD(0,t,n,\"all\",l[\"min\"+a],l[\"max\"+a]);for(var s=0;s<2;s++)e[s]=Yi(t[s],n,r,!0),o&&(e[s]=i.parse(e[s]))}return e?u(s,a,n,o,!1):u(a,s,o,n,!0),{valueWindow:s,percentWindow:a}},t.prototype.reset=function(t){if(t===this._dataZoomModel){var e=this.getTargetSeriesModels();this._dataExtent=function(t,e,n){var i=[1/0,-1/0];CN(n,(function(t){!function(t,e,n){e&&P(Xx(e,n),(function(n){var i=e.getApproximateExtent(n);i[0]<t[0]&&(t[0]=i[0]),i[1]>t[1]&&(t[1]=i[1])}))}(i,t.getData(),e)}));var r=t.getAxisModel(),o=Nx(r.axis.scale,r,i).calculate();return[o.min,o.max]}(this,this._dimName,e),this._updateMinMaxSpan();var n=this.calculateDataWindow(t.settledOption);this._valueWindow=n.valueWindow,this._percentWindow=n.percentWindow,this._setAxisModel()}},t.prototype.filterData=function(t,e){if(t===this._dataZoomModel){var n=this._dimName,i=this.getTargetSeriesModels(),r=t.get(\"filterMode\"),o=this._valueWindow;\"none\"!==r&&CN(i,(function(t){var e=t.getData(),i=e.mapDimensionsAll(n);i.length&&(\"weakFilter\"===r?e.filterSelf((function(t){for(var n,r,a,s=0;s<i.length;s++){var l=e.get(i[s],t),u=!isNaN(l),h=l<o[0],c=l>o[1];if(u&&!h&&!c)return!0;u&&(a=!0),h&&(n=!0),c&&(r=!0)}return a&&n&&r})):CN(i,(function(n){if(\"empty\"===r)t.setData(e=e.map(n,(function(t){return function(t){return t>=o[0]&&t<=o[1]}(t)?t:NaN})));else{var i={};i[n]=o,e.selectRange(i)}})),CN(i,(function(t){e.setApproximateExtent(o,t)})))}))}},t.prototype._updateMinMaxSpan=function(){var t=this._minMaxSpan={},e=this._dataZoomModel,n=this._dataExtent;CN([\"min\",\"max\"],(function(i){var r=e.get(i+\"Span\"),o=e.get(i+\"ValueSpan\");null!=o&&(o=this.getAxisModel().axis.scale.parse(o)),null!=o?r=Yi(n[0]+o,n,[0,100],!0):null!=r&&(o=Yi(r,[0,100],n,!0)-n[0]),t[i+\"Span\"]=r,t[i+\"ValueSpan\"]=o}),this)},t.prototype._setAxisModel=function(){var t=this.getAxisModel(),e=this._percentWindow,n=this._valueWindow;if(e){var i=Ji(n,[0,500]);i=Math.min(i,20);var r=t.axis.scale.rawExtentInfo;0!==e[0]&&r.setDeterminedMinMax(\"min\",+n[0].toFixed(i)),100!==e[1]&&r.setDeterminedMinMax(\"max\",+n[1].toFixed(i)),r.freeze()}},t}();var LN={getTargetSeries:function(t){function e(e){t.eachComponent(\"dataZoom\",(function(n){n.eachTargetAxis((function(i,r){var o=t.getComponent(mN(i),r);e(i,r,o,n)}))}))}e((function(t,e,n,i){n.__dzAxisProxy=null}));var n=[];e((function(e,i,r,o){r.__dzAxisProxy||(r.__dzAxisProxy=new AN(e,i,o,t),n.push(r.__dzAxisProxy))}));var i=ht();return P(n,(function(t){P(t.getTargetSeriesModels(),(function(t){i.set(t.uid,t)}))})),i},overallReset:function(t,e){t.eachComponent(\"dataZoom\",(function(t){t.eachTargetAxis((function(e,n){t.getAxisProxy(e,n).reset(t)})),t.eachTargetAxis((function(n,i){t.getAxisProxy(n,i).filterData(t,e)}))})),t.eachComponent(\"dataZoom\",(function(t){var e=t.findRepresentativeAxisProxy();if(e){var n=e.getDataPercentWindow(),i=e.getDataValueWindow();t.setCalculatedRange({start:n[0],end:n[1],startValue:i[0],endValue:i[1]})}}))}};var kN=!1;function PN(t){kN||(kN=!0,t.registerProcessor(t.PRIORITY.PROCESSOR.FILTER,LN),function(t){t.registerAction(\"dataZoom\",(function(t,e){P(_N(e,t),(function(e){e.setRawRange({start:t.start,end:t.end,startValue:t.startValue,endValue:t.endValue})}))}))}(t),t.registerSubTypeDefaulter(\"dataZoom\",(function(){return\"slider\"})))}function ON(t){t.registerComponentModel(MN),t.registerComponentView(TN),PN(t)}var RN=function(){},NN={};function zN(t,e){NN[t]=e}function EN(t){return NN[t]}var VN=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(){t.prototype.optionUpdated.apply(this,arguments);var e=this.ecModel;P(this.option.feature,(function(t,n){var i=EN(n);i&&(i.getDefaultOption&&(i.defaultOption=i.getDefaultOption(e)),S(t,i.defaultOption))}))},e.type=\"toolbox\",e.layoutMode={type:\"box\",ignoreSize:!0},e.defaultOption={show:!0,z:6,zlevel:0,orient:\"horizontal\",left:\"right\",top:\"top\",backgroundColor:\"transparent\",borderColor:\"#ccc\",borderRadius:0,borderWidth:0,padding:5,itemSize:15,itemGap:8,showTitle:!0,iconStyle:{borderColor:\"#666\",color:\"none\"},emphasis:{iconStyle:{borderColor:\"#3E98C5\"}},tooltip:{show:!1,position:\"bottom\"}},e}(Xc);function BN(t,e){var n=wc(e.get(\"padding\")),i=e.getItemStyle([\"color\",\"opacity\"]);return i.fill=e.get(\"backgroundColor\"),t=new ls({shape:{x:t.x-n[3],y:t.y-n[0],width:t.width+n[1]+n[3],height:t.height+n[0]+n[2],r:e.get(\"borderRadius\")},style:i,silent:!0,z2:-1})}var FN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n,i){var r=this.group;if(r.removeAll(),t.get(\"show\")){var o=+t.get(\"itemSize\"),a=t.get(\"feature\")||{},s=this._features||(this._features={}),l=[];P(a,(function(t,e){l.push(e)})),new n_(this._featureNames||[],l).add(u).update(u).remove(B(u,null)).execute(),this._featureNames=l,function(t,e,n){var i=e.getBoxLayoutParams(),r=e.get(\"padding\"),o={width:n.getWidth(),height:n.getHeight()},a=Vc(i,o,r);Ec(e.get(\"orient\"),t,e.get(\"itemGap\"),a.width,a.height),Bc(t,i,o,r)}(r,t,n),r.add(BN(r.getBoundingRect(),t)),r.eachChild((function(t){var e=t.__title,i=t.ensureState(\"emphasis\"),a=i.textConfig||(i.textConfig={}),s=t.getTextContent(),l=s&&s.states.emphasis;if(l&&!G(l)&&e){var u=l.style||(l.style={}),h=bi(e,cs.makeFont(u)),c=t.x+r.x,p=!1;t.y+r.y+o+h.height>n.getHeight()&&(a.position=\"top\",p=!0);var d=p?-5-h.height:o+8;c+h.width/2>n.getWidth()?(a.position=[\"100%\",d],u.align=\"right\"):c-h.width/2<0&&(a.position=[0,d],u.align=\"left\")}}))}function u(u,h){var c,p=l[u],d=l[h],f=a[p],g=new Oh(f,t,t.ecModel);if(i&&null!=i.newTitle&&i.featureName===p&&(f.title=i.newTitle),p&&!d){if(function(t){return 0===t.indexOf(\"my\")}(p))c={onclick:g.option.onclick,featureName:p};else{var y=EN(p);if(!y)return;c=new y}s[p]=c}else if(!(c=s[d]))return;c.uid=Nh(\"toolbox-feature\"),c.model=g,c.ecModel=e,c.api=n;var v=c instanceof RN;p||!d?!g.get(\"show\")||v&&c.unusable?v&&c.remove&&c.remove(e,n):(!function(i,a,s){var l,u,h=i.getModel(\"iconStyle\"),c=i.getModel([\"emphasis\",\"iconStyle\"]),p=a instanceof RN&&a.getIcons?a.getIcons():i.get(\"icon\"),d=i.get(\"title\")||{};\"string\"==typeof p?(l={})[s]=p:l=p;\"string\"==typeof d?(u={})[s]=d:u=d;var f=i.iconPaths={};P(l,(function(s,l){var p=eh(s,{},{x:-o/2,y:-o/2,width:o,height:o});p.setStyle(h.getItemStyle()),p.ensureState(\"emphasis\").style=c.getItemStyle();var d=new cs({style:{text:u[l],align:c.get(\"textAlign\"),borderRadius:c.get(\"textBorderRadius\"),padding:c.get(\"textPadding\"),fill:null},ignore:!0});p.setTextContent(d),oh({el:p,componentModel:t,itemName:l,formatterParamsExtra:{title:u[l]}}),p.__title=u[l],p.on(\"mouseover\",(function(){var e=c.getItemStyle(),n=\"vertical\"===t.get(\"orient\")?null==t.get(\"right\")?\"right\":\"left\":null==t.get(\"bottom\")?\"bottom\":\"top\";d.setStyle({fill:c.get(\"textFill\")||e.fill||e.stroke||\"#000\",backgroundColor:c.get(\"textBackgroundColor\")}),p.setTextConfig({position:c.get(\"textPosition\")||n}),d.ignore=!t.get(\"showTitle\"),js(this)})).on(\"mouseout\",(function(){\"emphasis\"!==i.get([\"iconStatus\",l])&&qs(this),d.hide()})),(\"emphasis\"===i.get([\"iconStatus\",l])?js:qs)(p),r.add(p),p.on(\"click\",V(a.onclick,a,e,n,l)),f[l]=p}))}(g,c,p),g.setIconStatus=function(t,e){var n=this.option,i=this.iconPaths;n.iconStatus=n.iconStatus||{},n.iconStatus[t]=e,i[t]&&(\"emphasis\"===e?js:qs)(i[t])},c instanceof RN&&c.render&&c.render(g,e,n,i)):v&&c.dispose&&c.dispose(e,n)}},e.prototype.updateView=function(t,e,n,i){P(this._features,(function(t){t instanceof RN&&t.updateView&&t.updateView(t.model,e,n,i)}))},e.prototype.remove=function(t,e){P(this._features,(function(n){n instanceof RN&&n.remove&&n.remove(t,e)})),this.group.removeAll()},e.prototype.dispose=function(t,e){P(this._features,(function(n){n instanceof RN&&n.dispose&&n.dispose(t,e)}))},e.type=\"toolbox\",e}(wf);var GN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.onclick=function(t,e){var n=this.model,i=n.get(\"name\")||t.get(\"title.0.text\")||\"echarts\",r=\"svg\"===e.getZr().painter.getType(),o=r?\"svg\":n.get(\"type\",!0)||\"png\",s=e.getConnectedDataURL({type:o,backgroundColor:n.get(\"backgroundColor\",!0)||t.get(\"backgroundColor\")||\"#fff\",connectedBackgroundColor:n.get(\"connectedBackgroundColor\"),excludeComponents:n.get(\"excludeComponents\"),pixelRatio:n.get(\"pixelRatio\")});if(\"function\"!=typeof MouseEvent||!a.browser.newEdge&&(a.browser.ie||a.browser.edge))if(window.navigator.msSaveOrOpenBlob||r){var l=s.split(\",\"),u=l[0].indexOf(\"base64\")>-1,h=r?decodeURIComponent(l[1]):l[1];u&&(h=window.atob(h));var c=i+\".\"+o;if(window.navigator.msSaveOrOpenBlob){for(var p=h.length,d=new Uint8Array(p);p--;)d[p]=h.charCodeAt(p);var f=new Blob([d]);window.navigator.msSaveOrOpenBlob(f,c)}else{var g=document.createElement(\"iframe\");document.body.appendChild(g);var y=g.contentWindow,v=y.document;v.open(\"image/svg+xml\",\"replace\"),v.write(h),v.close(),y.focus(),v.execCommand(\"SaveAs\",!0,c),document.body.removeChild(g)}}else{var m=n.get(\"lang\"),_='<body style=\"margin:0;\"><img src=\"'+s+'\" style=\"max-width:100%;\" title=\"'+(m&&m[0]||\"\")+'\" /></body>',x=window.open();x.document.write(_),x.document.title=i}else{var b=document.createElement(\"a\");b.download=i+\".\"+o,b.target=\"_blank\",b.href=s;var w=new MouseEvent(\"click\",{view:document.defaultView,bubbles:!0,cancelable:!1});b.dispatchEvent(w)}},e.getDefaultOption=function(t){return{show:!0,icon:\"M4.7,22.9L29.3,45.5L54.7,23.4M4.6,43.6L4.6,58L53.8,58L53.8,43.6M29.2,45.1L29.2,0\",title:t.getLocale([\"toolbox\",\"saveAsImage\",\"title\"]),type:\"png\",connectedBackgroundColor:\"#fff\",name:\"\",excludeComponents:[\"toolbox\"],lang:t.getLocale([\"toolbox\",\"saveAsImage\",\"lang\"])}},e}(RN);GN.prototype.unusable=!a.canvasSupported;var HN=\"__ec_magicType_stack__\",WN=[[\"line\",\"bar\"],[\"stack\"]],UN=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.getIcons=function(){var t=this.model,e=t.get(\"icon\"),n={};return P(t.get(\"type\"),(function(t){e[t]&&(n[t]=e[t])})),n},e.getDefaultOption=function(t){return{show:!0,type:[],icon:{line:\"M4.1,28.9h7.1l9.3-22l7.4,38l9.7-19.7l3,12.8h14.9M4.1,58h51.4\",bar:\"M6.7,22.9h10V48h-10V22.9zM24.9,13h10v35h-10V13zM43.2,2h10v46h-10V2zM3.1,58h53.7\",stack:\"M8.2,38.4l-8.4,4.1l30.6,15.3L60,42.5l-8.1-4.1l-21.5,11L8.2,38.4z M51.9,30l-8.1,4.2l-13.4,6.9l-13.9-6.9L8.2,30l-8.4,4.2l8.4,4.2l22.2,11l21.5-11l8.1-4.2L51.9,30z M51.9,21.7l-8.1,4.2L35.7,30l-5.3,2.8L24.9,30l-8.4-4.1l-8.3-4.2l-8.4,4.2L8.2,30l8.3,4.2l13.9,6.9l13.4-6.9l8.1-4.2l8.1-4.1L51.9,21.7zM30.4,2.2L-0.2,17.5l8.4,4.1l8.3,4.2l8.4,4.2l5.5,2.7l5.3-2.7l8.1-4.2l8.1-4.2l8.1-4.1L30.4,2.2z\"},title:t.getLocale([\"toolbox\",\"magicType\",\"title\"]),option:{},seriesIndex:{}}},e.prototype.onclick=function(t,e,n){var i=this.model,r=i.get([\"seriesIndex\",n]);if(XN[n]){var o,a={series:[]};P(WN,(function(t){D(t,n)>=0&&P(t,(function(t){i.setIconStatus(t,\"normal\")}))})),i.setIconStatus(n,\"emphasis\"),t.eachComponent({mainType:\"series\",query:null==r?null:{seriesIndex:r}},(function(t){var e=t.subType,r=t.id,o=XN[n](e,r,t,i);o&&(T(o,t.option),a.series.push(o));var s=t.coordinateSystem;if(s&&\"cartesian2d\"===s.type&&(\"line\"===n||\"bar\"===n)){var l=s.getAxesByScale(\"ordinal\")[0];if(l){var u=l.dim+\"Axis\",h=t.getReferringComponents(u,Nr).models[0].componentIndex;a[u]=a[u]||[];for(var c=0;c<=h;c++)a[u][h]=a[u][h]||{};a[u][h].boundaryGap=\"bar\"===n}}}));var s=n;\"stack\"===n&&(o=S({stack:i.option.title.tiled,tiled:i.option.title.stack},i.option.title),\"emphasis\"!==i.get([\"iconStatus\",n])&&(s=\"tiled\")),e.dispatchAction({type:\"changeMagicType\",currentType:s,newOption:a,newTitle:o,featureName:\"magicType\"})}},e}(RN),XN={line:function(t,e,n,i){if(\"bar\"===t)return S({id:e,type:\"line\",data:n.get(\"data\"),stack:n.get(\"stack\"),markPoint:n.get(\"markPoint\"),markLine:n.get(\"markLine\")},i.get([\"option\",\"line\"])||{},!0)},bar:function(t,e,n,i){if(\"line\"===t)return S({id:e,type:\"bar\",data:n.get(\"data\"),stack:n.get(\"stack\"),markPoint:n.get(\"markPoint\"),markLine:n.get(\"markLine\")},i.get([\"option\",\"bar\"])||{},!0)},stack:function(t,e,n,i){var r=n.get(\"stack\")===HN;if(\"line\"===t||\"bar\"===t)return i.setIconStatus(\"stack\",r?\"normal\":\"emphasis\"),S({id:e,stack:r?\"\":HN},i.get([\"option\",\"stack\"])||{},!0)}};Hm({type:\"changeMagicType\",event:\"magicTypeChanged\",update:\"prepareAndUpdate\"},(function(t,e){e.mergeOption(t.newOption)}));var YN=new Array(60).join(\"-\"),ZN=\"\\t\";function jN(t){return t.replace(/^\\s\\s*/,\"\").replace(/\\s\\s*$/,\"\")}var qN=new RegExp(\"[\\t]+\",\"g\");function KN(t,e){var n=t.split(new RegExp(\"\\n*\"+YN+\"\\n*\",\"g\")),i={series:[]};return P(n,(function(t,n){if(function(t){if(t.slice(0,t.indexOf(\"\\n\")).indexOf(ZN)>=0)return!0}(t)){var r=function(t){for(var e=t.split(/\\n+/g),n=[],i=O(jN(e.shift()).split(qN),(function(t){return{name:t,data:[]}})),r=0;r<e.length;r++){var o=jN(e[r]).split(qN);n.push(o.shift());for(var a=0;a<o.length;a++)i[a]&&(i[a].data[r]=o[a])}return{series:i,categories:n}}(t),o=e[n],a=o.axisDim+\"Axis\";o&&(i[a]=i[a]||[],i[a][o.axisIndex]={data:r.categories},i.series=i.series.concat(r.series))}else{r=function(t){for(var e=t.split(/\\n+/g),n=jN(e.shift()),i=[],r=0;r<e.length;r++){var o=jN(e[r]);if(o){var a=o.split(qN),s=\"\",l=void 0,u=!1;isNaN(a[0])?(u=!0,s=a[0],a=a.slice(1),i[r]={name:s,value:[]},l=i[r].value):l=i[r]=[];for(var h=0;h<a.length;h++)l.push(+a[h]);1===l.length&&(u?i[r].value=l[0]:i[r]=l[0])}}return{name:n,data:i}}(t);i.series.push(r)}})),i}var $N=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.onclick=function(t,e){var n=e.getDom(),i=this.model;this._dom&&n.removeChild(this._dom);var r=document.createElement(\"div\");r.style.cssText=\"position:absolute;left:5px;top:5px;bottom:5px;right:5px;\",r.style.backgroundColor=i.get(\"backgroundColor\")||\"#fff\";var o=document.createElement(\"h4\"),a=i.get(\"lang\")||[];o.innerHTML=a[0]||i.get(\"title\"),o.style.cssText=\"margin: 10px 20px;\",o.style.color=i.get(\"textColor\");var s=document.createElement(\"div\"),l=document.createElement(\"textarea\");s.style.cssText=\"display:block;width:100%;overflow:auto;\";var u=i.get(\"optionToContent\"),h=i.get(\"contentToOption\"),c=function(t){var e,n,i,r=function(t){var e={},n=[],i=[];return t.eachRawSeries((function(t){var r=t.coordinateSystem;if(!r||\"cartesian2d\"!==r.type&&\"polar\"!==r.type)n.push(t);else{var o=r.getBaseAxis();if(\"category\"===o.type){var a=o.dim+\"_\"+o.index;e[a]||(e[a]={categoryAxis:o,valueAxis:r.getOtherAxis(o),series:[]},i.push({axisDim:o.dim,axisIndex:o.index})),e[a].series.push(t)}else n.push(t)}})),{seriesGroupByCategoryAxis:e,other:n,meta:i}}(t);return{value:N([(n=r.seriesGroupByCategoryAxis,i=[],P(n,(function(t,e){var n=t.categoryAxis,r=t.valueAxis.dim,o=[\" \"].concat(O(t.series,(function(t){return t.name}))),a=[n.model.getCategories()];P(t.series,(function(t){var e=t.getRawData();a.push(t.getRawData().mapArray(e.mapDimension(r),(function(t){return t})))}));for(var s=[o.join(ZN)],l=0;l<a[0].length;l++){for(var u=[],h=0;h<a.length;h++)u.push(a[h][l]);s.push(u.join(ZN))}i.push(s.join(\"\\n\"))})),i.join(\"\\n\\n\"+YN+\"\\n\\n\")),(e=r.other,O(e,(function(t){var e=t.getRawData(),n=[t.name],i=[];return e.each(e.dimensions,(function(){for(var t=arguments.length,r=arguments[t-1],o=e.getName(r),a=0;a<t-1;a++)i[a]=arguments[a];n.push((o?o+ZN:\"\")+i.join(ZN))})),n.join(\"\\n\")})).join(\"\\n\\n\"+YN+\"\\n\\n\"))],(function(t){return!!t.replace(/[\\n\\t\\s]/g,\"\")})).join(\"\\n\\n\"+YN+\"\\n\\n\"),meta:r.meta}}(t);if(\"function\"==typeof u){var p=u(e.getOption());\"string\"==typeof p?s.innerHTML=p:j(p)&&s.appendChild(p)}else s.appendChild(l),l.readOnly=i.get(\"readOnly\"),l.style.cssText=\"width:100%;height:100%;font-family:monospace;font-size:14px;line-height:1.6rem;\",l.style.color=i.get(\"textColor\"),l.style.borderColor=i.get(\"textareaBorderColor\"),l.style.backgroundColor=i.get(\"textareaColor\"),l.value=c.value;var d=c.meta,f=document.createElement(\"div\");f.style.cssText=\"position:absolute;bottom:0;left:0;right:0;\";var g=\"float:right;margin-right:20px;border:none;cursor:pointer;padding:2px 5px;font-size:12px;border-radius:3px\",y=document.createElement(\"div\"),v=document.createElement(\"div\");g+=\";background-color:\"+i.get(\"buttonColor\"),g+=\";color:\"+i.get(\"buttonTextColor\");var m=this;function _(){n.removeChild(r),m._dom=null}te(y,\"click\",_),te(v,\"click\",(function(){if(null==h&&null!=u||null!=h&&null==u)_();else{var t;try{t=\"function\"==typeof h?h(s,e.getOption()):KN(l.value,d)}catch(t){throw _(),new Error(\"Data view format error \"+t)}t&&e.dispatchAction({type:\"changeDataView\",newOption:t}),_()}})),y.innerHTML=a[1],v.innerHTML=a[2],v.style.cssText=g,y.style.cssText=g,!i.get(\"readOnly\")&&f.appendChild(v),f.appendChild(y),r.appendChild(o),r.appendChild(s),r.appendChild(f),s.style.height=n.clientHeight-80+\"px\",n.appendChild(r),this._dom=r},e.prototype.remove=function(t,e){this._dom&&e.getDom().removeChild(this._dom)},e.prototype.dispose=function(t,e){this.remove(t,e)},e.getDefaultOption=function(t){return{show:!0,readOnly:!1,optionToContent:null,contentToOption:null,icon:\"M17.5,17.3H33 M17.5,17.3H33 M45.4,29.5h-28 M11.5,2v56H51V14.8L38.4,2H11.5z M38.4,2.2v12.7H51 M45.4,41.7h-28\",title:t.getLocale([\"toolbox\",\"dataView\",\"title\"]),lang:t.getLocale([\"toolbox\",\"dataView\",\"lang\"]),backgroundColor:\"#fff\",textColor:\"#000\",textareaColor:\"#fff\",textareaBorderColor:\"#333\",buttonColor:\"#c23531\",buttonTextColor:\"#fff\"}},e}(RN);function JN(t,e){return O(t,(function(t,n){var i=e&&e[n];if(X(i)&&!F(i)){X(t)&&!F(t)||(t={value:t});var r=null!=i.name&&null==t.name;return t=T(t,i),r&&delete t.name,t}return t}))}Hm({type:\"changeDataView\",event:\"dataViewChanged\",update:\"prepareAndUpdate\"},(function(t,e){var n=[];P(t.newOption.series,(function(t){var i=e.getSeriesByName(t.name)[0];if(i){var r=i.get(\"data\");n.push({name:t.name,data:JN(t.data,r)})}else n.push(I({type:\"scatter\"},t))})),e.mergeOption(T({series:n},t.newOption))}));var QN=P,tz=kr();function ez(t){var e=tz(t);return e.snapshots||(e.snapshots=[{}]),e.snapshots}var nz=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.onclick=function(t,e){!function(t){tz(t).snapshots=null}(t),e.dispatchAction({type:\"restore\",from:this.uid})},e.getDefaultOption=function(t){return{show:!0,icon:\"M3.8,33.4 M47,18.9h9.8V8.7 M56.3,20.1 C52.1,9,40.5,0.6,26.8,2.1C12.6,3.7,1.6,16.2,2.1,30.6 M13,41.1H3.1v10.2 M3.7,39.9c4.2,11.1,15.8,19.5,29.5,18 c14.2-1.6,25.2-14.1,24.7-28.5\",title:t.getLocale([\"toolbox\",\"restore\",\"title\"])}},e}(RN);Hm({type:\"restore\",event:\"restore\",update:\"prepareAndUpdate\"},(function(t,e){e.resetOption(\"recreate\")}));var iz=[\"grid\",\"xAxis\",\"yAxis\",\"geo\",\"graph\",\"polar\",\"radiusAxis\",\"angleAxis\",\"bmap\"],rz=function(){function t(t,e,n){var i=this;this._targetInfoList=[];var r=az(e,t);P(sz,(function(t,e){(!n||!n.include||D(n.include,e)>=0)&&t(r,i._targetInfoList)}))}return t.prototype.setOutputRanges=function(t,e){return this.matchOutputRanges(t,e,(function(t,e,n){if((t.coordRanges||(t.coordRanges=[])).push(e),!t.coordRange){t.coordRange=e;var i=hz[t.brushType](0,n,e);t.__rangeOffset={offset:pz[t.brushType](i.values,t.range,[1,1]),xyMinMax:i.xyMinMax}}})),t},t.prototype.matchOutputRanges=function(t,e,n){P(t,(function(t){var i=this.findTargetInfo(t,e);i&&!0!==i&&P(i.coordSyses,(function(i){var r=hz[t.brushType](1,i,t.range,!0);n(t,r.values,i,e)}))}),this)},t.prototype.setInputRanges=function(t,e){P(t,(function(t){var n,i,r,o,a,s=this.findTargetInfo(t,e);if(t.range=t.range||[],s&&!0!==s){t.panelId=s.panelId;var l=hz[t.brushType](0,s.coordSys,t.coordRange),u=t.__rangeOffset;t.range=u?pz[t.brushType](l.values,u.offset,(n=l.xyMinMax,i=u.xyMinMax,r=fz(n),o=fz(i),a=[r[0]/o[0],r[1]/o[1]],isNaN(a[0])&&(a[0]=1),isNaN(a[1])&&(a[1]=1),a)):l.values}}),this)},t.prototype.makePanelOpts=function(t,e){return O(this._targetInfoList,(function(n){var i=n.getPanelRect();return{panelId:n.panelId,defaultBrushType:e?e(n):null,clipPath:RA(i),isTargetByCursor:zA(i,t,n.coordSysModel),getLinearBrushOtherExtent:NA(i)}}))},t.prototype.controlSeries=function(t,e,n){var i=this.findTargetInfo(t,n);return!0===i||i&&D(i.coordSyses,e.coordinateSystem)>=0},t.prototype.findTargetInfo=function(t,e){for(var n=this._targetInfoList,i=az(e,t),r=0;r<n.length;r++){var o=n[r],a=t.panelId;if(a){if(o.panelId===a)return o}else for(var s=0;s<lz.length;s++)if(lz[s](i,o))return o}return!0},t}();function oz(t){return t[0]>t[1]&&t.reverse(),t}function az(t,e){return Or(t,e,{includeMainTypes:iz})}var sz={grid:function(t,e){var n=t.xAxisModels,i=t.yAxisModels,r=t.gridModels,o=ht(),a={},s={};(n||i||r)&&(P(n,(function(t){var e=t.axis.grid.model;o.set(e.id,e),a[e.id]=!0})),P(i,(function(t){var e=t.axis.grid.model;o.set(e.id,e),s[e.id]=!0})),P(r,(function(t){o.set(t.id,t),a[t.id]=!0,s[t.id]=!0})),o.each((function(t){var r=t.coordinateSystem,o=[];P(r.getCartesians(),(function(t,e){(D(n,t.getAxis(\"x\").model)>=0||D(i,t.getAxis(\"y\").model)>=0)&&o.push(t)})),e.push({panelId:\"grid--\"+t.id,gridModel:t,coordSysModel:t,coordSys:o[0],coordSyses:o,getPanelRect:uz.grid,xAxisDeclared:a[t.id],yAxisDeclared:s[t.id]})})))},geo:function(t,e){P(t.geoModels,(function(t){var n=t.coordinateSystem;e.push({panelId:\"geo--\"+t.id,geoModel:t,coordSysModel:t,coordSys:n,coordSyses:[n],getPanelRect:uz.geo})}))}},lz=[function(t,e){var n=t.xAxisModel,i=t.yAxisModel,r=t.gridModel;return!r&&n&&(r=n.axis.grid.model),!r&&i&&(r=i.axis.grid.model),r&&r===e.gridModel},function(t,e){var n=t.geoModel;return n&&n===e.geoModel}],uz={grid:function(){return this.coordSys.master.getRect().clone()},geo:function(){var t=this.coordSys,e=t.getBoundingRect().clone();return e.applyTransform(ju(t)),e}},hz={lineX:B(cz,0),lineY:B(cz,1),rect:function(t,e,n,i){var r=t?e.pointToData([n[0][0],n[1][0]],i):e.dataToPoint([n[0][0],n[1][0]],i),o=t?e.pointToData([n[0][1],n[1][1]],i):e.dataToPoint([n[0][1],n[1][1]],i),a=[oz([r[0],o[0]]),oz([r[1],o[1]])];return{values:a,xyMinMax:a}},polygon:function(t,e,n,i){var r=[[1/0,-1/0],[1/0,-1/0]];return{values:O(n,(function(n){var o=t?e.pointToData(n,i):e.dataToPoint(n,i);return r[0][0]=Math.min(r[0][0],o[0]),r[1][0]=Math.min(r[1][0],o[1]),r[0][1]=Math.max(r[0][1],o[0]),r[1][1]=Math.max(r[1][1],o[1]),o})),xyMinMax:r}}};function cz(t,e,n,i){var r=n.getAxis([\"x\",\"y\"][t]),o=oz(O([0,1],(function(t){return e?r.coordToData(r.toLocalCoord(i[t]),!0):r.toGlobalCoord(r.dataToCoord(i[t]))}))),a=[];return a[t]=o,a[1-t]=[NaN,NaN],{values:o,xyMinMax:a}}var pz={lineX:B(dz,0),lineY:B(dz,1),rect:function(t,e,n){return[[t[0][0]-n[0]*e[0][0],t[0][1]-n[0]*e[0][1]],[t[1][0]-n[1]*e[1][0],t[1][1]-n[1]*e[1][1]]]},polygon:function(t,e,n){return O(t,(function(t,i){return[t[0]-n[0]*e[i][0],t[1]-n[1]*e[i][1]]}))}};function dz(t,e,n,i){return[e[0]-i[t]*n[0],e[1]-i[t]*n[1]]}function fz(t){return t?[t[0][1]-t[0][0],t[1][1]-t[1][0]]:[NaN,NaN]}var gz,yz,vz=P,mz=_r+\"toolbox-dataZoom_\",_z=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n,i){this._brushController||(this._brushController=new iA(n.getZr()),this._brushController.on(\"brush\",V(this._onBrush,this)).mount()),function(t,e,n,i,r){var o=n._isZoomActive;i&&\"takeGlobalCursor\"===i.type&&(o=\"dataZoomSelect\"===i.key&&i.dataZoomSelectActive);n._isZoomActive=o,t.setIconStatus(\"zoom\",o?\"emphasis\":\"normal\");var a=new rz(bz(t),e,{include:[\"grid\"]}).makePanelOpts(r,(function(t){return t.xAxisDeclared&&!t.yAxisDeclared?\"lineX\":!t.xAxisDeclared&&t.yAxisDeclared?\"lineY\":\"rect\"}));n._brushController.setPanels(a).enableBrush(!(!o||!a.length)&&{brushType:\"auto\",brushStyle:t.getModel(\"brushStyle\").getItemStyle()})}(t,e,this,i,n),function(t,e){t.setIconStatus(\"back\",function(t){return ez(t).length}(e)>1?\"emphasis\":\"normal\")}(t,e)},e.prototype.onclick=function(t,e,n){xz[n].call(this)},e.prototype.remove=function(t,e){this._brushController&&this._brushController.unmount()},e.prototype.dispose=function(t,e){this._brushController&&this._brushController.dispose()},e.prototype._onBrush=function(t){var e=t.areas;if(t.isEnd&&e.length){var n={},i=this.ecModel;this._brushController.updateCovers([]),new rz(bz(this.model),i,{include:[\"grid\"]}).matchOutputRanges(e,i,(function(t,e,n){if(\"cartesian2d\"===n.type){var i=t.brushType;\"rect\"===i?(r(\"x\",n,e[0]),r(\"y\",n,e[1])):r({lineX:\"x\",lineY:\"y\"}[i],n,e)}})),function(t,e){var n=ez(t);QN(e,(function(e,i){for(var r=n.length-1;r>=0&&!n[r][i];r--);if(r<0){var o=t.queryComponents({mainType:\"dataZoom\",subType:\"select\",id:i})[0];if(o){var a=o.getPercentRange();n[0][i]={dataZoomId:i,start:a[0],end:a[1]}}}})),n.push(e)}(i,n),this._dispatchZoomAction(n)}function r(t,e,r){var o=e.getAxis(t),a=o.model,s=function(t,e,n){var i;return n.eachComponent({mainType:\"dataZoom\",subType:\"select\"},(function(n){n.getAxisModel(t,e.componentIndex)&&(i=n)})),i}(t,a,i),l=s.findRepresentativeAxisProxy(a).getMinMaxSpan();null==l.minValueSpan&&null==l.maxValueSpan||(r=PD(0,r.slice(),o.scale.getExtent(),0,l.minValueSpan,l.maxValueSpan)),s&&(n[s.id]={dataZoomId:s.id,startValue:r[0],endValue:r[1]})}},e.prototype._dispatchZoomAction=function(t){var e=[];vz(t,(function(t,n){e.push(w(t))})),e.length&&this.api.dispatchAction({type:\"dataZoom\",from:this.uid,batch:e})},e.getDefaultOption=function(t){return{show:!0,filterMode:\"filter\",icon:{zoom:\"M0,13.5h26.9 M13.5,26.9V0 M32.1,13.5H58V58H13.5 V32.1\",back:\"M22,1.4L9.9,13.5l12.3,12.3 M10.3,13.5H54.9v44.6 H10.3v-26\"},title:t.getLocale([\"toolbox\",\"dataZoom\",\"title\"]),brushStyle:{borderWidth:0,color:\"rgba(210,219,238,0.2)\"}}},e}(RN),xz={zoom:function(){var t=!this._isZoomActive;this.api.dispatchAction({type:\"takeGlobalCursor\",key:\"dataZoomSelect\",dataZoomSelectActive:t})},back:function(){this._dispatchZoomAction(function(t){var e=ez(t),n=e[e.length-1];e.length>1&&e.pop();var i={};return QN(n,(function(t,n){for(var r=e.length-1;r>=0;r--)if(t=e[r][n]){i[n]=t;break}})),i}(this.ecModel))}};function bz(t){var e={xAxisIndex:t.get(\"xAxisIndex\",!0),yAxisIndex:t.get(\"yAxisIndex\",!0),xAxisId:t.get(\"xAxisId\",!0),yAxisId:t.get(\"yAxisId\",!0)};return null==e.xAxisIndex&&null==e.xAxisId&&(e.xAxisIndex=\"all\"),null==e.yAxisIndex&&null==e.yAxisId&&(e.yAxisIndex=\"all\"),e}gz=\"dataZoom\",yz=function(t){var e=t.getComponent(\"toolbox\",0),n=[\"feature\",\"dataZoom\"];if(e&&null!=e.get(n)){var i=e.getModel(n),r=[],o=Or(t,bz(i));return vz(o.xAxisModels,(function(t){return a(t,\"xAxis\",\"xAxisIndex\")})),vz(o.yAxisModels,(function(t){return a(t,\"yAxis\",\"yAxisIndex\")})),r}function a(t,e,n){var o=t.componentIndex,a={type:\"select\",$fromToolbox:!0,filterMode:i.get(\"filterMode\",!0)||\"filter\",id:mz+e+o};a[n]=o,r.push(a)}},rt(null==dp.get(gz)&&yz),dp.set(gz,yz);var wz=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"tooltip\",e.dependencies=[\"axisPointer\"],e.defaultOption={zlevel:0,z:60,show:!0,showContent:!0,trigger:\"item\",triggerOn:\"mousemove|click\",alwaysShowContent:!1,displayMode:\"single\",renderMode:\"auto\",confine:null,showDelay:0,hideDelay:100,transitionDuration:.4,enterable:!1,backgroundColor:\"#fff\",shadowBlur:10,shadowColor:\"rgba(0, 0, 0, .2)\",shadowOffsetX:1,shadowOffsetY:2,borderRadius:4,borderWidth:1,padding:null,extraCssText:\"\",axisPointer:{type:\"line\",axis:\"auto\",animation:\"auto\",animationDurationUpdate:200,animationEasingUpdate:\"exponentialOut\",crossStyle:{color:\"#999\",width:1,type:\"dashed\",textStyle:{}}},textStyle:{color:\"#666\",fontSize:14}},e}(Xc);function Sz(t){var e=t.get(\"confine\");return null!=e?!!e:\"richText\"===t.get(\"renderMode\")}function Mz(t){if(a.domSupported)for(var e=document.documentElement.style,n=0,i=t.length;n<i;n++)if(t[n]in e)return t[n]}var Iz=Mz([\"transform\",\"webkitTransform\",\"OTransform\",\"MozTransform\",\"msTransform\"]);function Tz(t,e){if(!t)return e;e=bc(e,!0);var n=t.indexOf(e);return(t=-1===n?e:\"-\"+t.slice(0,n)+\"-\"+e).toLowerCase()}function Cz(t,e){var n=t.currentStyle||document.defaultView&&document.defaultView.getComputedStyle(t);return n?e?n[e]:n:null}var Dz=Tz(Mz([\"webkitTransition\",\"transition\",\"OTransition\",\"MozTransition\",\"msTransition\"]),\"transition\"),Az=Tz(Iz,\"transform\"),Lz=\"position:absolute;display:block;border-style:solid;white-space:nowrap;z-index:9999999;\"+(a.transform3dSupported?\"will-change:transform;\":\"\");function kz(t,e,n){var i=t.toFixed(0)+\"px\",r=e.toFixed(0)+\"px\";if(!a.transformSupported)return n?\"top:\"+r+\";left:\"+i+\";\":[[\"top\",r],[\"left\",i]];var o=a.transform3dSupported,s=\"translate\"+(o?\"3d\":\"\")+\"(\"+i+\",\"+r+(o?\",0\":\"\")+\")\";return n?\"top:0;left:0;\"+Az+\":\"+s+\";\":[[\"top\",0],[\"left\",0],[Iz,s]]}function Pz(t,e,n){var i=[],r=t.get(\"transitionDuration\"),o=t.get(\"backgroundColor\"),s=t.get(\"shadowBlur\"),l=t.get(\"shadowColor\"),u=t.get(\"shadowOffsetX\"),h=t.get(\"shadowOffsetY\"),c=t.getModel(\"textStyle\"),p=uf(t,\"html\"),d=u+\"px \"+h+\"px \"+s+\"px \"+l;return i.push(\"box-shadow:\"+d),e&&r&&i.push(function(t,e){var n=\"cubic-bezier(0.23,1,0.32,1)\",i=\" \"+t/2+\"s \"+n,r=\"opacity\"+i+\",visibility\"+i;return e||(i=\" \"+t+\"s \"+n,r+=a.transformSupported?\",\"+Az+i:\",left\"+i+\",top\"+i),Dz+\":\"+r}(r,n)),o&&(a.canvasSupported?i.push(\"background-color:\"+o):(i.push(\"background-color:#\"+Xe(o)),i.push(\"filter:alpha(opacity=70)\"))),P([\"width\",\"color\",\"radius\"],(function(e){var n=\"border-\"+e,r=bc(n),o=t.get(r);null!=o&&i.push(n+\":\"+o+(\"color\"===e?\"\":\"px\"))})),i.push(function(t){var e=[],n=t.get(\"fontSize\"),i=t.getTextColor();i&&e.push(\"color:\"+i),e.push(\"font:\"+t.getFont()),n&&e.push(\"line-height:\"+Math.round(3*n/2)+\"px\");var r=t.get(\"textShadowColor\"),o=t.get(\"textShadowBlur\")||0,a=t.get(\"textShadowOffsetX\")||0,s=t.get(\"textShadowOffsetY\")||0;return r&&o&&e.push(\"text-shadow:\"+a+\"px \"+s+\"px \"+o+\"px \"+r),P([\"decoration\",\"align\"],(function(n){var i=t.get(n);i&&e.push(\"text-\"+n+\":\"+i)})),e.join(\";\")}(c)),null!=p&&i.push(\"padding:\"+wc(p).join(\"px \")+\"px\"),i.join(\";\")+\";\"}function Oz(t,e,n,i,r){var o=e&&e.painter;if(n){var a=o&&o.getViewportRoot();a&&function(t,e,n,i,r){Xt(Ut,e,i,r,!0)&&Xt(t,n,Ut[0],Ut[1])}(t,a,document.body,i,r)}else{t[0]=i,t[1]=r;var s=o&&o.getViewportRootOffset();s&&(t[0]+=s.offsetLeft,t[1]+=s.offsetTop)}t[2]=t[0]/e.getWidth(),t[3]=t[1]/e.getHeight()}var Rz=function(){function t(t,e,n){if(this._show=!1,this._styleCoord=[0,0,0,0],this._enterable=!0,this._firstShow=!0,this._longHide=!0,a.wxa)return null;var i=document.createElement(\"div\");i.domBelongToZr=!0,this.el=i;var r=this._zr=e.getZr(),o=this._appendToBody=n&&n.appendToBody;Oz(this._styleCoord,r,o,e.getWidth()/2,e.getHeight()/2),o?document.body.appendChild(i):t.appendChild(i),this._container=t;var s=this;i.onmouseenter=function(){s._enterable&&(clearTimeout(s._hideTimeout),s._show=!0),s._inContent=!0},i.onmousemove=function(t){if(t=t||window.event,!s._enterable){var e=r.handler;Qt(r.painter.getViewportRoot(),t,!0),e.dispatch(\"mousemove\",t)}},i.onmouseleave=function(){s._inContent=!1,s._enterable&&s._show&&s.hideLater(s._hideDelay)}}return t.prototype.update=function(t){var e=this._container,n=Cz(e,\"position\"),i=e.style;\"absolute\"!==i.position&&\"absolute\"!==n&&(i.position=\"relative\"),t.get(\"alwaysShowContent\")&&this._moveIfResized(),this.el.className=t.get(\"className\")||\"\"},t.prototype.show=function(t,e){clearTimeout(this._hideTimeout),clearTimeout(this._longHideTimeout);var n=this.el,i=n.style,r=this._styleCoord;n.innerHTML?i.cssText=Lz+Pz(t,!this._firstShow,this._longHide)+kz(r[0],r[1],!0)+\"border-color:\"+kc(e)+\";\"+(t.get(\"extraCssText\")||\"\")+\";pointer-event:\"+(this._enterable?\"auto\":\"none\"):i.display=\"none\",this._show=!0,this._firstShow=!1,this._longHide=!1},t.prototype.setContent=function(t,e,n,i,r){if(null!=t){var o=this.el;if(H(r)&&\"item\"===n.get(\"trigger\")&&!Sz(n)&&(t+=function(t,e,n){if(!H(n)||\"inside\"===n)return\"\";e=kc(e);var i,r=\"left\"===(i=n)?\"right\":\"right\"===i?\"left\":\"top\"===i?\"bottom\":\"top\",o=r+\":-6px;\",a=Az+\":\";D([\"left\",\"right\"],r)>-1?(o+=\"top:50%\",a+=\"translateY(-50%) rotate(\"+(\"left\"===r?-225:-45)+\"deg)\"):(o+=\"left:50%\",a+=\"translateX(-50%) rotate(\"+(\"top\"===r?225:45)+\"deg)\");var s=e+\" solid 1px;\";return'<div style=\"'+[\"position:absolute;width:10px;height:10px;\",o+\";\"+a+\";\",\"border-bottom:\"+s,\"border-right:\"+s,\"background-color:\"+t+\";\",\"box-shadow:8px 8px 16px -3px #000;\"].join(\"\")+'\"></div>'}(n.get(\"backgroundColor\"),i,r)),H(t))o.innerHTML=t;else if(t){o.innerHTML=\"\",F(t)||(t=[t]);for(var a=0;a<t.length;a++)j(t[a])&&t[a].parentNode!==o&&o.appendChild(t[a])}}},t.prototype.setEnterable=function(t){this._enterable=t},t.prototype.getSize=function(){var t=this.el;return[t.clientWidth,t.clientHeight]},t.prototype.moveTo=function(t,e){var n=this._styleCoord;if(Oz(n,this._zr,this._appendToBody,t,e),null!=n[0]&&null!=n[1]){var i=this.el.style;P(kz(n[0],n[1]),(function(t){i[t[0]]=t[1]}))}},t.prototype._moveIfResized=function(){var t=this._styleCoord[2],e=this._styleCoord[3];this.moveTo(t*this._zr.getWidth(),e*this._zr.getHeight())},t.prototype.hide=function(){var t=this,e=this.el.style;e.visibility=\"hidden\",e.opacity=\"0\",a.transform3dSupported&&(e.willChange=\"\"),this._show=!1,this._longHideTimeout=setTimeout((function(){return t._longHide=!0}),500)},t.prototype.hideLater=function(t){!this._show||this._inContent&&this._enterable||(t?(this._hideDelay=t,this._show=!1,this._hideTimeout=setTimeout(V(this.hide,this),t)):this.hide())},t.prototype.isShow=function(){return this._show},t.prototype.dispose=function(){this.el.parentNode.removeChild(this.el)},t.prototype.getOuterSize=function(){var t=this.el.clientWidth,e=this.el.clientHeight,n=Cz(this.el);return n&&(t+=parseInt(n.borderLeftWidth,10)+parseInt(n.borderRightWidth,10),e+=parseInt(n.borderTopWidth,10)+parseInt(n.borderBottomWidth,10)),{width:t,height:e}},t}(),Nz=function(){function t(t){this._show=!1,this._styleCoord=[0,0,0,0],this._enterable=!0,this._zr=t.getZr(),Vz(this._styleCoord,this._zr,t.getWidth()/2,t.getHeight()/2)}return t.prototype.update=function(t){t.get(\"alwaysShowContent\")&&this._moveIfResized()},t.prototype.show=function(){this._hideTimeout&&clearTimeout(this._hideTimeout),this.el.show(),this._show=!0},t.prototype.setContent=function(t,e,n,i,r){X(t)&&vr(\"\"),this.el&&this._zr.remove(this.el);var o=n.getModel(\"textStyle\");this.el=new cs({style:{rich:e.richTextStyles,text:t,lineHeight:22,backgroundColor:n.get(\"backgroundColor\"),borderRadius:n.get(\"borderRadius\"),borderWidth:1,borderColor:i,shadowColor:n.get(\"shadowColor\"),shadowBlur:n.get(\"shadowBlur\"),shadowOffsetX:n.get(\"shadowOffsetX\"),shadowOffsetY:n.get(\"shadowOffsetY\"),textShadowColor:o.get(\"textShadowColor\"),textShadowBlur:o.get(\"textShadowBlur\")||0,textShadowOffsetX:o.get(\"textShadowOffsetX\")||0,textShadowOffsetY:o.get(\"textShadowOffsetY\")||0,fill:n.get([\"textStyle\",\"color\"]),padding:uf(n,\"richText\"),verticalAlign:\"top\",align:\"left\"},z:n.get(\"z\")}),this._zr.add(this.el);var a=this;this.el.on(\"mouseover\",(function(){a._enterable&&(clearTimeout(a._hideTimeout),a._show=!0),a._inContent=!0})),this.el.on(\"mouseout\",(function(){a._enterable&&a._show&&a.hideLater(a._hideDelay),a._inContent=!1}))},t.prototype.setEnterable=function(t){this._enterable=t},t.prototype.getSize=function(){var t=this.el,e=this.el.getBoundingRect(),n=Ez(t.style);return[e.width+n.left+n.right,e.height+n.top+n.bottom]},t.prototype.moveTo=function(t,e){var n=this.el;if(n){var i=this._styleCoord;Vz(i,this._zr,t,e),t=i[0],e=i[1];var r=n.style,o=zz(r.borderWidth||0),a=Ez(r);n.x=t+o+a.left,n.y=e+o+a.top,n.markRedraw()}},t.prototype._moveIfResized=function(){var t=this._styleCoord[2],e=this._styleCoord[3];this.moveTo(t*this._zr.getWidth(),e*this._zr.getHeight())},t.prototype.hide=function(){this.el&&this.el.hide(),this._show=!1},t.prototype.hideLater=function(t){!this._show||this._inContent&&this._enterable||(t?(this._hideDelay=t,this._show=!1,this._hideTimeout=setTimeout(V(this.hide,this),t)):this.hide())},t.prototype.isShow=function(){return this._show},t.prototype.getOuterSize=function(){var t=this.getSize();return{width:t[0],height:t[1]}},t.prototype.dispose=function(){this._zr.remove(this.el)},t}();function zz(t){return Math.max(0,t)}function Ez(t){var e=zz(t.shadowBlur||0),n=zz(t.shadowOffsetX||0),i=zz(t.shadowOffsetY||0);return{left:zz(e-n),right:zz(e+n),top:zz(e-i),bottom:zz(e+i)}}function Vz(t,e,n,i){t[0]=n,t[1]=i,t[2]=t[0]/e.getWidth(),t[3]=t[1]/e.getHeight()}var Bz=V,Fz=P,Gz=Zi,Hz=new ls({shape:{x:-1,y:-1,width:2,height:2}}),Wz=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){if(!a.node){var n,i=t.getComponent(\"tooltip\"),r=i.get(\"renderMode\");this._renderMode=\"auto\"===(n=r)?a.domSupported?\"html\":\"richText\":n||\"html\",this._tooltipContent=\"richText\"===this._renderMode?new Nz(e):new Rz(e.getDom(),e,{appendToBody:i.get(\"appendToBody\",!0)})}},e.prototype.render=function(t,e,n){if(!a.node){this.group.removeAll(),this._tooltipModel=t,this._ecModel=e,this._api=n,this._alwaysShowContent=t.get(\"alwaysShowContent\");var i=this._tooltipContent;i.update(t),i.setEnterable(t.get(\"enterable\")),this._initGlobalListener(),this._keepShow()}},e.prototype._initGlobalListener=function(){var t=this._tooltipModel.get(\"triggerOn\");ZO(\"itemTooltip\",this._api,Bz((function(e,n,i){\"none\"!==t&&(t.indexOf(e)>=0?this._tryShow(n,i):\"leave\"===e&&this._hide(i))}),this))},e.prototype._keepShow=function(){var t=this._tooltipModel,e=this._ecModel,n=this._api;if(null!=this._lastX&&null!=this._lastY&&\"none\"!==t.get(\"triggerOn\")){var i=this;clearTimeout(this._refreshUpdateTimeout),this._refreshUpdateTimeout=setTimeout((function(){!n.isDisposed()&&i.manuallyShowTip(t,e,n,{x:i._lastX,y:i._lastY,dataByCoordSys:i._lastDataByCoordSys})}))}},e.prototype.manuallyShowTip=function(t,e,n,i){if(i.from!==this.uid&&!a.node){var r=Xz(i,n);this._ticket=\"\";var o=i.dataByCoordSys,s=function(t,e,n){var i=Rr(t).queryOptionMap,r=i.keys()[0];if(!r||\"series\"===r)return;var o,a=Er(e,r,i.get(r),{useDefault:!1,enableAll:!1,enableNone:!1}).models[0];if(!a)return;if(n.getViewOfComponentModel(a).group.traverse((function(e){var n=_s(e).tooltipConfig;if(n&&n.name===t.name)return o=e,!0})),o)return{componentMainType:r,componentIndex:a.componentIndex,el:o}}(i,e,n);if(s){var l=s.el.getBoundingRect().clone();l.applyTransform(s.el.transform),this._tryShow({offsetX:l.x+l.width/2,offsetY:l.y+l.height/2,target:s.el,position:i.position,positionDefault:\"bottom\"},r)}else if(i.tooltip&&null!=i.x&&null!=i.y){var u=Hz;u.x=i.x,u.y=i.y,u.update(),_s(u).tooltipConfig={name:null,option:i.tooltip},this._tryShow({offsetX:i.x,offsetY:i.y,target:u},r)}else if(o)this._tryShow({offsetX:i.x,offsetY:i.y,position:i.position,dataByCoordSys:o,tooltipOption:i.tooltipOption},r);else if(null!=i.seriesIndex){if(this._manuallyAxisShowTip(t,e,n,i))return;var h=JO(i,e),c=h.point[0],p=h.point[1];null!=c&&null!=p&&this._tryShow({offsetX:c,offsetY:p,target:h.el,position:i.position,positionDefault:\"bottom\"},r)}else null!=i.x&&null!=i.y&&(n.dispatchAction({type:\"updateAxisPointer\",x:i.x,y:i.y}),this._tryShow({offsetX:i.x,offsetY:i.y,position:i.position,target:n.getZr().findHover(i.x,i.y).target},r))}},e.prototype.manuallyHideTip=function(t,e,n,i){var r=this._tooltipContent;!this._alwaysShowContent&&this._tooltipModel&&r.hideLater(this._tooltipModel.get(\"hideDelay\")),this._lastX=this._lastY=this._lastDataByCoordSys=null,i.from!==this.uid&&this._hide(Xz(i,n))},e.prototype._manuallyAxisShowTip=function(t,e,n,i){var r=i.seriesIndex,o=i.dataIndex,a=e.getComponent(\"axisPointer\").coordSysAxesInfo;if(null!=r&&null!=o&&null!=a){var s=e.getSeriesByIndex(r);if(s)if(\"axis\"===Uz([s.getData().getItemModel(o),s,(s.coordinateSystem||{}).model],this._tooltipModel).get(\"trigger\"))return n.dispatchAction({type:\"updateAxisPointer\",seriesIndex:r,dataIndex:o,position:i.position}),!0}},e.prototype._tryShow=function(t,e){var n=t.target;if(this._tooltipModel){this._lastX=t.offsetX,this._lastY=t.offsetY;var i=t.dataByCoordSys;if(i&&i.length)this._showAxisTooltip(i,t);else if(n){var r,o;this._lastDataByCoordSys=null,iy(n,(function(t){return null!=_s(t).dataIndex?(r=t,!0):null!=_s(t).tooltipConfig?(o=t,!0):void 0}),!0),r?this._showSeriesItemTooltip(t,r,e):o?this._showComponentItemTooltip(t,o,e):this._hide(e)}else this._lastDataByCoordSys=null,this._hide(e)}},e.prototype._showOrMove=function(t,e){var n=t.get(\"showDelay\");e=V(e,this),clearTimeout(this._showTimout),n>0?this._showTimout=setTimeout(e,n):e()},e.prototype._showAxisTooltip=function(t,e){var n=this._ecModel,i=this._tooltipModel,r=[e.offsetX,e.offsetY],o=Uz([e.tooltipOption],i),a=this._renderMode,s=[],l=tf(\"section\",{blocks:[],noHeader:!0}),u=[],h=new hf;Fz(t,(function(t){Fz(t.dataByAxis,(function(t){var e=n.getComponent(t.axisDim+\"Axis\",t.axisIndex),i=t.value;if(e&&null!=i){var r=RO(i,e.axis,n,t.seriesDataIndices,t.valueLabelOpt),o=tf(\"section\",{header:r,noHeader:!ot(r),sortBlocks:!0,blocks:[]});l.blocks.push(o),P(t.seriesDataIndices,(function(l){var c=n.getSeriesByIndex(l.seriesIndex),p=l.dataIndexInside,d=c.getDataParams(p);d.axisDim=t.axisDim,d.axisIndex=t.axisIndex,d.axisType=t.axisType,d.axisId=t.axisId,d.axisValue=Gx(e.axis,{value:i}),d.axisValueLabel=r,d.marker=h.makeTooltipMarker(\"item\",kc(d.color),a);var f=Cd(c.formatTooltip(p,!0,null));f.markupFragment&&o.blocks.push(f.markupFragment),f.markupText&&u.push(f.markupText),s.push(d)}))}}))})),l.blocks.reverse(),u.reverse();var c=e.position,p=o.get(\"order\"),d=rf(l,h,a,p,n.get(\"useUTC\"),o.get(\"textStyle\"));d&&u.unshift(d);var f=\"richText\"===a?\"\\n\\n\":\"<br/>\",g=u.join(f);this._showOrMove(o,(function(){this._updateContentNotChangedOnAxis(t)?this._updatePosition(o,c,r[0],r[1],this._tooltipContent,s):this._showTooltipContent(o,g,s,Math.random()+\"\",r[0],r[1],c,null,h)}))},e.prototype._showSeriesItemTooltip=function(t,e,n){var i=this._ecModel,r=_s(e),o=r.seriesIndex,a=i.getSeriesByIndex(o),s=r.dataModel||a,l=r.dataIndex,u=r.dataType,h=s.getData(u),c=this._renderMode,p=t.positionDefault,d=Uz([h.getItemModel(l),s,a&&(a.coordinateSystem||{}).model],this._tooltipModel,p?{position:p}:null),f=d.get(\"trigger\");if(null==f||\"item\"===f){var g=s.getDataParams(l,u),y=new hf;g.marker=y.makeTooltipMarker(\"item\",kc(g.color),c);var v=Cd(s.formatTooltip(l,!1,u)),m=d.get(\"order\"),_=v.markupFragment?rf(v.markupFragment,y,c,m,i.get(\"useUTC\"),d.get(\"textStyle\")):v.markupText,x=\"item_\"+s.name+\"_\"+l;this._showOrMove(d,(function(){this._showTooltipContent(d,_,g,x,t.offsetX,t.offsetY,t.position,t.target,y)})),n({type:\"showTip\",dataIndexInside:l,dataIndex:h.getRawIndex(l),seriesIndex:o,from:this.uid})}},e.prototype._showComponentItemTooltip=function(t,e,n){var i=_s(e),r=i.tooltipConfig.option||{};if(H(r)){r={content:r,formatter:r}}var o=[r],a=this._ecModel.getComponent(i.componentMainType,i.componentIndex);a&&o.push(a),o.push({formatter:r.content});var s=t.positionDefault,l=Uz(o,this._tooltipModel,s?{position:s}:null),u=l.get(\"content\"),h=Math.random()+\"\",c=new hf;this._showOrMove(l,(function(){var n=w(l.get(\"formatterParams\")||{});this._showTooltipContent(l,u,n,h,t.offsetX,t.offsetY,t.position,e,c)})),n({type:\"showTip\",from:this.uid})},e.prototype._showTooltipContent=function(t,e,n,i,r,o,a,s,l){if(this._ticket=\"\",t.get(\"showContent\")&&t.get(\"show\")){var u=this._tooltipContent,h=t.get(\"formatter\");a=a||t.get(\"position\");var c=e,p=this._getNearestPoint([r,o],n,t.get(\"trigger\"),t.get(\"borderColor\")).color;if(h&&H(h)){var d=t.ecModel.get(\"useUTC\"),f=F(n)?n[0]:n;c=h,f&&f.axisType&&f.axisType.indexOf(\"time\")>=0&&(c=ic(f.axisValue,c,d)),c=Ac(c,n,!0)}else if(G(h)){var g=Bz((function(e,i){e===this._ticket&&(u.setContent(i,l,t,p,a),this._updatePosition(t,a,r,o,u,n,s))}),this);this._ticket=i,c=h(n,i,g)}u.setContent(c,l,t,p,a),u.show(t,p),this._updatePosition(t,a,r,o,u,n,s)}},e.prototype._getNearestPoint=function(t,e,n,i){return\"axis\"===n||F(e)?{color:i||(\"html\"===this._renderMode?\"#fff\":\"none\")}:F(e)?void 0:{color:i||e.color||e.borderColor}},e.prototype._updatePosition=function(t,e,n,i,r,o,a){var s=this._api.getWidth(),l=this._api.getHeight();e=e||t.get(\"position\");var u=r.getSize(),h=t.get(\"align\"),c=t.get(\"verticalAlign\"),p=a&&a.getBoundingRect().clone();if(a&&p.applyTransform(a.transform),G(e)&&(e=e([n,i],o,r.el,p,{viewSize:[s,l],contentSize:u.slice()})),F(e))n=Gz(e[0],s),i=Gz(e[1],l);else if(X(e)){var d=e;d.width=u[0],d.height=u[1];var f=Vc(d,{width:s,height:l});n=f.x,i=f.y,h=null,c=null}else if(H(e)&&a){var g=function(t,e,n){var i=n[0],r=n[1],o=10,a=5,s=0,l=0,u=e.width,h=e.height;switch(t){case\"inside\":s=e.x+u/2-i/2,l=e.y+h/2-r/2;break;case\"top\":s=e.x+u/2-i/2,l=e.y-r-o;break;case\"bottom\":s=e.x+u/2-i/2,l=e.y+h+o;break;case\"left\":s=e.x-i-o-a,l=e.y+h/2-r/2;break;case\"right\":s=e.x+u+o+a,l=e.y+h/2-r/2}return[s,l]}(e,p,u);n=g[0],i=g[1]}else{g=function(t,e,n,i,r,o,a){var s=n.getOuterSize(),l=s.width,u=s.height;null!=o&&(t+l+o+2>i?t-=l+o:t+=o);null!=a&&(e+u+a>r?e-=u+a:e+=a);return[t,e]}(n,i,r,s,l,h?null:20,c?null:20);n=g[0],i=g[1]}if(h&&(n-=Yz(h)?u[0]/2:\"right\"===h?u[0]:0),c&&(i-=Yz(c)?u[1]/2:\"bottom\"===c?u[1]:0),Sz(t)){g=function(t,e,n,i,r){var o=n.getOuterSize(),a=o.width,s=o.height;return t=Math.min(t+a,i)-a,e=Math.min(e+s,r)-s,t=Math.max(t,0),e=Math.max(e,0),[t,e]}(n,i,r,s,l);n=g[0],i=g[1]}r.moveTo(n,i)},e.prototype._updateContentNotChangedOnAxis=function(t){var e=this._lastDataByCoordSys,n=!!e&&e.length===t.length;return n&&Fz(e,(function(e,i){var r=e.dataByAxis||[],o=(t[i]||{}).dataByAxis||[];(n=n&&r.length===o.length)&&Fz(r,(function(t,e){var i=o[e]||{},r=t.seriesDataIndices||[],a=i.seriesDataIndices||[];(n=n&&t.value===i.value&&t.axisType===i.axisType&&t.axisId===i.axisId&&r.length===a.length)&&Fz(r,(function(t,e){var i=a[e];n=n&&t.seriesIndex===i.seriesIndex&&t.dataIndex===i.dataIndex}))}))})),this._lastDataByCoordSys=t,!!n},e.prototype._hide=function(t){this._lastDataByCoordSys=null,t({type:\"hideTip\",from:this.uid})},e.prototype.dispose=function(t,e){a.node||(this._tooltipContent.dispose(),KO(\"itemTooltip\",e))},e.type=\"tooltip\",e}(wf);function Uz(t,e,n){var i,r=e.ecModel;n?(i=new Oh(n,r,r),i=new Oh(e.option,i,r)):i=e;for(var o=t.length-1;o>=0;o--){var a=t[o];a&&(a instanceof Oh&&(a=a.get(\"tooltip\",!0)),H(a)&&(a={formatter:a}),a&&(i=new Oh(a,i,r)))}return i}function Xz(t,e){return t.dispatchAction||V(e.dispatchAction,e)}function Yz(t){return\"center\"===t||\"middle\"===t}var Zz=[\"rect\",\"polygon\",\"keep\",\"clear\"];function jz(t,e){var n=xr(t?t.brush:[]);if(n.length){var i=[];P(n,(function(t){var e=t.hasOwnProperty(\"toolbox\")?t.toolbox:[];e instanceof Array&&(i=i.concat(e))}));var r=t&&t.toolbox;F(r)&&(r=r[0]),r||(r={feature:{}},t.toolbox=[r]);var o=r.feature||(r.feature={}),a=o.brush||(o.brush={}),s=a.type||(a.type=[]);s.push.apply(s,i),function(t){var e={};P(t,(function(t){e[t]=1})),t.length=0,P(e,(function(e,n){t.push(n)}))}(s),e&&!s.length&&s.push.apply(s,Zz)}}var qz=P;function Kz(t){if(t)for(var e in t)if(t.hasOwnProperty(e))return!0}function $z(t,e,n){var i={};return qz(e,(function(e){var r,o=i[e]=((r=function(){}).prototype.__hidden=r.prototype,new r);qz(t[e],(function(t,i){if(TT.isValidType(i)){var r={type:i,visual:t};n&&n(r,e),o[i]=new TT(r),\"opacity\"===i&&((r=w(r)).type=\"colorAlpha\",o.__hidden.__alphaForOpacity=new TT(r))}}))})),i}function Jz(t,e,n){var i;P(n,(function(t){e.hasOwnProperty(t)&&Kz(e[t])&&(i=!0)})),i&&P(n,(function(n){e.hasOwnProperty(n)&&Kz(e[n])?t[n]=w(e[n]):delete t[n]}))}var Qz={lineX:tE(0),lineY:tE(1),rect:{point:function(t,e,n){return t&&n.boundingRect.contain(t[0],t[1])},rect:function(t,e,n){return t&&n.boundingRect.intersect(t)}},polygon:{point:function(t,e,n){return t&&n.boundingRect.contain(t[0],t[1])&&uv(n.range,t[0],t[1])},rect:function(t,e,n){var i=n.range;if(!t||i.length<=1)return!1;var r=t.x,o=t.y,a=t.width,s=t.height,l=i[0];return!!(uv(i,r,o)||uv(i,r+a,o)||uv(i,r,o+s)||uv(i,r+a,o+s)||gi.create(t).contain(l[0],l[1])||nh(r,o,r+a,o,i)||nh(r,o,r,o+s,i)||nh(r+a,o,r+a,o+s,i)||nh(r,o+s,r+a,o+s,i))||void 0}}};function tE(t){var e=[\"x\",\"y\"],n=[\"width\",\"height\"];return{point:function(e,n,i){if(e){var r=i.range;return eE(e[t],r)}},rect:function(i,r,o){if(i){var a=o.range,s=[i[e[t]],i[e[t]]+i[n[t]]];return s[1]<s[0]&&s.reverse(),eE(s[0],a)||eE(s[1],a)||eE(a[0],s)||eE(a[1],s)}}}}function eE(t,e){return e[0]<=t&&t<=e[1]}var nE=[\"inBrush\",\"outOfBrush\"],iE=\"__ecBrushSelect\",rE=\"__ecInBrushSelectEvent\";function oE(t){t.eachComponent({mainType:\"brush\"},(function(e){(e.brushTargetManager=new rz(e.option,t)).setInputRanges(e.areas,t)}))}function aE(t,e,n){var i,r,o=[];t.eachComponent({mainType:\"brush\"},(function(t){n&&\"takeGlobalCursor\"===n.type&&t.setBrushOption(\"brush\"===n.key?n.brushOption:{brushType:!1})})),oE(t),t.eachComponent({mainType:\"brush\"},(function(e,n){var a={brushId:e.id,brushIndex:n,brushName:e.name,areas:w(e.areas),selected:[]};o.push(a);var s=e.option,l=s.brushLink,u=[],h=[],c=[],p=!1;n||(i=s.throttleType,r=s.throttleDelay);var d=O(e.areas,(function(t){var e=uE[t.brushType],n=T({boundingRect:e?e(t):void 0},t);return n.selectors=function(t){var e=t.brushType,n={point:function(i){return Qz[e].point(i,n,t)},rect:function(i){return Qz[e].rect(i,n,t)}};return n}(n),n})),f=$z(e.option,nE,(function(t){t.mappingMethod=\"fixed\"}));function g(t){return\"all\"===l||!!u[t]}function y(t){return!!t.length}F(l)&&P(l,(function(t){u[t]=1})),t.eachSeries((function(n,i){var r=c[i]=[];\"parallel\"===n.subType?function(t,e){var n=t.coordinateSystem;p=p||n.hasAxisBrushed(),g(e)&&n.eachActiveState(t.getData(),(function(t,e){\"active\"===t&&(h[e]=1)}))}(n,i):function(n,i,r){if(!n.brushSelector||function(t,e){var n=t.option.seriesIndex;return null!=n&&\"all\"!==n&&(F(n)?D(n,e)<0:e!==n)}(e,i))return;if(P(d,(function(i){e.brushTargetManager.controlSeries(i,n,t)&&r.push(i),p=p||y(r)})),g(i)&&y(r)){var o=n.getData();o.each((function(t){lE(n,r,o,t)&&(h[t]=1)}))}}(n,i,r)})),t.eachSeries((function(t,e){var n={seriesId:t.id,seriesIndex:e,seriesName:t.name,dataIndex:[]};a.selected.push(n);var i=c[e],r=t.getData(),o=g(e)?function(t){return h[t]?(n.dataIndex.push(r.getRawIndex(t)),\"inBrush\"):\"outOfBrush\"}:function(e){return lE(t,i,r,e)?(n.dataIndex.push(r.getRawIndex(e)),\"inBrush\"):\"outOfBrush\"};(g(e)?p:y(i))&&function(t,e,n,i,r,o){var a,s={};function l(t){return vg(n,a,t)}function u(t,e){_g(n,a,t,e)}function h(t,h){a=null==o?t:h;var c=n.getRawDataItem(a);if(!c||!1!==c.visualMap)for(var p=i.call(r,t),d=e[p],f=s[p],g=0,y=f.length;g<y;g++){var v=f[g];d[v]&&d[v].applyVisual(t,l,u)}}P(t,(function(t){var n=TT.prepareVisualTypes(e[t]);s[t]=n})),null==o?n.each(h):n.each([o],h)}(nE,f,r,o)}))})),function(t,e,n,i,r){if(!r)return;var o=t.getZr();if(o[rE])return;o.__ecBrushSelect||(o.__ecBrushSelect=sE);zf(o,iE,n,e)(t,i)}(e,i,r,o,n)}function sE(t,e){if(!t.isDisposed()){var n=t.getZr();n[rE]=!0,t.dispatchAction({type:\"brushSelect\",batch:e}),n[rE]=!1}}function lE(t,e,n,i){for(var r=0,o=e.length;r<o;r++){var a=e[r];if(t.brushSelector(i,n,a.selectors,a))return!0}}var uE={rect:function(t){return hE(t.range)},polygon:function(t){for(var e,n=t.range,i=0,r=n.length;i<r;i++){e=e||[[1/0,-1/0],[1/0,-1/0]];var o=n[i];o[0]<e[0][0]&&(e[0][0]=o[0]),o[0]>e[0][1]&&(e[0][1]=o[0]),o[1]<e[1][0]&&(e[1][0]=o[1]),o[1]>e[1][1]&&(e[1][1]=o[1])}return e&&hE(e)}};function hE(t){return new gi(t[0][0],t[1][0],t[0][1]-t[0][0],t[1][1]-t[1][0])}var cE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){this.ecModel=t,this.api=e,this.model,(this._brushController=new iA(e.getZr())).on(\"brush\",V(this._onBrush,this)).mount()},e.prototype.render=function(t,e,n,i){this.model=t,this._updateController(t,e,n,i)},e.prototype.updateTransform=function(t,e,n,i){oE(e),this._updateController(t,e,n,i)},e.prototype.updateVisual=function(t,e,n,i){this.updateTransform(t,e,n,i)},e.prototype.updateView=function(t,e,n,i){this._updateController(t,e,n,i)},e.prototype._updateController=function(t,e,n,i){(!i||i.$from!==t.id)&&this._brushController.setPanels(t.brushTargetManager.makePanelOpts(n)).enableBrush(t.brushOption).updateCovers(t.areas.slice())},e.prototype.dispose=function(){this._brushController.dispose()},e.prototype._onBrush=function(t){var e=this.model.id,n=this.model.brushTargetManager.setOutputRanges(t.areas,this.ecModel);(!t.isEnd||t.removeOnClick)&&this.api.dispatchAction({type:\"brush\",brushId:e,areas:w(n),$from:e}),t.isEnd&&this.api.dispatchAction({type:\"brushEnd\",brushId:e,areas:w(n),$from:e})},e.type=\"brush\",e}(wf),pE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.areas=[],n.brushOption={},n}return n(e,t),e.prototype.optionUpdated=function(t,e){var n=this.option;!e&&Jz(n,t,[\"inBrush\",\"outOfBrush\"]);var i=n.inBrush=n.inBrush||{};n.outOfBrush=n.outOfBrush||{color:\"#ddd\"},i.hasOwnProperty(\"liftZ\")||(i.liftZ=5)},e.prototype.setAreas=function(t){t&&(this.areas=O(t,(function(t){return dE(this.option,t)}),this))},e.prototype.setBrushOption=function(t){this.brushOption=dE(this.option,t),this.brushType=this.brushOption.brushType},e.type=\"brush\",e.dependencies=[\"geo\",\"grid\",\"xAxis\",\"yAxis\",\"parallel\",\"series\"],e.defaultOption={seriesIndex:\"all\",brushType:\"rect\",brushMode:\"single\",transformable:!0,brushStyle:{borderWidth:1,color:\"rgba(210,219,238,0.3)\",borderColor:\"#D2DBEE\"},throttleType:\"fixRate\",throttleDelay:0,removeOnClick:!0,z:1e4},e}(Xc);function dE(t,e){return S({brushType:t.brushType,brushMode:t.brushMode,transformable:t.transformable,brushStyle:new Oh(t.brushStyle).getItemStyle(),removeOnClick:t.removeOnClick,z:t.z},e,!0)}var fE=[\"rect\",\"polygon\",\"lineX\",\"lineY\",\"keep\",\"clear\"],gE=function(t){function e(){return null!==t&&t.apply(this,arguments)||this}return n(e,t),e.prototype.render=function(t,e,n){var i,r,o;e.eachComponent({mainType:\"brush\"},(function(t){i=t.brushType,r=t.brushOption.brushMode||\"single\",o=o||!!t.areas.length})),this._brushType=i,this._brushMode=r,P(t.get(\"type\",!0),(function(e){t.setIconStatus(e,(\"keep\"===e?\"multiple\"===r:\"clear\"===e?o:e===i)?\"emphasis\":\"normal\")}))},e.prototype.updateView=function(t,e,n){this.render(t,e,n)},e.prototype.getIcons=function(){var t=this.model,e=t.get(\"icon\",!0),n={};return P(t.get(\"type\",!0),(function(t){e[t]&&(n[t]=e[t])})),n},e.prototype.onclick=function(t,e,n){var i=this._brushType,r=this._brushMode;\"clear\"===n?(e.dispatchAction({type:\"axisAreaSelect\",intervals:[]}),e.dispatchAction({type:\"brush\",command:\"clear\",areas:[]})):e.dispatchAction({type:\"takeGlobalCursor\",key:\"brush\",brushOption:{brushType:\"keep\"===n?i:i!==n&&n,brushMode:\"keep\"===n?\"multiple\"===r?\"single\":\"multiple\":r}})},e.getDefaultOption=function(t){return{show:!0,type:fE.slice(),icon:{rect:\"M7.3,34.7 M0.4,10V-0.2h9.8 M89.6,10V-0.2h-9.8 M0.4,60v10.2h9.8 M89.6,60v10.2h-9.8 M12.3,22.4V10.5h13.1 M33.6,10.5h7.8 M49.1,10.5h7.8 M77.5,22.4V10.5h-13 M12.3,31.1v8.2 M77.7,31.1v8.2 M12.3,47.6v11.9h13.1 M33.6,59.5h7.6 M49.1,59.5 h7.7 M77.5,47.6v11.9h-13\",polygon:\"M55.2,34.9c1.7,0,3.1,1.4,3.1,3.1s-1.4,3.1-3.1,3.1 s-3.1-1.4-3.1-3.1S53.5,34.9,55.2,34.9z M50.4,51c1.7,0,3.1,1.4,3.1,3.1c0,1.7-1.4,3.1-3.1,3.1c-1.7,0-3.1-1.4-3.1-3.1 C47.3,52.4,48.7,51,50.4,51z M55.6,37.1l1.5-7.8 M60.1,13.5l1.6-8.7l-7.8,4 M59,19l-1,5.3 M24,16.1l6.4,4.9l6.4-3.3 M48.5,11.6 l-5.9,3.1 M19.1,12.8L9.7,5.1l1.1,7.7 M13.4,29.8l1,7.3l6.6,1.6 M11.6,18.4l1,6.1 M32.8,41.9 M26.6,40.4 M27.3,40.2l6.1,1.6 M49.9,52.1l-5.6-7.6l-4.9-1.2\",lineX:\"M15.2,30 M19.7,15.6V1.9H29 M34.8,1.9H40.4 M55.3,15.6V1.9H45.9 M19.7,44.4V58.1H29 M34.8,58.1H40.4 M55.3,44.4 V58.1H45.9 M12.5,20.3l-9.4,9.6l9.6,9.8 M3.1,29.9h16.5 M62.5,20.3l9.4,9.6L62.3,39.7 M71.9,29.9H55.4\",lineY:\"M38.8,7.7 M52.7,12h13.2v9 M65.9,26.6V32 M52.7,46.3h13.2v-9 M24.9,12H11.8v9 M11.8,26.6V32 M24.9,46.3H11.8v-9 M48.2,5.1l-9.3-9l-9.4,9.2 M38.9-3.9V12 M48.2,53.3l-9.3,9l-9.4-9.2 M38.9,62.3V46.4\",keep:\"M4,10.5V1h10.3 M20.7,1h6.1 M33,1h6.1 M55.4,10.5V1H45.2 M4,17.3v6.6 M55.6,17.3v6.6 M4,30.5V40h10.3 M20.7,40 h6.1 M33,40h6.1 M55.4,30.5V40H45.2 M21,18.9h62.9v48.6H21V18.9z\",clear:\"M22,14.7l30.9,31 M52.9,14.7L22,45.7 M4.7,16.8V4.2h13.1 M26,4.2h7.8 M41.6,4.2h7.8 M70.3,16.8V4.2H57.2 M4.7,25.9v8.6 M70.3,25.9v8.6 M4.7,43.2v12.6h13.1 M26,55.8h7.8 M41.6,55.8h7.8 M70.3,43.2v12.6H57.2\"},title:t.getLocale([\"toolbox\",\"brush\",\"title\"])}},e}(RN);var yE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode={type:\"box\",ignoreSize:!0},n}return n(e,t),e.type=\"title\",e.defaultOption={zlevel:0,z:6,show:!0,text:\"\",target:\"blank\",subtext:\"\",subtarget:\"blank\",left:0,top:0,backgroundColor:\"rgba(0,0,0,0)\",borderColor:\"#ccc\",borderWidth:0,padding:5,itemGap:10,textStyle:{fontSize:18,fontWeight:\"bold\",color:\"#464646\"},subtextStyle:{fontSize:12,color:\"#6E7079\"}},e}(Xc),vE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.render=function(t,e,n){if(this.group.removeAll(),t.get(\"show\")){var i=this.group,r=t.getModel(\"textStyle\"),o=t.getModel(\"subtextStyle\"),a=t.get(\"textAlign\"),s=tt(t.get(\"textBaseline\"),t.get(\"textVerticalAlign\")),l=new cs({style:ph(r,{text:t.get(\"text\"),fill:r.getTextColor()},{disableBox:!0}),z2:10}),u=l.getBoundingRect(),h=t.get(\"subtext\"),c=new cs({style:ph(o,{text:h,fill:o.getTextColor(),y:u.height+t.get(\"itemGap\"),verticalAlign:\"top\"},{disableBox:!0}),z2:10}),p=t.get(\"link\"),d=t.get(\"sublink\"),f=t.get(\"triggerEvent\",!0);l.silent=!p&&!f,c.silent=!d&&!f,p&&l.on(\"click\",(function(){Pc(p,\"_\"+t.get(\"target\"))})),d&&c.on(\"click\",(function(){Pc(d,\"_\"+t.get(\"subtarget\"))})),_s(l).eventData=_s(c).eventData=f?{componentType:\"title\",componentIndex:t.componentIndex}:null,i.add(l),h&&i.add(c);var g=i.getBoundingRect(),y=t.getBoxLayoutParams();y.width=g.width,y.height=g.height;var v=Vc(y,{width:n.getWidth(),height:n.getHeight()},t.get(\"padding\"));a||(\"middle\"===(a=t.get(\"left\")||t.get(\"right\"))&&(a=\"center\"),\"right\"===a?v.x+=v.width:\"center\"===a&&(v.x+=v.width/2)),s||(\"center\"===(s=t.get(\"top\")||t.get(\"bottom\"))&&(s=\"middle\"),\"bottom\"===s?v.y+=v.height:\"middle\"===s&&(v.y+=v.height/2),s=s||\"top\"),i.x=v.x,i.y=v.y,i.markRedraw();var m={align:a,verticalAlign:s};l.setStyle(m),c.setStyle(m),g=i.getBoundingRect();var _=v.margin,x=t.getItemStyle([\"color\",\"opacity\"]);x.fill=t.get(\"backgroundColor\");var b=new ls({shape:{x:g.x-_[3],y:g.y-_[0],width:g.width+_[1]+_[3],height:g.height+_[0]+_[2],r:t.get(\"borderRadius\")},style:x,subPixelOptimize:!0,silent:!0});i.add(b)}},e.type=\"title\",e}(wf);var mE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode=\"box\",n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n),this._initData()},e.prototype.mergeOption=function(e){t.prototype.mergeOption.apply(this,arguments),this._initData()},e.prototype.setCurrentIndex=function(t){null==t&&(t=this.option.currentIndex);var e=this._data.count();this.option.loop?t=(t%e+e)%e:(t>=e&&(t=e-1),t<0&&(t=0)),this.option.currentIndex=t},e.prototype.getCurrentIndex=function(){return this.option.currentIndex},e.prototype.isIndexMax=function(){return this.getCurrentIndex()>=this._data.count()-1},e.prototype.setPlayState=function(t){this.option.autoPlay=!!t},e.prototype.getPlayState=function(){return!!this.option.autoPlay},e.prototype._initData=function(){var t,e=this.option,n=e.data||[],i=e.axisType,r=this._names=[];\"category\"===i?(t=[],P(n,(function(e,n){var i,o=Cr(Sr(e),\"\");X(e)?(i=w(e)).value=n:i=n,t.push(i),r.push(o)}))):t=n;var o={category:\"ordinal\",time:\"time\",value:\"number\"}[i]||\"number\";(this._data=new L_([{name:\"value\",type:o}],this)).initData(t,r)},e.prototype.getData=function(){return this._data},e.prototype.getCategories=function(){if(\"category\"===this.get(\"axisType\"))return this._names.slice()},e.type=\"timeline\",e.defaultOption={zlevel:0,z:4,show:!0,axisType:\"time\",realtime:!0,left:\"20%\",top:null,right:\"20%\",bottom:0,width:null,height:40,padding:5,controlPosition:\"left\",autoPlay:!1,rewind:!1,loop:!0,playInterval:2e3,currentIndex:0,itemStyle:{},label:{color:\"#000\"},data:[]},e}(Xc),_E=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"timeline.slider\",e.defaultOption=zh(mE.defaultOption,{backgroundColor:\"rgba(0,0,0,0)\",borderColor:\"#ccc\",borderWidth:0,orient:\"horizontal\",inverse:!1,tooltip:{trigger:\"item\"},symbol:\"circle\",symbolSize:12,lineStyle:{show:!0,width:2,color:\"#DAE1F5\"},label:{position:\"auto\",show:!0,interval:\"auto\",rotate:0,color:\"#A4B1D7\"},itemStyle:{color:\"#A4B1D7\",borderWidth:1},checkpointStyle:{symbol:\"circle\",symbolSize:15,color:\"#316bf3\",borderColor:\"#fff\",borderWidth:2,shadowBlur:2,shadowOffsetX:1,shadowOffsetY:1,shadowColor:\"rgba(0, 0, 0, 0.3)\",animation:!0,animationDuration:300,animationEasing:\"quinticInOut\"},controlStyle:{show:!0,showPlayBtn:!0,showPrevBtn:!0,showNextBtn:!0,itemSize:24,itemGap:12,position:\"left\",playIcon:\"path://M31.6,53C17.5,53,6,41.5,6,27.4S17.5,1.8,31.6,1.8C45.7,1.8,57.2,13.3,57.2,27.4S45.7,53,31.6,53z M31.6,3.3 C18.4,3.3,7.5,14.1,7.5,27.4c0,13.3,10.8,24.1,24.1,24.1C44.9,51.5,55.7,40.7,55.7,27.4C55.7,14.1,44.9,3.3,31.6,3.3z M24.9,21.3 c0-2.2,1.6-3.1,3.5-2l10.5,6.1c1.899,1.1,1.899,2.9,0,4l-10.5,6.1c-1.9,1.1-3.5,0.2-3.5-2V21.3z\",stopIcon:\"path://M30.9,53.2C16.8,53.2,5.3,41.7,5.3,27.6S16.8,2,30.9,2C45,2,56.4,13.5,56.4,27.6S45,53.2,30.9,53.2z M30.9,3.5C17.6,3.5,6.8,14.4,6.8,27.6c0,13.3,10.8,24.1,24.101,24.1C44.2,51.7,55,40.9,55,27.6C54.9,14.4,44.1,3.5,30.9,3.5z M36.9,35.8c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H36c0.5,0,0.9,0.4,0.9,1V35.8z M27.8,35.8 c0,0.601-0.4,1-0.9,1h-1.3c-0.5,0-0.9-0.399-0.9-1V19.5c0-0.6,0.4-1,0.9-1H27c0.5,0,0.9,0.4,0.9,1L27.8,35.8L27.8,35.8z\",nextIcon:\"M2,18.5A1.52,1.52,0,0,1,.92,18a1.49,1.49,0,0,1,0-2.12L7.81,9.36,1,3.11A1.5,1.5,0,1,1,3,.89l8,7.34a1.48,1.48,0,0,1,.49,1.09,1.51,1.51,0,0,1-.46,1.1L3,18.08A1.5,1.5,0,0,1,2,18.5Z\",prevIcon:\"M10,.5A1.52,1.52,0,0,1,11.08,1a1.49,1.49,0,0,1,0,2.12L4.19,9.64,11,15.89a1.5,1.5,0,1,1-2,2.22L1,10.77A1.48,1.48,0,0,1,.5,9.68,1.51,1.51,0,0,1,1,8.58L9,.92A1.5,1.5,0,0,1,10,.5Z\",prevBtnSize:18,nextBtnSize:18,color:\"#A4B1D7\",borderColor:\"#A4B1D7\",borderWidth:1},emphasis:{label:{show:!0,color:\"#6f778d\"},itemStyle:{color:\"#316BF3\"},controlStyle:{color:\"#316BF3\",borderColor:\"#316BF3\",borderWidth:2}},progress:{lineStyle:{color:\"#316BF3\"},itemStyle:{color:\"#316BF3\"},label:{color:\"#6f778d\"}},data:[]}),e}(mE);L(_E,Td.prototype);var xE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"timeline\",e}(wf),bE=function(t){function e(e,n,i,r){var o=t.call(this,e,n,i)||this;return o.type=r||\"value\",o}return n(e,t),e.prototype.getLabelModel=function(){return this.model.getModel(\"label\")},e.prototype.isHorizontal=function(){return\"horizontal\"===this.model.get(\"orient\")},e}(hb),wE=Math.PI,SE=kr(),ME=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(t,e){this.api=e},e.prototype.render=function(t,e,n){if(this.model=t,this.api=n,this.ecModel=e,this.group.removeAll(),t.get(\"show\",!0)){var i=this._layout(t,n),r=this._createGroup(\"_mainGroup\"),o=this._createGroup(\"_labelGroup\"),a=this._axis=this._createAxis(i,t);t.formatTooltip=function(t){return tf(\"nameValue\",{noName:!0,value:a.scale.getLabel({value:t})})},P([\"AxisLine\",\"AxisTick\",\"Control\",\"CurrentPointer\"],(function(e){this[\"_render\"+e](i,r,a,t)}),this),this._renderAxisLabel(i,o,a,t),this._position(i,t)}this._doPlayStop(),this._updateTicksStatus()},e.prototype.remove=function(){this._clearTimer(),this.group.removeAll()},e.prototype.dispose=function(){this._clearTimer()},e.prototype._layout=function(t,e){var n,i,r,o,a=t.get([\"label\",\"position\"]),s=t.get(\"orient\"),l=function(t,e){return Vc(t.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()},t.get(\"padding\"))}(t,e),u={horizontal:\"center\",vertical:(n=null==a||\"auto\"===a?\"horizontal\"===s?l.y+l.height/2<e.getHeight()/2?\"-\":\"+\":l.x+l.width/2<e.getWidth()/2?\"+\":\"-\":H(a)?{horizontal:{top:\"-\",bottom:\"+\"},vertical:{left:\"-\",right:\"+\"}}[s][a]:a)>=0||\"+\"===n?\"left\":\"right\"},h={horizontal:n>=0||\"+\"===n?\"top\":\"bottom\",vertical:\"middle\"},c={horizontal:0,vertical:wE/2},p=\"vertical\"===s?l.height:l.width,d=t.getModel(\"controlStyle\"),f=d.get(\"show\",!0),g=f?d.get(\"itemSize\"):0,y=f?d.get(\"itemGap\"):0,v=g+y,m=t.get([\"label\",\"rotate\"])||0;m=m*wE/180;var _=d.get(\"position\",!0),x=f&&d.get(\"showPlayBtn\",!0),b=f&&d.get(\"showPrevBtn\",!0),w=f&&d.get(\"showNextBtn\",!0),S=0,M=p;\"left\"===_||\"bottom\"===_?(x&&(i=[0,0],S+=v),b&&(r=[S,0],S+=v),w&&(o=[M-g,0],M-=v)):(x&&(i=[M-g,0],M-=v),b&&(r=[0,0],S+=v),w&&(o=[M-g,0],M-=v));var I=[S,M];return t.get(\"inverse\")&&I.reverse(),{viewRect:l,mainLength:p,orient:s,rotation:c[s],labelRotation:m,labelPosOpt:n,labelAlign:t.get([\"label\",\"align\"])||u[s],labelBaseline:t.get([\"label\",\"verticalAlign\"])||t.get([\"label\",\"baseline\"])||h[s],playPosition:i,prevBtnPosition:r,nextBtnPosition:o,axisExtent:I,controlSize:g,controlGap:y}},e.prototype._position=function(t,e){var n=this._mainGroup,i=this._labelGroup,r=t.viewRect;if(\"vertical\"===t.orient){var o=[1,0,0,1,0,0],a=r.x,s=r.y+r.height;Un(o,o,[-a,-s]),Xn(o,o,-wE/2),Un(o,o,[a,s]),(r=r.clone()).applyTransform(o)}var l=y(r),u=y(n.getBoundingRect()),h=y(i.getBoundingRect()),c=[n.x,n.y],p=[i.x,i.y];p[0]=c[0]=l[0][0];var d,f=t.labelPosOpt;null==f||H(f)?(v(c,u,l,1,d=\"+\"===f?0:1),v(p,h,l,1,1-d)):(v(c,u,l,1,d=f>=0?0:1),p[1]=c[1]+f);function g(t){t.originX=l[0][0]-t.x,t.originY=l[1][0]-t.y}function y(t){return[[t.x,t.x+t.width],[t.y,t.y+t.height]]}function v(t,e,n,i,r){t[i]+=n[i][r]-e[i][r]}n.setPosition(c),i.setPosition(p),n.rotation=i.rotation=t.rotation,g(n),g(i)},e.prototype._createAxis=function(t,e){var n=e.getData(),i=e.get(\"axisType\"),r=function(t,e){if(e=e||t.get(\"type\"))switch(e){case\"category\":return new $_({ordinalMeta:t.getCategories(),extent:[1/0,-1/0]});case\"time\":return new dx({locale:t.ecModel.getLocaleModel(),useUTC:t.ecModel.get(\"useUTC\")});default:return new Q_}}(e,i);r.getTicks=function(){return n.mapArray([\"value\"],(function(t){return{value:t}}))};var o=n.getDataExtent(\"value\");r.setExtent(o[0],o[1]),r.niceTicks();var a=new bE(\"value\",r,t.axisExtent,i);return a.model=e,a},e.prototype._createGroup=function(t){var e=this[t]=new Ei;return this.group.add(e),e},e.prototype._renderAxisLine=function(t,e,n,i){var r=n.getExtent();if(i.get([\"lineStyle\",\"show\"])){var o=new uu({shape:{x1:r[0],y1:0,x2:r[1],y2:0},style:I({lineCap:\"round\"},i.getModel(\"lineStyle\").getLineStyle()),silent:!0,z2:1});e.add(o);var a=this._progressLine=new uu({shape:{x1:r[0],x2:this._currentPointer?this._currentPointer.x:r[0],y1:0,y2:0},style:T({lineCap:\"round\",lineWidth:o.style.lineWidth},i.getModel([\"progress\",\"lineStyle\"]).getLineStyle()),silent:!0,z2:1});e.add(a)}},e.prototype._renderAxisTick=function(t,e,n,i){var r=this,o=i.getData(),a=n.scale.getTicks();this._tickSymbols=[],P(a,(function(t){var a=n.dataToCoord(t.value),s=o.getItemModel(t.value),l=s.getModel(\"itemStyle\"),u=s.getModel([\"emphasis\",\"itemStyle\"]),h=s.getModel([\"progress\",\"itemStyle\"]),c={x:a,y:0,onclick:V(r._changeTimeline,r,t.value)},p=IE(s,l,e,c);p.ensureState(\"emphasis\").style=u.getItemStyle(),p.ensureState(\"progress\").style=h.getItemStyle(),sl(p);var d=_s(p);s.get(\"tooltip\")?(d.dataIndex=t.value,d.dataModel=i):d.dataIndex=d.dataModel=null,r._tickSymbols.push(p)}))},e.prototype._renderAxisLabel=function(t,e,n,i){var r=this;if(n.getLabelModel().get(\"show\")){var o=i.getData(),a=n.getViewLabels();this._tickLabels=[],P(a,(function(i){var a=i.tickValue,s=o.getItemModel(a),l=s.getModel(\"label\"),u=s.getModel([\"emphasis\",\"label\"]),h=s.getModel([\"progress\",\"label\"]),c=n.dataToCoord(i.tickValue),p=new cs({x:c,y:0,rotation:t.labelRotation-t.rotation,onclick:V(r._changeTimeline,r,a),silent:!1,style:ph(l,{text:i.formattedLabel,align:t.labelAlign,verticalAlign:t.labelBaseline})});p.ensureState(\"emphasis\").style=ph(u),p.ensureState(\"progress\").style=ph(h),e.add(p),sl(p),SE(p).dataIndex=a,r._tickLabels.push(p)}))}},e.prototype._renderControl=function(t,e,n,i){var r=t.controlSize,o=t.rotation,a=i.getModel(\"controlStyle\").getItemStyle(),s=i.getModel([\"emphasis\",\"controlStyle\"]).getItemStyle(),l=i.getPlayState(),u=i.get(\"inverse\",!0);function h(t,n,l,u){if(t){var h=Ii(tt(i.get([\"controlStyle\",n+\"BtnSize\"]),r),r),c=function(t,e,n,i){var r=i.style,o=eh(t.get([\"controlStyle\",e]),i||{},new gi(n[0],n[1],n[2],n[3]));r&&o.setStyle(r);return o}(i,n+\"Icon\",[0,-h/2,h,h],{x:t[0],y:t[1],originX:r/2,originY:0,rotation:u?-o:0,rectHover:!0,style:a,onclick:l});c.ensureState(\"emphasis\").style=s,e.add(c),sl(c)}}h(t.nextBtnPosition,\"next\",V(this._changeTimeline,this,u?\"-\":\"+\")),h(t.prevBtnPosition,\"prev\",V(this._changeTimeline,this,u?\"+\":\"-\")),h(t.playPosition,l?\"stop\":\"play\",V(this._handlePlayClick,this,!l),!0)},e.prototype._renderCurrentPointer=function(t,e,n,i){var r=i.getData(),o=i.getCurrentIndex(),a=r.getItemModel(o).getModel(\"checkpointStyle\"),s=this,l={onCreate:function(t){t.draggable=!0,t.drift=V(s._handlePointerDrag,s),t.ondragend=V(s._handlePointerDragend,s),TE(t,s._progressLine,o,n,i,!0)},onUpdate:function(t){TE(t,s._progressLine,o,n,i)}};this._currentPointer=IE(a,a,this._mainGroup,{},this._currentPointer,l)},e.prototype._handlePlayClick=function(t){this._clearTimer(),this.api.dispatchAction({type:\"timelinePlayChange\",playState:t,from:this.uid})},e.prototype._handlePointerDrag=function(t,e,n){this._clearTimer(),this._pointerChangeTimeline([n.offsetX,n.offsetY])},e.prototype._handlePointerDragend=function(t){this._pointerChangeTimeline([t.offsetX,t.offsetY],!0)},e.prototype._pointerChangeTimeline=function(t,e){var n=this._toAxisCoord(t)[0],i=qi(this._axis.getExtent().slice());n>i[1]&&(n=i[1]),n<i[0]&&(n=i[0]),this._currentPointer.x=n,this._currentPointer.markRedraw(),this._progressLine.shape.x2=n,this._progressLine.dirty();var r=this._findNearestTick(n),o=this.model;(e||r!==o.getCurrentIndex()&&o.get(\"realtime\"))&&this._changeTimeline(r)},e.prototype._doPlayStop=function(){var t=this;this._clearTimer(),this.model.getPlayState()&&(this._timer=setTimeout((function(){var e=t.model;t._changeTimeline(e.getCurrentIndex()+(e.get(\"rewind\",!0)?-1:1))}),this.model.get(\"playInterval\")))},e.prototype._toAxisCoord=function(t){return qu(t,this._mainGroup.getLocalTransform(),!0)},e.prototype._findNearestTick=function(t){var e,n=this.model.getData(),i=1/0,r=this._axis;return n.each([\"value\"],(function(n,o){var a=r.dataToCoord(n),s=Math.abs(a-t);s<i&&(i=s,e=o)})),e},e.prototype._clearTimer=function(){this._timer&&(clearTimeout(this._timer),this._timer=null)},e.prototype._changeTimeline=function(t){var e=this.model.getCurrentIndex();\"+\"===t?t=e+1:\"-\"===t&&(t=e-1),this.api.dispatchAction({type:\"timelineChange\",currentIndex:t,from:this.uid})},e.prototype._updateTicksStatus=function(){var t=this.model.getCurrentIndex(),e=this._tickSymbols,n=this._tickLabels;if(e)for(var i=0;i<e.length;i++)e&&e[i]&&e[i].toggleState(\"progress\",i<t);if(n)for(i=0;i<n.length;i++)n&&n[i]&&n[i].toggleState(\"progress\",SE(n[i]).dataIndex<=t)},e.type=\"timeline.slider\",e}(xE);function IE(t,e,n,i,r,o){var a=e.get(\"color\");r?(r.setColor(a),n.add(r),o&&o.onUpdate(r)):((r=fy(t.get(\"symbol\"),-1,-1,2,2,a)).setStyle(\"strokeNoScale\",!0),n.add(r),o&&o.onCreate(r));var s=e.getItemStyle([\"color\"]);r.setStyle(s),i=S({rectHover:!0,z2:100},i,!0);var l=t.get(\"symbolSize\");l=l instanceof Array?l.slice():[+l,+l],i.scaleX=l[0]/2,i.scaleY=l[1]/2;var u=t.get(\"symbolOffset\");u&&(i.x=i.x||0,i.y=i.y||0,i.x+=Zi(u[0],l[0]),i.y+=Zi(u[1],l[1]));var h=t.get(\"symbolRotate\");return i.rotation=(h||0)*Math.PI/180||0,r.attr(i),r.updateTransform(),r}function TE(t,e,n,i,r,o){if(!t.dragging){var a=r.getModel(\"checkpointStyle\"),s=i.dataToCoord(r.getData().get(\"value\",n));if(o||!a.get(\"animation\",!0))t.attr({x:s,y:0}),e&&e.attr({shape:{x2:s}});else{var l={duration:a.get(\"animationDuration\",!0),easing:a.get(\"animationEasing\",!0)};t.stopAnimation(null,!0),t.animateTo({x:s,y:0},l),e&&e.animateTo({shape:{x2:s}},l)}}}function CE(t){var e=t&&t.timeline;F(e)||(e=e?[e]:[]),P(e,(function(t){t&&function(t){var e=t.type,n={number:\"value\",time:\"time\"};n[e]&&(t.axisType=n[e],delete t.type);if(DE(t),AE(t,\"controlPosition\")){var i=t.controlStyle||(t.controlStyle={});AE(i,\"position\")||(i.position=t.controlPosition),\"none\"!==i.position||AE(i,\"show\")||(i.show=!1,delete i.position),delete t.controlPosition}P(t.data||[],(function(t){X(t)&&!F(t)&&(!AE(t,\"value\")&&AE(t,\"name\")&&(t.value=t.name),DE(t))}))}(t)}))}function DE(t){var e=t.itemStyle||(t.itemStyle={}),n=e.emphasis||(e.emphasis={}),i=t.label||t.label||{},r=i.normal||(i.normal={}),o={normal:1,emphasis:1};P(i,(function(t,e){o[e]||AE(r,e)||(r[e]=t)})),n.label&&!AE(i,\"emphasis\")&&(i.emphasis=n.label,delete n.label)}function AE(t,e){return t.hasOwnProperty(e)}function LE(t,e){if(!t)return!1;for(var n=F(t)?t:[t],i=0;i<n.length;i++)if(n[i]&&n[i][e])return!0;return!1}function kE(t){br(t,\"label\",[\"show\"])}var PE=kr(),OE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.createdBySelf=!1,n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n),this._mergeOption(t,n,!1,!0)},e.prototype.isAnimationEnabled=function(){if(a.node)return!1;var t=this.__hostSeries;return this.getShallow(\"animation\")&&t&&t.isAnimationEnabled()},e.prototype.mergeOption=function(t,e){this._mergeOption(t,e,!1,!1)},e.prototype._mergeOption=function(t,e,n,i){var r=this.mainType;n||e.eachSeries((function(t){var n=t.get(this.mainType,!0),o=PE(t)[r];n&&n.data?(o?o._mergeOption(n,e,!0):(i&&kE(n),P(n.data,(function(t){t instanceof Array?(kE(t[0]),kE(t[1])):kE(t)})),I(o=this.createMarkerModelFromSeries(n,this,e),{mainType:this.mainType,seriesIndex:t.seriesIndex,name:t.name,createdBySelf:!0}),o.__hostSeries=t),PE(t)[r]=o):PE(t)[r]=null}),this)},e.prototype.formatTooltip=function(t,e,n){var i=this.getData(),r=this.getRawValue(t),o=i.getName(t);return tf(\"section\",{header:this.name,blocks:[tf(\"nameValue\",{name:o,value:r,noName:!o,noValue:null==r})]})},e.prototype.getData=function(){return this._data},e.prototype.setData=function(t){this._data=t},e.getMarkerModelFromSeries=function(t,e){return PE(t)[e]},e.type=\"marker\",e.dependencies=[\"series\",\"grid\",\"polar\",\"geo\"],e}(Xc);L(OE,Td.prototype);var RE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type=\"markPoint\",e.defaultOption={zlevel:0,z:5,symbol:\"pin\",symbolSize:50,tooltip:{trigger:\"item\"},label:{show:!0,position:\"inside\"},itemStyle:{borderWidth:2},emphasis:{label:{show:!0}}},e}(OE);function NE(t,e,n,i,r,o){var a=[],s=V_(e,i)?e.getCalculationInfo(\"stackResultDimension\"):i,l=GE(e,s,t),u=e.indicesOfNearest(s,l)[0];a[r]=e.get(n,u),a[o]=e.get(s,u);var h=e.get(i,u),c=Ki(e.get(i,u));return(c=Math.min(c,20))>=0&&(a[o]=+a[o].toFixed(c)),[a,h]}var zE={min:B(NE,\"min\"),max:B(NE,\"max\"),average:B(NE,\"average\"),median:B(NE,\"median\")};function EE(t,e){var n=t.getData(),i=t.coordinateSystem;if(e&&!function(t){return!isNaN(parseFloat(t.x))&&!isNaN(parseFloat(t.y))}(e)&&!F(e.coord)&&i){var r=i.dimensions,o=VE(e,n,i,t);if((e=w(e)).type&&zE[e.type]&&o.baseAxis&&o.valueAxis){var a=D(r,o.baseAxis.dim),s=D(r,o.valueAxis.dim),l=zE[e.type](n,o.baseDataDim,o.valueDataDim,a,s);e.coord=l[0],e.value=l[1]}else{for(var u=[null!=e.xAxis?e.xAxis:e.radiusAxis,null!=e.yAxis?e.yAxis:e.angleAxis],h=0;h<2;h++)zE[u[h]]&&(u[h]=GE(n,n.mapDimension(r[h]),u[h]));e.coord=u}}return e}function VE(t,e,n,i){var r={};return null!=t.valueIndex||null!=t.valueDim?(r.valueDataDim=null!=t.valueIndex?e.getDimension(t.valueIndex):t.valueDim,r.valueAxis=n.getAxis(function(t,e){var n=t.getData(),i=n.dimensions;e=n.getDimension(e);for(var r=0;r<i.length;r++){var o=n.getDimensionInfo(i[r]);if(o.name===e)return o.coordDim}}(i,r.valueDataDim)),r.baseAxis=n.getOtherAxis(r.valueAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim)):(r.baseAxis=i.getBaseAxis(),r.valueAxis=n.getOtherAxis(r.baseAxis),r.baseDataDim=e.mapDimension(r.baseAxis.dim),r.valueDataDim=e.mapDimension(r.valueAxis.dim)),r}function BE(t,e){return!(t&&t.containData&&e.coord&&!function(t){return!(isNaN(parseFloat(t.x))&&isNaN(parseFloat(t.y)))}(e))||t.containData(e.coord)}function FE(t,e,n,i){return i<2?t.coord&&t.coord[i]:t.value}function GE(t,e,n){if(\"average\"===n){var i=0,r=0;return t.each(e,(function(t,e){isNaN(t)||(i+=t,r++)})),i/r}return\"median\"===n?t.getMedian(e):t.getDataExtent(e)[\"max\"===n?1:0]}var HE=kr(),WE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.init=function(){this.markerGroupMap=ht()},e.prototype.render=function(t,e,n){var i=this,r=this.markerGroupMap;r.each((function(t){HE(t).keep=!1})),e.eachSeries((function(t){var r=OE.getMarkerModelFromSeries(t,i.type);r&&i.renderSeries(t,r,e,n)})),r.each((function(t){!HE(t).keep&&i.group.remove(t.group)}))},e.prototype.markKeep=function(t){HE(t).keep=!0},e.prototype.blurSeries=function(t){var e=this;P(t,(function(t){var n=OE.getMarkerModelFromSeries(t,e.type);n&&n.getData().eachItemGraphicEl((function(t){t&&Ks(t)}))}))},e.type=\"marker\",e}(wf);function UE(t,e,n){var i=e.coordinateSystem;t.each((function(r){var o,a=t.getItemModel(r),s=Zi(a.get(\"x\"),n.getWidth()),l=Zi(a.get(\"y\"),n.getHeight());if(isNaN(s)||isNaN(l)){if(e.getMarkerPosition)o=e.getMarkerPosition(t.getValues(t.dimensions,r));else if(i){var u=t.get(i.dimensions[0],r),h=t.get(i.dimensions[1],r);o=i.dataToPoint([u,h])}}else o=[s,l];isNaN(s)||(o[0]=s),isNaN(l)||(o[1]=l),t.setItemLayout(r,o)}))}var XE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=OE.getMarkerModelFromSeries(t,\"markPoint\");e&&(UE(e.getData(),t,n),this.markerGroupMap.get(t.id).updateLayout())}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,new mw),u=function(t,e,n){var i;i=t?O(t&&t.dimensions,(function(t){return T({name:t},e.getData().getDimensionInfo(e.getData().mapDimension(t))||{})})):[{name:\"value\",type:\"float\"}];var r=new L_(i,n),o=O(n.get(\"data\"),B(EE,e));t&&(o=N(o,B(BE,t)));return r.initData(o,null,t?FE:function(t){return t.value}),r}(r,t,e);e.setData(u),UE(e.getData(),t,i),u.each((function(t){var n=u.getItemModel(t),i=n.getShallow(\"symbol\"),r=n.getShallow(\"symbolSize\"),o=n.getShallow(\"symbolRotate\");if(G(i)||G(r)||G(o)){var s=e.getRawValue(t),l=e.getDataParams(t);G(i)&&(i=i(s,l)),G(r)&&(r=r(s,l)),G(o)&&(o=o(s,l))}var h=n.getModel(\"itemStyle\").getItemStyle(),c=mg(a,\"color\");h.fill||(h.fill=c),u.setItemVisual(t,{symbol:i,symbolSize:r,symbolRotate:o,style:h})})),l.updateData(u),this.group.add(l.group),u.eachItemGraphicEl((function(t){t.traverse((function(t){_s(t).dataModel=e}))})),this.markKeep(l),l.group.silent=e.get(\"silent\")||t.get(\"silent\")},e.type=\"markPoint\",e}(WE);var YE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type=\"markLine\",e.defaultOption={zlevel:0,z:5,symbol:[\"circle\",\"arrow\"],symbolSize:[8,16],symbolOffset:0,precision:2,tooltip:{trigger:\"item\"},label:{show:!0,position:\"end\",distance:5},lineStyle:{type:\"dashed\"},emphasis:{label:{show:!0},lineStyle:{width:3}},animationEasing:\"linear\"},e}(OE),ZE=kr(),jE=function(t,e,n,i){var r,o=t.getData();if(F(i))r=i;else{var a=i.type;if(\"min\"===a||\"max\"===a||\"average\"===a||\"median\"===a||null!=i.xAxis||null!=i.yAxis){var s=void 0,l=void 0;if(null!=i.yAxis||null!=i.xAxis)s=e.getAxis(null!=i.yAxis?\"y\":\"x\"),l=Q(i.yAxis,i.xAxis);else{var u=VE(i,o,e,t);s=u.valueAxis,l=GE(o,B_(o,u.valueDataDim),a)}var h=\"x\"===s.dim?0:1,c=1-h,p=w(i),d={coord:[]};p.type=null,p.coord=[],p.coord[c]=-1/0,d.coord[c]=1/0;var f=n.get(\"precision\");f>=0&&\"number\"==typeof l&&(l=+l.toFixed(Math.min(f,20))),p.coord[h]=d.coord[h]=l,r=[p,d,{type:a,valueIndex:i.valueIndex,value:l}]}else r=[]}var g=[EE(t,r[0]),EE(t,r[1]),I({},r[2])];return g[2].type=g[2].type||null,S(g[2],g[0]),S(g[2],g[1]),g};function qE(t){return!isNaN(t)&&!isFinite(t)}function KE(t,e,n,i){var r=1-t,o=i.dimensions[t];return qE(e[r])&&qE(n[r])&&e[t]===n[t]&&i.getAxis(o).containData(e[t])}function $E(t,e){if(\"cartesian2d\"===t.type){var n=e[0].coord,i=e[1].coord;if(n&&i&&(KE(1,n,i,t)||KE(0,n,i,t)))return!0}return BE(t,e[0])&&BE(t,e[1])}function JE(t,e,n,i,r){var o,a=i.coordinateSystem,s=t.getItemModel(e),l=Zi(s.get(\"x\"),r.getWidth()),u=Zi(s.get(\"y\"),r.getHeight());if(isNaN(l)||isNaN(u)){if(i.getMarkerPosition)o=i.getMarkerPosition(t.getValues(t.dimensions,e));else{var h=a.dimensions,c=t.get(h[0],e),p=t.get(h[1],e);o=a.dataToPoint([c,p])}if(Nw(a,\"cartesian2d\")){var d=a.getAxis(\"x\"),f=a.getAxis(\"y\");h=a.dimensions;qE(t.get(h[0],e))?o[0]=d.toGlobalCoord(d.getExtent()[n?0:1]):qE(t.get(h[1],e))&&(o[1]=f.toGlobalCoord(f.getExtent()[n?0:1]))}isNaN(l)||(o[0]=l),isNaN(u)||(o[1]=u)}else o=[l,u];t.setItemLayout(e,o)}var QE=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=OE.getMarkerModelFromSeries(t,\"markLine\");if(e){var i=e.getData(),r=ZE(e).from,o=ZE(e).to;r.each((function(e){JE(r,e,!0,t,n),JE(o,e,!1,t,n)})),i.each((function(t){i.setItemLayout(t,[r.getItemLayout(t),o.getItemLayout(t)])})),this.markerGroupMap.get(t.id).updateLayout()}}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,new VC);this.group.add(l.group);var u=function(t,e,n){var i;i=t?O(t&&t.dimensions,(function(t){return T({name:t},e.getData().getDimensionInfo(e.getData().mapDimension(t))||{})})):[{name:\"value\",type:\"float\"}];var r=new L_(i,n),o=new L_(i,n),a=new L_([],n),s=O(n.get(\"data\"),B(jE,e,t,n));t&&(s=N(s,B($E,t)));var l=t?FE:function(t){return t.value};return r.initData(O(s,(function(t){return t[0]})),null,l),o.initData(O(s,(function(t){return t[1]})),null,l),a.initData(O(s,(function(t){return t[2]}))),a.hasItemOption=!0,{from:r,to:o,line:a}}(r,t,e),h=u.from,c=u.to,p=u.line;ZE(e).from=h,ZE(e).to=c,e.setData(p);var d=e.get(\"symbol\"),f=e.get(\"symbolSize\"),g=e.get(\"symbolRotate\"),y=e.get(\"symbolOffset\");function v(e,n,r){var o=e.getItemModel(n);JE(e,n,r,t,i);var s=o.getModel(\"itemStyle\").getItemStyle();null==s.fill&&(s.fill=mg(a,\"color\")),e.setItemVisual(n,{symbolKeepAspect:o.get(\"symbolKeepAspect\"),symbolOffset:tt(o.get(\"symbolOffset\"),y[r?0:1]),symbolRotate:tt(o.get(\"symbolRotate\",!0),g[r?0:1]),symbolSize:tt(o.get(\"symbolSize\"),f[r?0:1]),symbol:tt(o.get(\"symbol\",!0),d[r?0:1]),style:s})}F(d)||(d=[d,d]),F(f)||(f=[f,f]),F(g)||(g=[g,g]),F(y)||(y=[y,y]),u.from.each((function(t){v(h,t,!0),v(c,t,!1)})),p.each((function(t){var e=p.getItemModel(t).getModel(\"lineStyle\").getLineStyle();p.setItemLayout(t,[h.getItemLayout(t),c.getItemLayout(t)]),null==e.stroke&&(e.stroke=h.getItemVisual(t,\"style\").fill),p.setItemVisual(t,{fromSymbolKeepAspect:h.getItemVisual(t,\"symbolKeepAspect\"),fromSymbolOffset:h.getItemVisual(t,\"symbolOffset\"),fromSymbolRotate:h.getItemVisual(t,\"symbolRotate\"),fromSymbolSize:h.getItemVisual(t,\"symbolSize\"),fromSymbol:h.getItemVisual(t,\"symbol\"),toSymbolKeepAspect:c.getItemVisual(t,\"symbolKeepAspect\"),toSymbolOffset:c.getItemVisual(t,\"symbolOffset\"),toSymbolRotate:c.getItemVisual(t,\"symbolRotate\"),toSymbolSize:c.getItemVisual(t,\"symbolSize\"),toSymbol:c.getItemVisual(t,\"symbol\"),style:e})})),l.updateData(p),u.line.eachItemGraphicEl((function(t,n){t.traverse((function(t){_s(t).dataModel=e}))})),this.markKeep(l),l.group.silent=e.get(\"silent\")||t.get(\"silent\")},e.type=\"markLine\",e}(WE);var tV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.createMarkerModelFromSeries=function(t,n,i){return new e(t,n,i)},e.type=\"markArea\",e.defaultOption={zlevel:0,z:1,tooltip:{trigger:\"item\"},animation:!1,label:{show:!0,position:\"top\"},itemStyle:{borderWidth:0},emphasis:{label:{show:!0,position:\"top\"}}},e}(OE),eV=kr(),nV=function(t,e,n,i){var r=EE(t,i[0]),o=EE(t,i[1]),a=r.coord,s=o.coord;a[0]=Q(a[0],-1/0),a[1]=Q(a[1],-1/0),s[0]=Q(s[0],1/0),s[1]=Q(s[1],1/0);var l=M([{},r,o]);return l.coord=[r.coord,o.coord],l.x0=r.x,l.y0=r.y,l.x1=o.x,l.y1=o.y,l};function iV(t){return!isNaN(t)&&!isFinite(t)}function rV(t,e,n,i){var r=1-t;return iV(e[r])&&iV(n[r])}function oV(t,e){var n=e.coord[0],i=e.coord[1];return!!(Nw(t,\"cartesian2d\")&&n&&i&&(rV(1,n,i)||rV(0,n,i)))||(BE(t,{coord:n,x:e.x0,y:e.y0})||BE(t,{coord:i,x:e.x1,y:e.y1}))}function aV(t,e,n,i,r){var o,a=i.coordinateSystem,s=t.getItemModel(e),l=Zi(s.get(n[0]),r.getWidth()),u=Zi(s.get(n[1]),r.getHeight());if(isNaN(l)||isNaN(u)){if(i.getMarkerPosition)o=i.getMarkerPosition(t.getValues(n,e));else{var h=[d=t.get(n[0],e),f=t.get(n[1],e)];a.clampData&&a.clampData(h,h),o=a.dataToPoint(h,!0)}if(Nw(a,\"cartesian2d\")){var c=a.getAxis(\"x\"),p=a.getAxis(\"y\"),d=t.get(n[0],e),f=t.get(n[1],e);iV(d)?o[0]=c.toGlobalCoord(c.getExtent()[\"x0\"===n[0]?0:1]):iV(f)&&(o[1]=p.toGlobalCoord(p.getExtent()[\"y0\"===n[1]?0:1]))}isNaN(l)||(o[0]=l),isNaN(u)||(o[1]=u)}else o=[l,u];return o}var sV=[[\"x0\",\"y0\"],[\"x1\",\"y0\"],[\"x1\",\"y1\"],[\"x0\",\"y1\"]],lV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.updateTransform=function(t,e,n){e.eachSeries((function(t){var e=OE.getMarkerModelFromSeries(t,\"markArea\");if(e){var i=e.getData();i.each((function(e){var r=O(sV,(function(r){return aV(i,e,r,t,n)}));i.setItemLayout(e,r),i.getItemGraphicEl(e).setShape(\"points\",r)}))}}),this)},e.prototype.renderSeries=function(t,e,n,i){var r=t.coordinateSystem,o=t.id,a=t.getData(),s=this.markerGroupMap,l=s.get(o)||s.set(o,{group:new Ei});this.group.add(l.group),this.markKeep(l);var u=function(t,e,n){var i,r,o=[\"x0\",\"y0\",\"x1\",\"y1\"];t?(i=O(t&&t.dimensions,(function(t){var n=e.getData();return T({name:t},n.getDimensionInfo(n.mapDimension(t))||{})})),r=new L_(O(o,(function(t,e){return{name:t,type:i[e%2].type}})),n)):r=new L_(i=[{name:\"value\",type:\"float\"}],n);var a=O(n.get(\"data\"),B(nV,e,t,n));t&&(a=N(a,B(oV,t)));var s=t?function(t,e,n,i){return t.coord[Math.floor(i/2)][i%2]}:function(t){return t.value};return r.initData(a,null,s),r.hasItemOption=!0,r}(r,t,e);e.setData(u),u.each((function(e){var n=O(sV,(function(n){return aV(u,e,n,t,i)})),o=r.getAxis(\"x\").scale,s=r.getAxis(\"y\").scale,l=o.getExtent(),h=s.getExtent(),c=[o.parse(u.get(\"x0\",e)),o.parse(u.get(\"x1\",e))],p=[s.parse(u.get(\"y0\",e)),s.parse(u.get(\"y1\",e))];qi(c),qi(p);var d=!!(l[0]>c[1]||l[1]<c[0]||h[0]>p[1]||h[1]<p[0]);u.setItemLayout(e,{points:n,allClipped:d});var f=u.getItemModel(e).getModel(\"itemStyle\").getItemStyle(),g=mg(a,\"color\");f.fill||(f.fill=g,\"string\"==typeof f.fill&&(f.fill=$e(f.fill,.4))),f.stroke||(f.stroke=g),u.setItemVisual(e,\"style\",f)})),u.diff(eV(l).data).add((function(t){var e=u.getItemLayout(t);if(!e.allClipped){var n=new ru({shape:{points:e.points}});u.setItemGraphicEl(t,n),l.group.add(n)}})).update((function(t,n){var i=eV(l).data.getItemGraphicEl(n),r=u.getItemLayout(t);r.allClipped?i&&l.group.remove(i):(i?Hu(i,{shape:{points:r.points}},e,t):i=new ru({shape:{points:r.points}}),u.setItemGraphicEl(t,i),l.group.add(i))})).remove((function(t){var e=eV(l).data.getItemGraphicEl(t);l.group.remove(e)})).execute(),u.eachItemGraphicEl((function(t,n){var i=u.getItemModel(n),r=u.getItemVisual(n,\"style\");t.useStyle(u.getItemVisual(n,\"style\")),hh(t,ch(i),{labelFetcher:e,labelDataIndex:n,defaultText:u.getName(n)||\"\",inheritColor:\"string\"==typeof r.fill?$e(r.fill,1):\"#000\"}),cl(t,i),sl(t),_s(t).dataModel=e})),eV(l).data=u,l.group.silent=e.get(\"silent\")||t.get(\"silent\")},e.type=\"markArea\",e}(WE);var uV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.layoutMode={type:\"box\",ignoreSize:!0},n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n),t.selected=t.selected||{},this._updateSelector(t)},e.prototype.mergeOption=function(e,n){t.prototype.mergeOption.call(this,e,n),this._updateSelector(e)},e.prototype._updateSelector=function(t){var e=t.selector,n=this.ecModel;!0===e&&(e=t.selector=[\"all\",\"inverse\"]),F(e)&&P(e,(function(t,i){H(t)&&(t={type:t}),e[i]=S(t,function(t,e){return\"all\"===e?{type:\"all\",title:t.getLocale([\"legend\",\"selector\",\"all\"])}:\"inverse\"===e?{type:\"inverse\",title:t.getLocale([\"legend\",\"selector\",\"inverse\"])}:void 0}(n,t.type))}))},e.prototype.optionUpdated=function(){this._updateData(this.ecModel);var t=this._data;if(t[0]&&\"single\"===this.get(\"selectedMode\")){for(var e=!1,n=0;n<t.length;n++){var i=t[n].get(\"name\");if(this.isSelected(i)){this.select(i),e=!0;break}}!e&&this.select(t[0].get(\"name\"))}},e.prototype._updateData=function(t){var e=[],n=[];t.eachRawSeries((function(i){var r,o=i.name;if(n.push(o),i.legendVisualProvider){var a=i.legendVisualProvider.getAllNames();t.isSeriesFiltered(i)||(n=n.concat(a)),a.length?e=e.concat(a):r=!0}else r=!0;r&&Dr(i)&&e.push(i.name)})),this._availableNames=n;var i=O(this.get(\"data\")||e,(function(t){return\"string\"!=typeof t&&\"number\"!=typeof t||(t={name:t}),new Oh(t,this,this.ecModel)}),this);this._data=i},e.prototype.getData=function(){return this._data},e.prototype.select=function(t){var e=this.option.selected;\"single\"===this.get(\"selectedMode\")&&P(this._data,(function(t){e[t.get(\"name\")]=!1}));e[t]=!0},e.prototype.unSelect=function(t){\"single\"!==this.get(\"selectedMode\")&&(this.option.selected[t]=!1)},e.prototype.toggleSelected=function(t){var e=this.option.selected;e.hasOwnProperty(t)||(e[t]=!0),this[e[t]?\"unSelect\":\"select\"](t)},e.prototype.allSelect=function(){var t=this._data,e=this.option.selected;P(t,(function(t){e[t.get(\"name\",!0)]=!0}))},e.prototype.inverseSelect=function(){var t=this._data,e=this.option.selected;P(t,(function(t){var n=t.get(\"name\",!0);e.hasOwnProperty(n)||(e[n]=!0),e[n]=!e[n]}))},e.prototype.isSelected=function(t){var e=this.option.selected;return!(e.hasOwnProperty(t)&&!e[t])&&D(this._availableNames,t)>=0},e.prototype.getOrient=function(){return\"vertical\"===this.get(\"orient\")?{index:1,name:\"vertical\"}:{index:0,name:\"horizontal\"}},e.type=\"legend.plain\",e.dependencies=[\"series\"],e.defaultOption={zlevel:0,z:4,show:!0,orient:\"horizontal\",left:\"center\",top:0,align:\"auto\",backgroundColor:\"rgba(0,0,0,0)\",borderColor:\"#ccc\",borderRadius:0,borderWidth:0,padding:5,itemGap:10,itemWidth:25,itemHeight:14,symbolRotate:\"inherit\",inactiveColor:\"#ccc\",inactiveBorderColor:\"#ccc\",inactiveBorderWidth:\"auto\",itemStyle:{color:\"inherit\",opacity:\"inherit\",decal:\"inherit\",shadowBlur:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0,borderColor:\"inherit\",borderWidth:\"auto\",borderCap:\"inherit\",borderJoin:\"inherit\",borderDashOffset:\"inherit\",borderMiterLimit:\"inherit\"},lineStyle:{width:\"auto\",color:\"inherit\",inactiveColor:\"#ccc\",inactiveWidth:2,opacity:\"inherit\",type:\"inherit\",cap:\"inherit\",join:\"inherit\",dashOffset:\"inherit\",miterLimit:\"inherit\",shadowBlur:0,shadowColor:null,shadowOffsetX:0,shadowOffsetY:0},textStyle:{color:\"#333\"},selectedMode:!0,selector:!1,selectorLabel:{show:!0,borderRadius:10,padding:[3,5,3,5],fontSize:12,fontFamily:\" sans-serif\",color:\"#666\",borderWidth:1,borderColor:\"#666\"},emphasis:{selectorLabel:{show:!0,color:\"#eee\",backgroundColor:\"#666\"}},selectorPosition:\"auto\",selectorItemGap:7,selectorButtonGap:10,tooltip:{show:!1}},e}(Xc),hV=B,cV=P,pV=Ei,dV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.newlineDisabled=!1,n}return n(e,t),e.prototype.init=function(){this.group.add(this._contentGroup=new pV),this.group.add(this._selectorGroup=new pV),this._isFirstRender=!0},e.prototype.getContentGroup=function(){return this._contentGroup},e.prototype.getSelectorGroup=function(){return this._selectorGroup},e.prototype.render=function(t,e,n){var i=this._isFirstRender;if(this._isFirstRender=!1,this.resetInner(),t.get(\"show\",!0)){var r=t.get(\"align\"),o=t.get(\"orient\");r&&\"auto\"!==r||(r=\"right\"===t.get(\"left\")&&\"vertical\"===o?\"right\":\"left\");var a=t.get(\"selector\",!0),s=t.get(\"selectorPosition\",!0);!a||s&&\"auto\"!==s||(s=\"horizontal\"===o?\"end\":\"start\"),this.renderInner(r,t,e,n,a,o,s);var l=t.getBoxLayoutParams(),u={width:n.getWidth(),height:n.getHeight()},h=t.get(\"padding\"),c=Vc(l,u,h),p=this.layoutInner(t,r,c,i,a,s),d=Vc(T({width:p.width,height:p.height},l),u,h);this.group.x=d.x-p.x,this.group.y=d.y-p.y,this.group.markRedraw(),this.group.add(this._backgroundEl=BN(p,t))}},e.prototype.resetInner=function(){this.getContentGroup().removeAll(),this._backgroundEl&&this.group.remove(this._backgroundEl),this.getSelectorGroup().removeAll()},e.prototype.renderInner=function(t,e,n,i,r,o,a){var s=this.getContentGroup(),l=ht(),u=e.get(\"selectedMode\"),h=[];n.eachRawSeries((function(t){!t.get(\"legendHoverLink\")&&h.push(t.id)})),cV(e.getData(),(function(r,o){var a=r.get(\"name\");if(!this.newlineDisabled&&(\"\"===a||\"\\n\"===a)){var c=new pV;return c.newline=!0,void s.add(c)}var p=n.getSeriesByName(a)[0];if(!l.get(a)){if(p){var d=p.getData(),f=d.getVisual(\"legendLineStyle\")||{},g=d.getVisual(\"legendIcon\"),y=d.getVisual(\"style\");this._createItem(p,a,o,r,e,t,f,y,g,u).on(\"click\",hV(fV,a,null,i,h)).on(\"mouseover\",hV(yV,p.name,null,i,h)).on(\"mouseout\",hV(vV,p.name,null,i,h)),l.set(a,!0)}else n.eachRawSeries((function(n){if(!l.get(a)&&n.legendVisualProvider){var s=n.legendVisualProvider;if(!s.containName(a))return;var c=s.indexOfName(a),p=s.getItemVisual(c,\"style\"),d=s.getItemVisual(c,\"legendIcon\"),f=He(p.fill);f&&0===f[3]&&(f[3]=.2,p.fill=Je(f,\"rgba\")),this._createItem(n,a,o,r,e,t,{},p,d,u).on(\"click\",hV(fV,null,a,i,h)).on(\"mouseover\",hV(yV,null,a,i,h)).on(\"mouseout\",hV(vV,null,a,i,h)),l.set(a,!0)}}),this);0}}),this),r&&this._createSelector(r,e,i,o,a)},e.prototype._createSelector=function(t,e,n,i,r){var o=this.getSelectorGroup();cV(t,(function(t){var i=t.type,r=new cs({style:{x:0,y:0,align:\"center\",verticalAlign:\"middle\"},onclick:function(){n.dispatchAction({type:\"all\"===i?\"legendAllSelect\":\"legendInverseSelect\"})}});o.add(r),hh(r,{normal:e.getModel(\"selectorLabel\"),emphasis:e.getModel([\"emphasis\",\"selectorLabel\"])},{defaultText:t.title}),sl(r)}))},e.prototype._createItem=function(t,e,n,i,r,o,a,s,l,u){var h=t.visualDrawType,c=r.get(\"itemWidth\"),p=r.get(\"itemHeight\"),d=r.isSelected(e),f=i.get(\"symbolRotate\"),g=i.get(\"icon\"),y=function(t,e,n,i,r,o,a){for(var s=e.getModel(\"itemStyle\"),l=Lh.concat([[\"decal\"]]),u={},h=0;h<l.length;++h){var c=l[h][l[h].length-1],p=l[h][0];if(\"inherit\"===(y=s.getShallow(c)))switch(p){case\"fill\":u.fill=r[o];break;case\"stroke\":u.stroke=r[0===t.lastIndexOf(\"empty\",0)?\"fill\":\"stroke\"];break;case\"opacity\":u.opacity=(\"fill\"===o?r:i).opacity;break;default:u[p]=r[p]}else\"auto\"===y&&\"lineWidth\"===p?u.lineWidth=r.lineWidth>0?2:0:u[p]=y}var d=e.getModel(\"lineStyle\"),f=Ch.concat([[\"inactiveColor\"],[\"inactiveWidth\"]]),g={};for(h=0;h<f.length;++h){var y;c=f[h][1],p=f[h][0];\"inherit\"===(y=d.getShallow(c))?g[p]=i[p]:\"auto\"===y&&\"lineWidth\"===p?g.lineWidth=i.lineWidth>0?2:0:g[p]=y}if(\"auto\"===u.fill&&(u.fill=r.fill),\"auto\"===u.stroke&&(u.stroke=r.fill),\"auto\"===g.stroke&&(g.stroke=r.fill),!a){var v=e.get(\"inactiveBorderWidth\"),m=u[t.indexOf(\"empty\")>-1?\"fill\":\"stroke\"];u.lineWidth=\"auto\"===v?r.lineWidth>0&&m?2:0:u.lineWidth,u.fill=e.get(\"inactiveColor\"),u.stroke=e.get(\"inactiveBorderColor\"),g.stroke=n.get(\"inactiveColor\"),g.lineWidth=n.get(\"inactiveWidth\")}return{itemStyle:u,lineStyle:g}}(l=g||l||\"roundRect\",i,r.getModel(\"lineStyle\"),a,s,h,d),v=new pV,m=i.getModel(\"textStyle\");if(\"function\"!=typeof t.getLegendIcon||g&&\"inherit\"!==g){var _=\"inherit\"===g&&t.getData().getVisual(\"symbol\")?\"inherit\"===f?t.getData().getVisual(\"symbolRotate\"):f:0;v.add(function(t){var e=t.icon||\"roundRect\",n=fy(e,0,0,t.itemWidth,t.itemHeight,t.itemStyle.fill);n.setStyle(t.itemStyle),n.rotation=(t.iconRotate||0)*Math.PI/180,n.setOrigin([t.itemWidth/2,t.itemHeight/2]),e.indexOf(\"empty\")>-1&&(n.style.stroke=n.style.fill,n.style.fill=\"#fff\",n.style.lineWidth=2);return n}({itemWidth:c,itemHeight:p,icon:l,iconRotate:_,itemStyle:y.itemStyle,lineStyle:y.lineStyle}))}else v.add(t.getLegendIcon({itemWidth:c,itemHeight:p,icon:l,iconRotate:f,itemStyle:y.itemStyle,lineStyle:y.lineStyle}));var x=\"left\"===o?c+5:-5,b=o,w=r.get(\"formatter\"),S=e;\"string\"==typeof w&&w?S=w.replace(\"{name}\",null!=e?e:\"\"):\"function\"==typeof w&&(S=w(e));var M=i.get(\"inactiveColor\");v.add(new cs({style:ph(m,{text:S,x:x,y:p/2,fill:d?m.getTextColor():M,align:b,verticalAlign:\"middle\"})}));var I=new ls({shape:v.getBoundingRect(),invisible:!0}),T=i.getModel(\"tooltip\");return T.get(\"show\")&&oh({el:I,componentModel:r,itemName:e,itemTooltipOption:T.option}),v.add(I),v.eachChild((function(t){t.silent=!0})),I.silent=!u,this.getContentGroup().add(v),sl(v),v.__legendDataIndex=n,v},e.prototype.layoutInner=function(t,e,n,i,r,o){var a=this.getContentGroup(),s=this.getSelectorGroup();Ec(t.get(\"orient\"),a,t.get(\"itemGap\"),n.width,n.height);var l=a.getBoundingRect(),u=[-l.x,-l.y];if(s.markRedraw(),a.markRedraw(),r){Ec(\"horizontal\",s,t.get(\"selectorItemGap\",!0));var h=s.getBoundingRect(),c=[-h.x,-h.y],p=t.get(\"selectorButtonGap\",!0),d=t.getOrient().index,f=0===d?\"width\":\"height\",g=0===d?\"height\":\"width\",y=0===d?\"y\":\"x\";\"end\"===o?c[d]+=l[f]+p:u[d]+=h[f]+p,c[1-d]+=l[g]/2-h[g]/2,s.x=c[0],s.y=c[1],a.x=u[0],a.y=u[1];var v={x:0,y:0};return v[f]=l[f]+p+h[f],v[g]=Math.max(l[g],h[g]),v[y]=Math.min(0,h[y]+c[1-d]),v}return a.x=u[0],a.y=u[1],this.group.getBoundingRect()},e.prototype.remove=function(){this.getContentGroup().removeAll(),this._isFirstRender=!0},e.type=\"legend.plain\",e}(wf);function fV(t,e,n,i){vV(t,e,n,i),n.dispatchAction({type:\"legendToggleSelect\",name:null!=t?t:e}),yV(t,e,n,i)}function gV(t){for(var e,n=t.getZr().storage.getDisplayList(),i=0,r=n.length;i<r&&!(e=n[i].states.emphasis);)i++;return e&&e.hoverLayer}function yV(t,e,n,i){gV(n)||n.dispatchAction({type:\"highlight\",seriesName:t,name:e,excludeSeriesId:i})}function vV(t,e,n,i){gV(n)||n.dispatchAction({type:\"downplay\",seriesName:t,name:e,excludeSeriesId:i})}function mV(t){var e=t.findComponents({mainType:\"legend\"});e&&e.length&&t.filterSeries((function(t){for(var n=0;n<e.length;n++)if(!e[n].isSelected(t.name))return!1;return!0}))}function _V(t,e,n){var i,r={},o=\"toggleSelected\"===t;return n.eachComponent(\"legend\",(function(n){o&&null!=i?n[i?\"select\":\"unSelect\"](e.name):\"allSelect\"===t||\"inverseSelect\"===t?n[t]():(n[t](e.name),i=n.isSelected(e.name)),P(n.getData(),(function(t){var e=t.get(\"name\");if(\"\\n\"!==e&&\"\"!==e){var i=n.isSelected(e);r.hasOwnProperty(e)?r[e]=r[e]&&i:r[e]=i}}))})),\"allSelect\"===t||\"inverseSelect\"===t?{selected:r}:{name:e.name,selected:r}}function xV(t){t.registerComponentModel(uV),t.registerComponentView(dV),t.registerProcessor(t.PRIORITY.PROCESSOR.SERIES_FILTER,mV),t.registerSubTypeDefaulter(\"legend\",(function(){return\"plain\"})),function(t){t.registerAction(\"legendToggleSelect\",\"legendselectchanged\",B(_V,\"toggleSelected\")),t.registerAction(\"legendAllSelect\",\"legendselectall\",B(_V,\"allSelect\")),t.registerAction(\"legendInverseSelect\",\"legendinverseselect\",B(_V,\"inverseSelect\")),t.registerAction(\"legendSelect\",\"legendselected\",B(_V,\"select\")),t.registerAction(\"legendUnSelect\",\"legendunselected\",B(_V,\"unSelect\"))}(t)}var bV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.setScrollDataIndex=function(t){this.option.scrollDataIndex=t},e.prototype.init=function(e,n,i){var r=Hc(e);t.prototype.init.call(this,e,n,i),wV(this,e,r)},e.prototype.mergeOption=function(e,n){t.prototype.mergeOption.call(this,e,n),wV(this,this.option,e)},e.type=\"legend.scroll\",e.defaultOption=zh(uV.defaultOption,{scrollDataIndex:0,pageButtonItemGap:5,pageButtonGap:null,pageButtonPosition:\"end\",pageFormatter:\"{current}/{total}\",pageIcons:{horizontal:[\"M0,0L12,-10L12,10z\",\"M0,0L-12,-10L-12,10z\"],vertical:[\"M0,0L20,0L10,-20z\",\"M0,0L20,0L10,20z\"]},pageIconColor:\"#2f4554\",pageIconInactiveColor:\"#aaa\",pageIconSize:15,pageTextStyle:{color:\"#333\"},animationDurationUpdate:800}),e}(uV);function wV(t,e,n){var i=[1,1];i[t.getOrient().index]=0,Gc(e,n,{type:\"box\",ignoreSize:!!i})}var SV=Ei,MV=[\"width\",\"height\"],IV=[\"x\",\"y\"],TV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.newlineDisabled=!0,n._currentIndex=0,n}return n(e,t),e.prototype.init=function(){t.prototype.init.call(this),this.group.add(this._containerGroup=new SV),this._containerGroup.add(this.getContentGroup()),this.group.add(this._controllerGroup=new SV)},e.prototype.resetInner=function(){t.prototype.resetInner.call(this),this._controllerGroup.removeAll(),this._containerGroup.removeClipPath(),this._containerGroup.__rectSize=null},e.prototype.renderInner=function(e,n,i,r,o,a,s){var l=this;t.prototype.renderInner.call(this,e,n,i,r,o,a,s);var u=this._controllerGroup,h=n.get(\"pageIconSize\",!0),c=F(h)?h:[h,h];d(\"pagePrev\",0);var p=n.getModel(\"pageTextStyle\");function d(t,e){var i=t+\"DataIndex\",o=eh(n.get(\"pageIcons\",!0)[n.getOrient().name][e],{onclick:V(l._pageGo,l,i,n,r)},{x:-c[0]/2,y:-c[1]/2,width:c[0],height:c[1]});o.name=t,u.add(o)}u.add(new cs({name:\"pageText\",style:{text:\"xx/xx\",fill:p.getTextColor(),font:p.getFont(),verticalAlign:\"middle\",align:\"center\"},silent:!0})),d(\"pageNext\",1)},e.prototype.layoutInner=function(t,e,n,i,r,o){var a=this.getSelectorGroup(),s=t.getOrient().index,l=MV[s],u=IV[s],h=MV[1-s],c=IV[1-s];r&&Ec(\"horizontal\",a,t.get(\"selectorItemGap\",!0));var p=t.get(\"selectorButtonGap\",!0),d=a.getBoundingRect(),f=[-d.x,-d.y],g=w(n);r&&(g[l]=n[l]-d[l]-p);var y=this._layoutContentAndController(t,i,g,s,l,h,c,u);if(r){if(\"end\"===o)f[s]+=y[l]+p;else{var v=d[l]+p;f[s]-=v,y[u]-=v}y[l]+=d[l]+p,f[1-s]+=y[c]+y[h]/2-d[h]/2,y[h]=Math.max(y[h],d[h]),y[c]=Math.min(y[c],d[c]+f[1-s]),a.x=f[0],a.y=f[1],a.markRedraw()}return y},e.prototype._layoutContentAndController=function(t,e,n,i,r,o,a,s){var l=this.getContentGroup(),u=this._containerGroup,h=this._controllerGroup;Ec(t.get(\"orient\"),l,t.get(\"itemGap\"),i?n.width:null,i?null:n.height),Ec(\"horizontal\",h,t.get(\"pageButtonItemGap\",!0));var c=l.getBoundingRect(),p=h.getBoundingRect(),d=this._showController=c[r]>n[r],f=[-c.x,-c.y];e||(f[i]=l[s]);var g=[0,0],y=[-p.x,-p.y],v=tt(t.get(\"pageButtonGap\",!0),t.get(\"itemGap\",!0));d&&(\"end\"===t.get(\"pageButtonPosition\",!0)?y[i]+=n[r]-p[r]:g[i]+=p[r]+v);y[1-i]+=c[o]/2-p[o]/2,l.setPosition(f),u.setPosition(g),h.setPosition(y);var m={x:0,y:0};if(m[r]=d?n[r]:c[r],m[o]=Math.max(c[o],p[o]),m[a]=Math.min(0,p[a]+y[1-i]),u.__rectSize=n[r],d){var _={x:0,y:0};_[r]=Math.max(n[r]-p[r]-v,0),_[o]=m[o],u.setClipPath(new ls({shape:_})),u.__rectSize=_[r]}else h.eachChild((function(t){t.attr({invisible:!0,silent:!0})}));var x=this._getPageInfo(t);return null!=x.pageIndex&&Hu(l,{x:x.contentPosition[0],y:x.contentPosition[1]},d?t:null),this._updatePageInfoView(t,x),m},e.prototype._pageGo=function(t,e,n){var i=this._getPageInfo(e)[t];null!=i&&n.dispatchAction({type:\"legendScroll\",scrollDataIndex:i,legendId:e.id})},e.prototype._updatePageInfoView=function(t,e){var n=this._controllerGroup;P([\"pagePrev\",\"pageNext\"],(function(i){var r=null!=e[i+\"DataIndex\"],o=n.childOfName(i);o&&(o.setStyle(\"fill\",r?t.get(\"pageIconColor\",!0):t.get(\"pageIconInactiveColor\",!0)),o.cursor=r?\"pointer\":\"default\")}));var i=n.childOfName(\"pageText\"),r=t.get(\"pageFormatter\"),o=e.pageIndex,a=null!=o?o+1:0,s=e.pageCount;i&&r&&i.setStyle(\"text\",H(r)?r.replace(\"{current}\",null==a?\"\":a+\"\").replace(\"{total}\",null==s?\"\":s+\"\"):r({current:a,total:s}))},e.prototype._getPageInfo=function(t){var e=t.get(\"scrollDataIndex\",!0),n=this.getContentGroup(),i=this._containerGroup.__rectSize,r=t.getOrient().index,o=MV[r],a=IV[r],s=this._findTargetItemIndex(e),l=n.children(),u=l[s],h=l.length,c=h?1:0,p={contentPosition:[n.x,n.y],pageCount:c,pageIndex:c-1,pagePrevDataIndex:null,pageNextDataIndex:null};if(!u)return p;var d=m(u);p.contentPosition[r]=-d.s;for(var f=s+1,g=d,y=d,v=null;f<=h;++f)(!(v=m(l[f]))&&y.e>g.s+i||v&&!_(v,g.s))&&(g=y.i>g.i?y:v)&&(null==p.pageNextDataIndex&&(p.pageNextDataIndex=g.i),++p.pageCount),y=v;for(f=s-1,g=d,y=d,v=null;f>=-1;--f)(v=m(l[f]))&&_(y,v.s)||!(g.i<y.i)||(y=g,null==p.pagePrevDataIndex&&(p.pagePrevDataIndex=g.i),++p.pageCount,++p.pageIndex),g=v;return p;function m(t){if(t){var e=t.getBoundingRect(),n=e[a]+t[a];return{s:n,e:n+e[o],i:t.__legendDataIndex}}}function _(t,e){return t.e>=e&&t.s<=e+i}},e.prototype._findTargetItemIndex=function(t){return this._showController?(this.getContentGroup().eachChild((function(i,r){var o=i.__legendDataIndex;null==n&&null!=o&&(n=r),o===t&&(e=r)})),null!=e?e:n):0;var e,n},e.type=\"legend.scroll\",e}(dV);function CV(t){Qm(xV),t.registerComponentModel(bV),t.registerComponentView(TV),function(t){t.registerAction(\"legendScroll\",\"legendscroll\",(function(t,e){var n=t.scrollDataIndex;null!=n&&e.eachComponent({mainType:\"legend\",subType:\"scroll\",query:t},(function(t){t.setScrollDataIndex(n)}))}))}(t)}var DV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"dataZoom.inside\",e.defaultOption=zh(wN.defaultOption,{disabled:!1,zoomLock:!1,zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!1,preventDefaultMouseMove:!0}),e}(wN),AV=kr();function LV(t,e,n){AV(t).coordSysRecordMap.each((function(t){var i=t.dataZoomInfoMap.get(e.uid);i&&(i.getRange=n)}))}function kV(t,e){if(e){t.removeKey(e.model.uid);var n=e.controller;n&&n.dispose()}}function PV(t,e){t.dispatchAction({type:\"dataZoom\",animation:{easing:\"cubicOut\",duration:100},batch:e})}function OV(t,e,n,i){return t.coordinateSystem.containPoint([n,i])}function RV(t){t.registerProcessor(t.PRIORITY.PROCESSOR.FILTER,(function(t,e){var n=AV(e),i=n.coordSysRecordMap||(n.coordSysRecordMap=ht());i.each((function(t){t.dataZoomInfoMap=null})),t.eachComponent({mainType:\"dataZoom\",subType:\"inside\"},(function(t){P(xN(t).infoList,(function(n){var r=n.model.uid,o=i.get(r)||i.set(r,function(t,e){var n={model:e,containsPoint:B(OV,e),dispatchAction:B(PV,t),dataZoomInfoMap:null,controller:null},i=n.controller=new WM(t.getZr());return P([\"pan\",\"zoom\",\"scrollMove\"],(function(t){i.on(t,(function(e){var i=[];n.dataZoomInfoMap.each((function(r){if(e.isAvailableBehavior(r.model.option)){var o=(r.getRange||{})[t],a=o&&o(r.dzReferCoordSysInfo,n.model.mainType,n.controller,e);!r.model.get(\"disabled\",!0)&&a&&i.push({dataZoomId:r.model.id,start:a[0],end:a[1]})}})),i.length&&n.dispatchAction(i)}))})),n}(e,n.model));(o.dataZoomInfoMap||(o.dataZoomInfoMap=ht())).set(t.uid,{dzReferCoordSysInfo:n,model:t,getRange:null})}))})),i.each((function(t){var e,n=t.controller,r=t.dataZoomInfoMap;if(r){var o=r.keys()[0];null!=o&&(e=r.get(o))}if(e){var a=function(t){var e,n=\"type_\",i={type_true:2,type_move:1,type_false:0,type_undefined:-1},r=!0;return t.each((function(t){var o=t.model,a=!o.get(\"disabled\",!0)&&(!o.get(\"zoomLock\",!0)||\"move\");i[n+a]>i[n+e]&&(e=a),r=r&&o.get(\"preventDefaultMouseMove\",!0)})),{controlType:e,opt:{zoomOnMouseWheel:!0,moveOnMouseMove:!0,moveOnMouseWheel:!0,preventDefaultMouseMove:!!r}}}(r);n.enable(a.controlType,a.opt),n.setPointerChecker(t.containsPoint),zf(t,\"dispatchAction\",e.model.get(\"throttle\",!0),\"fixRate\")}else kV(i,t)}))}))}var NV=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"dataZoom.inside\",e}return n(e,t),e.prototype.render=function(e,n,i){t.prototype.render.apply(this,arguments),e.noTarget()?this._clear():(this.range=e.getPercentRange(),LV(i,e,{pan:V(zV.pan,this),zoom:V(zV.zoom,this),scrollMove:V(zV.scrollMove,this)}))},e.prototype.dispose=function(){this._clear(),t.prototype.dispose.apply(this,arguments)},e.prototype._clear=function(){!function(t,e){for(var n=AV(t).coordSysRecordMap,i=n.keys(),r=0;r<i.length;r++){var o=i[r],a=n.get(o),s=a.dataZoomInfoMap;if(s){var l=e.uid;s.get(l)&&(s.removeKey(l),s.keys().length||kV(n,a))}}}(this.api,this.dataZoomModel),this.range=null},e.type=\"dataZoom.inside\",e}(IN),zV={zoom:function(t,e,n,i){var r=this.range,o=r.slice(),a=t.axisModels[0];if(a){var s=VV[e](null,[i.originX,i.originY],a,n,t),l=(s.signal>0?s.pixelStart+s.pixelLength-s.pixel:s.pixel-s.pixelStart)/s.pixelLength*(o[1]-o[0])+o[0],u=Math.max(1/i.scale,0);o[0]=(o[0]-l)*u+l,o[1]=(o[1]-l)*u+l;var h=this.dataZoomModel.findRepresentativeAxisProxy().getMinMaxSpan();return PD(0,o,[0,100],0,h.minSpan,h.maxSpan),this.range=o,r[0]!==o[0]||r[1]!==o[1]?o:void 0}},pan:EV((function(t,e,n,i,r,o){var a=VV[i]([o.oldX,o.oldY],[o.newX,o.newY],e,r,n);return a.signal*(t[1]-t[0])*a.pixel/a.pixelLength})),scrollMove:EV((function(t,e,n,i,r,o){return VV[i]([0,0],[o.scrollDelta,o.scrollDelta],e,r,n).signal*(t[1]-t[0])*o.scrollDelta}))};function EV(t){return function(e,n,i,r){var o=this.range,a=o.slice(),s=e.axisModels[0];if(s)return PD(t(a,s,e,n,i,r),a,[0,100],\"all\"),this.range=a,o[0]!==a[0]||o[1]!==a[1]?a:void 0}}var VV={grid:function(t,e,n,i,r){var o=n.axis,a={},s=r.model.coordinateSystem.getRect();return t=t||[0,0],\"x\"===o.dim?(a.pixel=e[0]-t[0],a.pixelLength=s.width,a.pixelStart=s.x,a.signal=o.inverse?1:-1):(a.pixel=e[1]-t[1],a.pixelLength=s.height,a.pixelStart=s.y,a.signal=o.inverse?-1:1),a},polar:function(t,e,n,i,r){var o=n.axis,a={},s=r.model.coordinateSystem,l=s.getRadiusAxis().getExtent(),u=s.getAngleAxis().getExtent();return t=t?s.pointToCoord(t):[0,0],e=s.pointToCoord(e),\"radiusAxis\"===n.mainType?(a.pixel=e[0]-t[0],a.pixelLength=l[1]-l[0],a.pixelStart=l[0],a.signal=o.inverse?1:-1):(a.pixel=e[1]-t[1],a.pixelLength=u[1]-u[0],a.pixelStart=u[0],a.signal=o.inverse?-1:1),a},singleAxis:function(t,e,n,i,r){var o=n.axis,a=r.model.coordinateSystem.getRect(),s={};return t=t||[0,0],\"horizontal\"===o.orient?(s.pixel=e[0]-t[0],s.pixelLength=a.width,s.pixelStart=a.x,s.signal=o.inverse?1:-1):(s.pixel=e[1]-t[1],s.pixelLength=a.height,s.pixelStart=a.y,s.signal=o.inverse?-1:1),s}};function BV(t){PN(t),t.registerComponentModel(DV),t.registerComponentView(NV),RV(t)}var FV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.type=\"dataZoom.slider\",e.layoutMode=\"box\",e.defaultOption=zh(wN.defaultOption,{show:!0,right:\"ph\",top:\"ph\",width:\"ph\",height:\"ph\",left:null,bottom:null,borderColor:\"#d2dbee\",borderRadius:3,backgroundColor:\"rgba(47,69,84,0)\",dataBackground:{lineStyle:{color:\"#d2dbee\",width:.5},areaStyle:{color:\"#d2dbee\",opacity:.2}},selectedDataBackground:{lineStyle:{color:\"#8fb0f7\",width:.5},areaStyle:{color:\"#8fb0f7\",opacity:.2}},fillerColor:\"rgba(135,175,274,0.2)\",handleIcon:\"path://M-9.35,34.56V42m0-40V9.5m-2,0h4a2,2,0,0,1,2,2v21a2,2,0,0,1-2,2h-4a2,2,0,0,1-2-2v-21A2,2,0,0,1-11.35,9.5Z\",handleSize:\"100%\",handleStyle:{color:\"#fff\",borderColor:\"#ACB8D1\"},moveHandleSize:7,moveHandleIcon:\"path://M-320.9-50L-320.9-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-348-41-339-50-320.9-50z M-212.3-50L-212.3-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-239.4-41-230.4-50-212.3-50z M-103.7-50L-103.7-50c18.1,0,27.1,9,27.1,27.1V85.7c0,18.1-9,27.1-27.1,27.1l0,0c-18.1,0-27.1-9-27.1-27.1V-22.9C-130.9-41-121.8-50-103.7-50z\",moveHandleStyle:{color:\"#D2DBEE\",opacity:.7},showDetail:!0,showDataShadow:\"auto\",realtime:!0,zoomLock:!1,textStyle:{color:\"#6E7079\"},brushSelect:!0,brushStyle:{color:\"rgba(135,175,274,0.15)\"},emphasis:{handleStyle:{borderColor:\"#8FB0F7\"},moveHandleStyle:{color:\"#8FB0F7\"}}}),e}(wN),GV=ls,HV=\"horizontal\",WV=\"vertical\",UV=[\"line\",\"bar\",\"candlestick\",\"scatter\"],XV={easing:\"cubicOut\",duration:100},YV=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._displayables={},n}return n(e,t),e.prototype.init=function(t,e){this.api=e,this._onBrush=V(this._onBrush,this),this._onBrushEnd=V(this._onBrushEnd,this)},e.prototype.render=function(e,n,i,r){if(t.prototype.render.apply(this,arguments),zf(this,\"_dispatchZoomAction\",e.get(\"throttle\"),\"fixRate\"),this._orient=e.getOrient(),!1!==e.get(\"show\")){if(e.noTarget())return this._clear(),void this.group.removeAll();r&&\"dataZoom\"===r.type&&r.from===this.uid||this._buildView(),this._updateView()}else this.group.removeAll()},e.prototype.dispose=function(){this._clear(),t.prototype.dispose.apply(this,arguments)},e.prototype._clear=function(){var t,e,n;(n=(t=this)[e=\"_dispatchZoomAction\"])&&n[Pf]&&(t[e]=n[Pf]);var i=this.api.getZr();i.off(\"mousemove\",this._onBrush),i.off(\"mouseup\",this._onBrushEnd)},e.prototype._buildView=function(){var t=this.group;t.removeAll(),this._brushing=!1,this._displayables.brushRect=null,this._resetLocation(),this._resetInterval();var e=this._displayables.sliderGroup=new Ei;this._renderBackground(),this._renderHandle(),this._renderDataShadow(),t.add(e),this._positionGroup()},e.prototype._resetLocation=function(){var t=this.dataZoomModel,e=this.api,n=t.get(\"brushSelect\")?7:0,i=this._findCoordRect(),r={width:e.getWidth(),height:e.getHeight()},o=this._orient===HV?{right:r.width-i.x-i.width,top:r.height-30-7-n,width:i.width,height:30}:{right:7,top:i.y,width:30,height:i.height},a=Hc(t.option);P([\"right\",\"top\",\"width\",\"height\"],(function(t){\"ph\"===a[t]&&(a[t]=o[t])}));var s=Vc(a,r);this._location={x:s.x,y:s.y},this._size=[s.width,s.height],this._orient===WV&&this._size.reverse()},e.prototype._positionGroup=function(){var t=this.group,e=this._location,n=this._orient,i=this.dataZoomModel.getFirstTargetAxisModel(),r=i&&i.get(\"inverse\"),o=this._displayables.sliderGroup,a=(this._dataShadowInfo||{}).otherAxisInverse;o.attr(n!==HV||r?n===HV&&r?{scaleY:a?1:-1,scaleX:-1}:n!==WV||r?{scaleY:a?-1:1,scaleX:-1,rotation:Math.PI/2}:{scaleY:a?-1:1,scaleX:1,rotation:Math.PI/2}:{scaleY:a?1:-1,scaleX:1});var s=t.getBoundingRect([o]);t.x=e.x-s.x,t.y=e.y-s.y,t.markRedraw()},e.prototype._getViewExtent=function(){return[0,this._size[0]]},e.prototype._renderBackground=function(){var t=this.dataZoomModel,e=this._size,n=this._displayables.sliderGroup,i=t.get(\"brushSelect\");n.add(new GV({silent:!0,shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:t.get(\"backgroundColor\")},z2:-40}));var r=new GV({shape:{x:0,y:0,width:e[0],height:e[1]},style:{fill:\"transparent\"},z2:0,onclick:V(this._onClickPanel,this)}),o=this.api.getZr();i?(r.on(\"mousedown\",this._onBrushStart,this),r.cursor=\"crosshair\",o.on(\"mousemove\",this._onBrush),o.on(\"mouseup\",this._onBrushEnd)):(o.off(\"mousemove\",this._onBrush),o.off(\"mouseup\",this._onBrushEnd)),n.add(r)},e.prototype._renderDataShadow=function(){var t=this._dataShadowInfo=this._prepareDataShadowInfo();if(this._displayables.dataShadowSegs=[],t){var e=this._size,n=t.series,i=n.getRawData(),r=n.getShadowDim?n.getShadowDim():t.otherDim;if(null!=r){var o=i.getDataExtent(r),a=.3*(o[1]-o[0]);o=[o[0]-a,o[1]+a];var s,l=[0,e[1]],u=[0,e[0]],h=[[e[0],0],[0,0]],c=[],p=u[1]/(i.count()-1),d=0,f=Math.round(i.count()/e[0]);i.each([r],(function(t,e){if(f>0&&e%f)d+=p;else{var n=null==t||isNaN(t)||\"\"===t,i=n?0:Yi(t,o,l,!0);n&&!s&&e?(h.push([h[h.length-1][0],0]),c.push([c[c.length-1][0],0])):!n&&s&&(h.push([d,0]),c.push([d,0])),h.push([d,i]),c.push([d,i]),d+=p,s=n}}));for(var g=this.dataZoomModel,y=0;y<3;y++){var v=m(1===y);this._displayables.sliderGroup.add(v),this._displayables.dataShadowSegs.push(v)}}}function m(t){var e=g.getModel(t?\"selectedDataBackground\":\"dataBackground\"),n=new Ei,i=new ru({shape:{points:h},segmentIgnoreThreshold:1,style:e.getModel(\"areaStyle\").getAreaStyle(),silent:!0,z2:-20}),r=new au({shape:{points:c},segmentIgnoreThreshold:1,style:e.getModel(\"lineStyle\").getLineStyle(),silent:!0,z2:-19});return n.add(i),n.add(r),n}},e.prototype._prepareDataShadowInfo=function(){var t=this.dataZoomModel,e=t.get(\"showDataShadow\");if(!1!==e){var n,i=this.ecModel;return t.eachTargetAxis((function(r,o){P(t.getAxisProxy(r,o).getTargetSeriesModels(),(function(t){if(!(n||!0!==e&&D(UV,t.get(\"type\"))<0)){var a,s=i.getComponent(mN(r),o).axis,l={x:\"y\",y:\"x\",radius:\"angle\",angle:\"radius\"}[r],u=t.coordinateSystem;null!=l&&u.getOtherAxis&&(a=u.getOtherAxis(s).inverse),l=t.getData().mapDimension(l),n={thisAxis:s,series:t,thisDim:r,otherDim:l,otherAxisInverse:a}}}),this)}),this),n}},e.prototype._renderHandle=function(){var t=this.group,e=this._displayables,n=e.handles=[null,null],i=e.handleLabels=[null,null],r=this._displayables.sliderGroup,o=this._size,a=this.dataZoomModel,s=this.api,l=a.get(\"borderRadius\")||0,u=a.get(\"brushSelect\"),h=e.filler=new GV({silent:u,style:{fill:a.get(\"fillerColor\")},textConfig:{position:\"inside\"}});r.add(h),r.add(new GV({silent:!0,subPixelOptimize:!0,shape:{x:0,y:0,width:o[0],height:o[1],r:l},style:{stroke:a.get(\"dataBackgroundColor\")||a.get(\"borderColor\"),lineWidth:1,fill:\"rgba(0,0,0,0)\"}})),P([0,1],(function(e){var o=a.get(\"handleIcon\");!cy[o]&&o.indexOf(\"path://\")<0&&o.indexOf(\"image://\")<0&&(o=\"path://\"+o);var s=fy(o,-1,0,2,2,null,!0);s.attr({cursor:ZV(this._orient),draggable:!0,drift:V(this._onDragMove,this,e),ondragend:V(this._onDragEnd,this),onmouseover:V(this._showDataInfo,this,!0),onmouseout:V(this._showDataInfo,this,!1),z2:5});var l=s.getBoundingRect(),u=a.get(\"handleSize\");this._handleHeight=Zi(u,this._size[1]),this._handleWidth=l.width/l.height*this._handleHeight,s.setStyle(a.getModel(\"handleStyle\").getItemStyle()),s.style.strokeNoScale=!0,s.rectHover=!0,s.ensureState(\"emphasis\").style=a.getModel([\"emphasis\",\"handleStyle\"]).getItemStyle(),sl(s);var h=a.get(\"handleColor\");null!=h&&(s.style.fill=h),r.add(n[e]=s);var c=a.getModel(\"textStyle\");t.add(i[e]=new cs({silent:!0,invisible:!0,style:ph(c,{x:0,y:0,text:\"\",verticalAlign:\"middle\",align:\"center\",fill:c.getTextColor(),font:c.getFont()}),z2:10}))}),this);var c=h;if(u){var p=Zi(a.get(\"moveHandleSize\"),o[1]),d=e.moveHandle=new ls({style:a.getModel(\"moveHandleStyle\").getItemStyle(),silent:!0,shape:{r:[0,0,2,2],y:o[1]-.5,height:p}}),f=.8*p,g=e.moveHandleIcon=fy(a.get(\"moveHandleIcon\"),-f/2,-f/2,f,f,\"#fff\",!0);g.silent=!0,g.y=o[1]+p/2-.5,d.ensureState(\"emphasis\").style=a.getModel([\"emphasis\",\"moveHandleStyle\"]).getItemStyle();var y=Math.min(o[1]/2,Math.max(p,10));(c=e.moveZone=new ls({invisible:!0,shape:{y:o[1]-y,height:p+y}})).on(\"mouseover\",(function(){s.enterEmphasis(d)})).on(\"mouseout\",(function(){s.leaveEmphasis(d)})),r.add(d),r.add(g),r.add(c)}c.attr({draggable:!0,cursor:ZV(this._orient),drift:V(this._onDragMove,this,\"all\"),ondragstart:V(this._showDataInfo,this,!0),ondragend:V(this._onDragEnd,this),onmouseover:V(this._showDataInfo,this,!0),onmouseout:V(this._showDataInfo,this,!1)})},e.prototype._resetInterval=function(){var t=this._range=this.dataZoomModel.getPercentRange(),e=this._getViewExtent();this._handleEnds=[Yi(t[0],[0,100],e,!0),Yi(t[1],[0,100],e,!0)]},e.prototype._updateInterval=function(t,e){var n=this.dataZoomModel,i=this._handleEnds,r=this._getViewExtent(),o=n.findRepresentativeAxisProxy().getMinMaxSpan(),a=[0,100];PD(e,i,r,n.get(\"zoomLock\")?\"all\":t,null!=o.minSpan?Yi(o.minSpan,a,r,!0):null,null!=o.maxSpan?Yi(o.maxSpan,a,r,!0):null);var s=this._range,l=this._range=qi([Yi(i[0],r,a,!0),Yi(i[1],r,a,!0)]);return!s||s[0]!==l[0]||s[1]!==l[1]},e.prototype._updateView=function(t){var e=this._displayables,n=this._handleEnds,i=qi(n.slice()),r=this._size;P([0,1],(function(t){var i=e.handles[t],o=this._handleHeight;i.attr({scaleX:o/2,scaleY:o/2,x:n[t]+(t?-1:1),y:r[1]/2-o/2})}),this),e.filler.setShape({x:i[0],y:0,width:i[1]-i[0],height:r[1]});var o={x:i[0],width:i[1]-i[0]};e.moveHandle&&(e.moveHandle.setShape(o),e.moveZone.setShape(o),e.moveZone.getBoundingRect(),e.moveHandleIcon&&e.moveHandleIcon.attr(\"x\",o.x+o.width/2));for(var a=e.dataShadowSegs,s=[0,i[0],i[1],r[0]],l=0;l<a.length;l++){var u=a[l],h=u.getClipPath();h||(h=new ls,u.setClipPath(h)),h.setShape({x:s[l],y:0,width:s[l+1]-s[l],height:r[1]})}this._updateDataInfo(t)},e.prototype._updateDataInfo=function(t){var e=this.dataZoomModel,n=this._displayables,i=n.handleLabels,r=this._orient,o=[\"\",\"\"];if(e.get(\"showDetail\")){var a=e.findRepresentativeAxisProxy();if(a){var s=a.getAxisModel().axis,l=this._range,u=t?a.calculateDataWindow({start:l[0],end:l[1]}).valueWindow:a.getDataValueWindow();o=[this._formatLabel(u[0],s),this._formatLabel(u[1],s)]}}var h=qi(this._handleEnds.slice());function c(t){var e=ju(n.handles[t].parent,this.group),a=Ku(0===t?\"right\":\"left\",e),s=this._handleWidth/2+5,l=qu([h[t]+(0===t?-s:s),this._size[1]/2],e);i[t].setStyle({x:l[0],y:l[1],verticalAlign:r===HV?\"middle\":a,align:r===HV?a:\"center\",text:o[t]})}c.call(this,0),c.call(this,1)},e.prototype._formatLabel=function(t,e){var n=this.dataZoomModel,i=n.get(\"labelFormatter\"),r=n.get(\"labelPrecision\");null!=r&&\"auto\"!==r||(r=e.getPixelPrecision());var o=null==t||isNaN(t)?\"\":\"category\"===e.type||\"time\"===e.type?e.scale.getLabel({value:Math.round(t)}):t.toFixed(Math.min(r,20));return G(i)?i(t,o):H(i)?i.replace(\"{value}\",o):o},e.prototype._showDataInfo=function(t){t=this._dragging||t;var e=this._displayables,n=e.handleLabels;n[0].attr(\"invisible\",!t),n[1].attr(\"invisible\",!t),e.moveHandle&&this.api[t?\"enterEmphasis\":\"leaveEmphasis\"](e.moveHandle,1)},e.prototype._onDragMove=function(t,e,n,i){this._dragging=!0,ee(i.event);var r=qu([e,n],this._displayables.sliderGroup.getLocalTransform(),!0),o=this._updateInterval(t,r[0]),a=this.dataZoomModel.get(\"realtime\");this._updateView(!a),o&&a&&this._dispatchZoomAction(!0)},e.prototype._onDragEnd=function(){this._dragging=!1,this._showDataInfo(!1),!this.dataZoomModel.get(\"realtime\")&&this._dispatchZoomAction(!1)},e.prototype._onClickPanel=function(t){var e=this._size,n=this._displayables.sliderGroup.transformCoordToLocal(t.offsetX,t.offsetY);if(!(n[0]<0||n[0]>e[0]||n[1]<0||n[1]>e[1])){var i=this._handleEnds,r=(i[0]+i[1])/2,o=this._updateInterval(\"all\",n[0]-r);this._updateView(),o&&this._dispatchZoomAction(!1)}},e.prototype._onBrushStart=function(t){var e=t.offsetX,n=t.offsetY;this._brushStart=new ai(e,n),this._brushing=!0,this._brushStartTime=+new Date},e.prototype._onBrushEnd=function(t){if(this._brushing){var e=this._displayables.brushRect;if(this._brushing=!1,e){e.attr(\"ignore\",!0);var n=e.shape;if(!(+new Date-this._brushStartTime<200&&Math.abs(n.width)<5)){var i=this._getViewExtent(),r=[0,100];this._range=qi([Yi(n.x,i,r,!0),Yi(n.x+n.width,i,r,!0)]),this._handleEnds=[n.x,n.x+n.width],this._updateView(),this._dispatchZoomAction(!1)}}}},e.prototype._onBrush=function(t){this._brushing&&(ee(t.event),this._updateBrushRect(t.offsetX,t.offsetY))},e.prototype._updateBrushRect=function(t,e){var n=this._displayables,i=this.dataZoomModel,r=n.brushRect;r||(r=n.brushRect=new GV({silent:!0,style:i.getModel(\"brushStyle\").getItemStyle()}),n.sliderGroup.add(r)),r.attr(\"ignore\",!1);var o=this._brushStart,a=this._displayables.sliderGroup,s=a.transformCoordToLocal(t,e),l=a.transformCoordToLocal(o.x,o.y),u=this._size;s[0]=Math.max(Math.min(u[0],s[0]),0),r.setShape({x:l[0],y:0,width:s[0]-l[0],height:u[1]})},e.prototype._dispatchZoomAction=function(t){var e=this._range;this.api.dispatchAction({type:\"dataZoom\",from:this.uid,dataZoomId:this.dataZoomModel.id,animation:t?XV:null,start:e[0],end:e[1]})},e.prototype._findCoordRect=function(){var t,e=xN(this.dataZoomModel).infoList;if(!t&&e.length){var n=e[0].model.coordinateSystem;t=n.getRect&&n.getRect()}if(!t){var i=this.api.getWidth(),r=this.api.getHeight();t={x:.2*i,y:.2*r,width:.6*i,height:.6*r}}return t},e.type=\"dataZoom.slider\",e}(IN);function ZV(t){return\"vertical\"===t?\"ns-resize\":\"ew-resize\"}function jV(t){t.registerComponentModel(FV),t.registerComponentView(YV),PN(t)}var qV=function(t,e,n){var i=w((KV[t]||{})[e]);return n&&F(i)?i[i.length-1]:i},KV={color:{active:[\"#006edd\",\"#e0ffff\"],inactive:[\"rgba(0,0,0,0)\"]},colorHue:{active:[0,360],inactive:[0,0]},colorSaturation:{active:[.3,1],inactive:[0,0]},colorLightness:{active:[.9,.5],inactive:[0,0]},colorAlpha:{active:[.3,1],inactive:[0,0]},opacity:{active:[.3,1],inactive:[0,0]},symbol:{active:[\"circle\",\"roundRect\",\"diamond\"],inactive:[\"none\"]},symbolSize:{active:[10,50],inactive:[0,0]}},$V=TT.mapVisual,JV=TT.eachVisual,QV=F,tB=P,eB=qi,nB=Yi,iB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.stateList=[\"inRange\",\"outOfRange\"],n.replacableOptionKeys=[\"inRange\",\"outOfRange\",\"target\",\"controller\",\"color\"],n.layoutMode={type:\"box\",ignoreSize:!0},n.dataBound=[-1/0,1/0],n.targetVisuals={},n.controllerVisuals={},n}return n(e,t),e.prototype.init=function(t,e,n){this.mergeDefaultAndTheme(t,n)},e.prototype.optionUpdated=function(t,e){var n=this.option;a.canvasSupported||(n.realtime=!1),!e&&Jz(n,t,this.replacableOptionKeys),this.textStyleModel=this.getModel(\"textStyle\"),this.resetItemSize(),this.completeVisualOption()},e.prototype.resetVisual=function(t){var e=this.stateList;t=V(t,this),this.controllerVisuals=$z(this.option.controller,e,t),this.targetVisuals=$z(this.option.target,e,t)},e.prototype.getItemSymbol=function(){return null},e.prototype.getTargetSeriesIndices=function(){var t=this.option.seriesIndex,e=[];return null==t||\"all\"===t?this.ecModel.eachSeries((function(t,n){e.push(n)})):e=xr(t),e},e.prototype.eachTargetSeries=function(t,e){P(this.getTargetSeriesIndices(),(function(n){var i=this.ecModel.getSeriesByIndex(n);i&&t.call(e,i)}),this)},e.prototype.isTargetSeries=function(t){var e=!1;return this.eachTargetSeries((function(n){n===t&&(e=!0)})),e},e.prototype.formatValueText=function(t,e,n){var i,r=this.option,o=r.precision,a=this.dataBound,s=r.formatter;n=n||[\"<\",\">\"],F(t)&&(t=t.slice(),i=!0);var l=e?t:i?[u(t[0]),u(t[1])]:u(t);return H(s)?s.replace(\"{value}\",i?l[0]:l).replace(\"{value2}\",i?l[1]:l):G(s)?i?s(t[0],t[1]):s(t):i?t[0]===a[0]?n[0]+\" \"+l[1]:t[1]===a[1]?n[1]+\" \"+l[0]:l[0]+\" - \"+l[1]:l;function u(t){return t===a[0]?\"min\":t===a[1]?\"max\":(+t).toFixed(Math.min(o,20))}},e.prototype.resetExtent=function(){var t=this.option,e=eB([t.min,t.max]);this._dataExtent=e},e.prototype.getDataDimension=function(t){var e=this.option.dimension,n=t.dimensions;if(null!=e||n.length){if(null!=e)return t.getDimension(e);for(var i=t.dimensions,r=i.length-1;r>=0;r--){var o=i[r];if(!t.getDimensionInfo(o).isCalculationCoord)return o}}},e.prototype.getExtent=function(){return this._dataExtent.slice()},e.prototype.completeVisualOption=function(){var t=this.ecModel,e=this.option,n={inRange:e.inRange,outOfRange:e.outOfRange},i=e.target||(e.target={}),r=e.controller||(e.controller={});S(i,n),S(r,n);var o=this.isCategory();function a(n){QV(e.color)&&!n.inRange&&(n.inRange={color:e.color.slice().reverse()}),n.inRange=n.inRange||{color:t.get(\"gradientColor\")}}a.call(this,i),a.call(this,r),function(t,e,n){var i=t[e],r=t[n];i&&!r&&(r=t[n]={},tB(i,(function(t,e){if(TT.isValidType(e)){var n=qV(e,\"inactive\",o);null!=n&&(r[e]=n,\"color\"!==e||r.hasOwnProperty(\"opacity\")||r.hasOwnProperty(\"colorAlpha\")||(r.opacity=[0,0]))}})))}.call(this,i,\"inRange\",\"outOfRange\"),function(t){var e=(t.inRange||{}).symbol||(t.outOfRange||{}).symbol,n=(t.inRange||{}).symbolSize||(t.outOfRange||{}).symbolSize,i=this.get(\"inactiveColor\"),r=this.getItemSymbol()||\"roundRect\";tB(this.stateList,(function(a){var s=this.itemSize,l=t[a];l||(l=t[a]={color:o?i:[i]}),null==l.symbol&&(l.symbol=e&&w(e)||(o?r:[r])),null==l.symbolSize&&(l.symbolSize=n&&w(n)||(o?s[0]:[s[0],s[0]])),l.symbol=$V(l.symbol,(function(t){return\"none\"===t?r:t}));var u=l.symbolSize;if(null!=u){var h=-1/0;JV(u,(function(t){t>h&&(h=t)})),l.symbolSize=$V(u,(function(t){return nB(t,[0,h],[0,s[0]],!0)}))}}),this)}.call(this,r)},e.prototype.resetItemSize=function(){this.itemSize=[parseFloat(this.get(\"itemWidth\")),parseFloat(this.get(\"itemHeight\"))]},e.prototype.isCategory=function(){return!!this.option.categories},e.prototype.setSelected=function(t){},e.prototype.getSelected=function(){return null},e.prototype.getValueState=function(t){return null},e.prototype.getVisualMeta=function(t){return null},e.type=\"visualMap\",e.dependencies=[\"series\"],e.defaultOption={show:!0,zlevel:0,z:4,seriesIndex:\"all\",min:0,max:200,left:0,right:null,top:null,bottom:0,itemWidth:null,itemHeight:null,inverse:!1,orient:\"vertical\",backgroundColor:\"rgba(0,0,0,0)\",borderColor:\"#ccc\",contentColor:\"#5793f3\",inactiveColor:\"#aaa\",borderWidth:0,padding:5,textGap:10,precision:0,textStyle:{color:\"#333\"}},e}(Xc),rB=[20,140],oB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.optionUpdated=function(e,n){t.prototype.optionUpdated.apply(this,arguments),this.resetExtent(),this.resetVisual((function(t){t.mappingMethod=\"linear\",t.dataExtent=this.getExtent()})),this._resetRange()},e.prototype.resetItemSize=function(){t.prototype.resetItemSize.apply(this,arguments);var e=this.itemSize;(null==e[0]||isNaN(e[0]))&&(e[0]=rB[0]),(null==e[1]||isNaN(e[1]))&&(e[1]=rB[1])},e.prototype._resetRange=function(){var t=this.getExtent(),e=this.option.range;!e||e.auto?(t.auto=1,this.option.range=t):F(e)&&(e[0]>e[1]&&e.reverse(),e[0]=Math.max(e[0],t[0]),e[1]=Math.min(e[1],t[1]))},e.prototype.completeVisualOption=function(){t.prototype.completeVisualOption.apply(this,arguments),P(this.stateList,(function(t){var e=this.option.controller[t].symbolSize;e&&e[0]!==e[1]&&(e[0]=e[1]/3)}),this)},e.prototype.setSelected=function(t){this.option.range=t.slice(),this._resetRange()},e.prototype.getSelected=function(){var t=this.getExtent(),e=qi((this.get(\"range\")||[]).slice());return e[0]>t[1]&&(e[0]=t[1]),e[1]>t[1]&&(e[1]=t[1]),e[0]<t[0]&&(e[0]=t[0]),e[1]<t[0]&&(e[1]=t[0]),e},e.prototype.getValueState=function(t){var e=this.option.range,n=this.getExtent();return(e[0]<=n[0]||e[0]<=t)&&(e[1]>=n[1]||t<=e[1])?\"inRange\":\"outOfRange\"},e.prototype.findTargetDataIndices=function(t){var e=[];return this.eachTargetSeries((function(n){var i=[],r=n.getData();r.each(this.getDataDimension(r),(function(e,n){t[0]<=e&&e<=t[1]&&i.push(n)}),this),e.push({seriesId:n.id,dataIndex:i})}),this),e},e.prototype.getVisualMeta=function(t){var e=aB(this,\"outOfRange\",this.getExtent()),n=aB(this,\"inRange\",this.option.range.slice()),i=[];function r(e,n){i.push({value:e,color:t(e,n)})}for(var o=0,a=0,s=n.length,l=e.length;a<l&&(!n.length||e[a]<=n[0]);a++)e[a]<n[o]&&r(e[a],\"outOfRange\");for(var u=1;o<s;o++,u=0)u&&i.length&&r(n[o],\"outOfRange\"),r(n[o],\"inRange\");for(u=1;a<l;a++)(!n.length||n[n.length-1]<e[a])&&(u&&(i.length&&r(i[i.length-1].value,\"outOfRange\"),u=0),r(e[a],\"outOfRange\"));var h=i.length;return{stops:i,outerColors:[h?i[0].color:\"transparent\",h?i[h-1].color:\"transparent\"]}},e.type=\"visualMap.continuous\",e.defaultOption=zh(iB.defaultOption,{align:\"auto\",calculable:!1,hoverLink:!0,realtime:!0,handleIcon:\"path://M-11.39,9.77h0a3.5,3.5,0,0,1-3.5,3.5h-22a3.5,3.5,0,0,1-3.5-3.5h0a3.5,3.5,0,0,1,3.5-3.5h22A3.5,3.5,0,0,1-11.39,9.77Z\",handleSize:\"120%\",handleStyle:{borderColor:\"#fff\",borderWidth:1},indicatorIcon:\"circle\",indicatorSize:\"50%\",indicatorStyle:{borderColor:\"#fff\",borderWidth:2,shadowBlur:2,shadowOffsetX:1,shadowOffsetY:1,shadowColor:\"rgba(0,0,0,0.2)\"}}),e}(iB);function aB(t,e,n){if(n[0]===n[1])return n.slice();for(var i=(n[1]-n[0])/200,r=n[0],o=[],a=0;a<=200&&r<n[1];a++)o.push(r),r+=i;return o.push(n[1]),o}var sB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n.autoPositionValues={left:1,right:1,top:1,bottom:1},n}return n(e,t),e.prototype.init=function(t,e){this.ecModel=t,this.api=e},e.prototype.render=function(t,e,n,i){this.visualMapModel=t,!1!==t.get(\"show\")?this.doRender(t,e,n,i):this.group.removeAll()},e.prototype.renderBackground=function(t){var e=this.visualMapModel,n=wc(e.get(\"padding\")||0),i=t.getBoundingRect();t.add(new ls({z2:-1,silent:!0,shape:{x:i.x-n[3],y:i.y-n[0],width:i.width+n[3]+n[1],height:i.height+n[0]+n[2]},style:{fill:e.get(\"backgroundColor\"),stroke:e.get(\"borderColor\"),lineWidth:e.get(\"borderWidth\")}}))},e.prototype.getControllerVisual=function(t,e,n){var i=(n=n||{}).forceState,r=this.visualMapModel,o={};if(\"color\"===e){var a=r.get(\"contentColor\");o.color=a}function s(t){return o[t]}function l(t,e){o[t]=e}var u=r.controllerVisuals[i||r.getValueState(t)];return P(TT.prepareVisualTypes(u),(function(i){var r=u[i];n.convertOpacityToAlpha&&\"opacity\"===i&&(i=\"colorAlpha\",r=u.__alphaForOpacity),TT.dependsOn(i,e)&&r&&r.applyVisual(t,s,l)})),o[e]},e.prototype.positionGroup=function(t){var e=this.visualMapModel,n=this.api;Bc(t,e.getBoxLayoutParams(),{width:n.getWidth(),height:n.getHeight()})},e.prototype.doRender=function(t,e,n,i){},e.type=\"visualMap\",e}(wf),lB=[[\"left\",\"right\",\"width\"],[\"top\",\"bottom\",\"height\"]];function uB(t,e,n){var i=t.option,r=i.align;if(null!=r&&\"auto\"!==r)return r;for(var o={width:e.getWidth(),height:e.getHeight()},a=\"horizontal\"===i.orient?1:0,s=lB[a],l=[0,null,10],u={},h=0;h<3;h++)u[lB[1-a][h]]=l[h],u[s[h]]=2===h?n[0]:i[s[h]];var c=[[\"x\",\"width\",3],[\"y\",\"height\",0]][a],p=Vc(u,o,i.padding);return s[(p.margin[c[2]]||0)+p[c[0]]+.5*p[c[1]]<.5*o[c[1]]?0:1]}function hB(t,e){return P(t||[],(function(t){null!=t.dataIndex&&(t.dataIndexInside=t.dataIndex,t.dataIndex=null),t.highlightKey=\"visualMap\"+(e?e.componentIndex:\"\")})),t}var cB=Yi,pB=P,dB=Math.min,fB=Math.max,gB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._shapes={},n._dataInterval=[],n._handleEnds=[],n._hoverLinkDataIndices=[],n}return n(e,t),e.prototype.doRender=function(t,e,n,i){this._api=n,i&&\"selectDataRange\"===i.type&&i.from===this.uid||this._buildView()},e.prototype._buildView=function(){this.group.removeAll();var t=this.visualMapModel,e=this.group;this._orient=t.get(\"orient\"),this._useHandle=t.get(\"calculable\"),this._resetInterval(),this._renderBar(e);var n=t.get(\"text\");this._renderEndsText(e,n,0),this._renderEndsText(e,n,1),this._updateView(!0),this.renderBackground(e),this._updateView(),this._enableHoverLinkToSeries(),this._enableHoverLinkFromSeries(),this.positionGroup(e)},e.prototype._renderEndsText=function(t,e,n){if(e){var i=e[1-n];i=null!=i?i+\"\":\"\";var r=this.visualMapModel,o=r.get(\"textGap\"),a=r.itemSize,s=this._shapes.mainGroup,l=this._applyTransform([a[0]/2,0===n?-o:a[1]+o],s),u=this._applyTransform(0===n?\"bottom\":\"top\",s),h=this._orient,c=this.visualMapModel.textStyleModel;this.group.add(new cs({style:{x:l[0],y:l[1],verticalAlign:\"horizontal\"===h?\"middle\":u,align:\"horizontal\"===h?u:\"center\",text:i,font:c.getFont(),fill:c.getTextColor()}}))}},e.prototype._renderBar=function(t){var e=this.visualMapModel,n=this._shapes,i=e.itemSize,r=this._orient,o=this._useHandle,a=uB(e,this.api,i),s=n.mainGroup=this._createBarGroup(a),l=new Ei;s.add(l),l.add(n.outOfRange=yB()),l.add(n.inRange=yB(null,o?mB(this._orient):null,V(this._dragHandle,this,\"all\",!1),V(this._dragHandle,this,\"all\",!0))),l.setClipPath(new ls({shape:{x:0,y:0,width:i[0],height:i[1],r:3}}));var u=e.textStyleModel.getTextRect(\"国\"),h=fB(u.width,u.height);o&&(n.handleThumbs=[],n.handleLabels=[],n.handleLabelPoints=[],this._createHandle(e,s,0,i,h,r),this._createHandle(e,s,1,i,h,r)),this._createIndicator(e,s,i,h,r),t.add(s)},e.prototype._createHandle=function(t,e,n,i,r,o){var a=V(this._dragHandle,this,n,!1),s=V(this._dragHandle,this,n,!0),l=Ii(t.get(\"handleSize\"),i[0]),u=fy(t.get(\"handleIcon\"),-l/2,-l/2,l,l,null,!0),h=mB(this._orient);u.attr({cursor:h,draggable:!0,drift:a,ondragend:s,onmousemove:function(t){ee(t.event)}}),u.x=i[0]/2,u.useStyle(t.getModel(\"handleStyle\").getItemStyle()),u.setStyle({strokeNoScale:!0,strokeFirst:!0}),u.style.lineWidth*=2,u.ensureState(\"emphasis\").style=t.getModel([\"emphasis\",\"handleStyle\"]).getItemStyle(),pl(u,!0),e.add(u);var c=this.visualMapModel.textStyleModel,p=new cs({cursor:h,draggable:!0,drift:a,onmousemove:function(t){ee(t.event)},ondragend:s,style:{x:0,y:0,text:\"\",font:c.getFont(),fill:c.getTextColor()}});p.ensureState(\"blur\").style={opacity:.1},p.stateTransition={duration:200},this.group.add(p);var d=[l,0],f=this._shapes;f.handleThumbs[n]=u,f.handleLabelPoints[n]=d,f.handleLabels[n]=p},e.prototype._createIndicator=function(t,e,n,i,r){var o=Ii(t.get(\"indicatorSize\"),n[0]),a=fy(t.get(\"indicatorIcon\"),-o/2,-o/2,o,o,null,!0);a.attr({cursor:\"move\",invisible:!0,silent:!0,x:n[0]/2});var s=t.getModel(\"indicatorStyle\").getItemStyle();if(a instanceof es){var l=a.style;a.useStyle(I({image:l.image,x:l.x,y:l.y,width:l.width,height:l.height},s))}else a.useStyle(s);e.add(a);var u=this.visualMapModel.textStyleModel,h=new cs({silent:!0,invisible:!0,style:{x:0,y:0,text:\"\",font:u.getFont(),fill:u.getTextColor()}});this.group.add(h);var c=[(\"horizontal\"===r?i/2:6)+n[0]/2,0],p=this._shapes;p.indicator=a,p.indicatorLabel=h,p.indicatorLabelPoint=c,this._firstShowIndicator=!0},e.prototype._dragHandle=function(t,e,n,i){if(this._useHandle){if(this._dragging=!e,!e){var r=this._applyTransform([n,i],this._shapes.mainGroup,!0);this._updateInterval(t,r[1]),this._hideIndicator(),this._updateView()}e===!this.visualMapModel.get(\"realtime\")&&this.api.dispatchAction({type:\"selectDataRange\",from:this.uid,visualMapId:this.visualMapModel.id,selected:this._dataInterval.slice()}),e?!this._hovering&&this._clearHoverLinkToSeries():vB(this.visualMapModel)&&this._doHoverLinkToSeries(this._handleEnds[t],!1)}},e.prototype._resetInterval=function(){var t=this.visualMapModel,e=this._dataInterval=t.getSelected(),n=t.getExtent(),i=[0,t.itemSize[1]];this._handleEnds=[cB(e[0],n,i,!0),cB(e[1],n,i,!0)]},e.prototype._updateInterval=function(t,e){e=e||0;var n=this.visualMapModel,i=this._handleEnds,r=[0,n.itemSize[1]];PD(e,i,r,t,0);var o=n.getExtent();this._dataInterval=[cB(i[0],r,o,!0),cB(i[1],r,o,!0)]},e.prototype._updateView=function(t){var e=this.visualMapModel,n=e.getExtent(),i=this._shapes,r=[0,e.itemSize[1]],o=t?r:this._handleEnds,a=this._createBarVisual(this._dataInterval,n,o,\"inRange\"),s=this._createBarVisual(n,n,r,\"outOfRange\");i.inRange.setStyle({fill:a.barColor}).setShape(\"points\",a.barPoints),i.outOfRange.setStyle({fill:s.barColor}).setShape(\"points\",s.barPoints),this._updateHandle(o,a)},e.prototype._createBarVisual=function(t,e,n,i){var r={forceState:i,convertOpacityToAlpha:!0},o=this._makeColorGradient(t,r),a=[this.getControllerVisual(t[0],\"symbolSize\",r),this.getControllerVisual(t[1],\"symbolSize\",r)],s=this._createBarPoints(n,a);return{barColor:new mu(0,0,0,1,o),barPoints:s,handlesColor:[o[0].color,o[o.length-1].color]}},e.prototype._makeColorGradient=function(t,e){var n=[],i=(t[1]-t[0])/100;n.push({color:this.getControllerVisual(t[0],\"color\",e),offset:0});for(var r=1;r<100;r++){var o=t[0]+i*r;if(o>t[1])break;n.push({color:this.getControllerVisual(o,\"color\",e),offset:r/100})}return n.push({color:this.getControllerVisual(t[1],\"color\",e),offset:1}),n},e.prototype._createBarPoints=function(t,e){var n=this.visualMapModel.itemSize;return[[n[0]-e[0],t[0]],[n[0],t[0]],[n[0],t[1]],[n[0]-e[1],t[1]]]},e.prototype._createBarGroup=function(t){var e=this._orient,n=this.visualMapModel.get(\"inverse\");return new Ei(\"horizontal\"!==e||n?\"horizontal\"===e&&n?{scaleX:\"bottom\"===t?-1:1,rotation:-Math.PI/2}:\"vertical\"!==e||n?{scaleX:\"left\"===t?1:-1}:{scaleX:\"left\"===t?1:-1,scaleY:-1}:{scaleX:\"bottom\"===t?1:-1,rotation:Math.PI/2})},e.prototype._updateHandle=function(t,e){if(this._useHandle){var n=this._shapes,i=this.visualMapModel,r=n.handleThumbs,o=n.handleLabels,a=i.itemSize,s=i.getExtent();pB([0,1],(function(l){var u=r[l];u.setStyle(\"fill\",e.handlesColor[l]),u.y=t[l];var h=cB(t[l],[0,a[1]],s,!0),c=this.getControllerVisual(h,\"symbolSize\");u.scaleX=u.scaleY=c/a[0],u.x=a[0]-c/2;var p=qu(n.handleLabelPoints[l],ju(u,this.group));o[l].setStyle({x:p[0],y:p[1],text:i.formatValueText(this._dataInterval[l]),verticalAlign:\"middle\",align:\"vertical\"===this._orient?this._applyTransform(\"left\",n.mainGroup):\"center\"})}),this)}},e.prototype._showIndicator=function(t,e,n,i){var r=this.visualMapModel,o=r.getExtent(),a=r.itemSize,s=[0,a[1]],l=this._shapes,u=l.indicator;if(u){u.attr(\"invisible\",!1);var h=this.getControllerVisual(t,\"color\",{convertOpacityToAlpha:!0}),c=this.getControllerVisual(t,\"symbolSize\"),p=cB(t,o,s,!0),d=a[0]-c/2,f={x:u.x,y:u.y};u.y=p,u.x=d;var g=qu(l.indicatorLabelPoint,ju(u,this.group)),y=l.indicatorLabel;y.attr(\"invisible\",!1);var v=this._applyTransform(\"left\",l.mainGroup),m=\"horizontal\"===this._orient;y.setStyle({text:(n||\"\")+r.formatValueText(e),verticalAlign:m?v:\"middle\",align:m?\"center\":v});var _={x:d,y:p,style:{fill:h}},x={style:{x:g[0],y:g[1]}};if(r.ecModel.isAnimationEnabled()&&!this._firstShowIndicator){var b={duration:100,easing:\"cubicInOut\",additive:!0};u.x=f.x,u.y=f.y,u.animateTo(_,b),y.animateTo(x,b)}else u.attr(_),y.attr(x);this._firstShowIndicator=!1;var w=this._shapes.handleLabels;if(w)for(var S=0;S<w.length;S++)this._api.enterBlur(w[S])}},e.prototype._enableHoverLinkToSeries=function(){var t=this;this._shapes.mainGroup.on(\"mousemove\",(function(e){if(t._hovering=!0,!t._dragging){var n=t.visualMapModel.itemSize,i=t._applyTransform([e.offsetX,e.offsetY],t._shapes.mainGroup,!0,!0);i[1]=dB(fB(0,i[1]),n[1]),t._doHoverLinkToSeries(i[1],0<=i[0]&&i[0]<=n[0])}})).on(\"mouseout\",(function(){t._hovering=!1,!t._dragging&&t._clearHoverLinkToSeries()}))},e.prototype._enableHoverLinkFromSeries=function(){var t=this.api.getZr();this.visualMapModel.option.hoverLink?(t.on(\"mouseover\",this._hoverLinkFromSeriesMouseOver,this),t.on(\"mouseout\",this._hideIndicator,this)):this._clearHoverLinkFromSeries()},e.prototype._doHoverLinkToSeries=function(t,e){var n=this.visualMapModel,i=n.itemSize;if(n.option.hoverLink){var r=[0,i[1]],o=n.getExtent();t=dB(fB(r[0],t),r[1]);var a=function(t,e,n){var i=6,r=t.get(\"hoverLinkDataSize\");r&&(i=cB(r,e,n,!0)/2);return i}(n,o,r),s=[t-a,t+a],l=cB(t,r,o,!0),u=[cB(s[0],r,o,!0),cB(s[1],r,o,!0)];s[0]<r[0]&&(u[0]=-1/0),s[1]>r[1]&&(u[1]=1/0),e&&(u[0]===-1/0?this._showIndicator(l,u[1],\"< \",a):u[1]===1/0?this._showIndicator(l,u[0],\"> \",a):this._showIndicator(l,l,\"≈ \",a));var h=this._hoverLinkDataIndices,c=[];(e||vB(n))&&(c=this._hoverLinkDataIndices=n.findTargetDataIndices(u));var p=function(t,e){var n={},i={};return r(t||[],n),r(e||[],i,n),[o(n),o(i)];function r(t,e,n){for(var i=0,r=t.length;i<r;i++){var o=Cr(t[i].seriesId,null);if(null==o)return;for(var a=xr(t[i].dataIndex),s=n&&n[o],l=0,u=a.length;l<u;l++){var h=a[l];s&&s[h]?s[h]=null:(e[o]||(e[o]={}))[h]=1}}}function o(t,e){var n=[];for(var i in t)if(t.hasOwnProperty(i)&&null!=t[i])if(e)n.push(+i);else{var r=o(t[i],!0);r.length&&n.push({seriesId:i,dataIndex:r})}return n}}(h,c);this._dispatchHighDown(\"downplay\",hB(p[0],n)),this._dispatchHighDown(\"highlight\",hB(p[1],n))}},e.prototype._hoverLinkFromSeriesMouseOver=function(t){var e=t.target,n=this.visualMapModel;if(e&&null!=_s(e).dataIndex){var i=_s(e),r=this.ecModel.getSeriesByIndex(i.seriesIndex);if(n.isTargetSeries(r)){var o=r.getData(i.dataType),a=o.get(n.getDataDimension(o),i.dataIndex);isNaN(a)||this._showIndicator(a,a)}}},e.prototype._hideIndicator=function(){var t=this._shapes;t.indicator&&t.indicator.attr(\"invisible\",!0),t.indicatorLabel&&t.indicatorLabel.attr(\"invisible\",!0);var e=this._shapes.handleLabels;if(e)for(var n=0;n<e.length;n++)this._api.leaveBlur(e[n])},e.prototype._clearHoverLinkToSeries=function(){this._hideIndicator();var t=this._hoverLinkDataIndices;this._dispatchHighDown(\"downplay\",hB(t,this.visualMapModel)),t.length=0},e.prototype._clearHoverLinkFromSeries=function(){this._hideIndicator();var t=this.api.getZr();t.off(\"mouseover\",this._hoverLinkFromSeriesMouseOver),t.off(\"mouseout\",this._hideIndicator)},e.prototype._applyTransform=function(t,e,n,i){var r=ju(e,i?null:this.group);return F(t)?qu(t,r,n):Ku(t,r,n)},e.prototype._dispatchHighDown=function(t,e){e&&e.length&&this.api.dispatchAction({type:t,batch:e})},e.prototype.dispose=function(){this._clearHoverLinkFromSeries(),this._clearHoverLinkToSeries()},e.prototype.remove=function(){this._clearHoverLinkFromSeries(),this._clearHoverLinkToSeries()},e.type=\"visualMap.continuous\",e}(sB);function yB(t,e,n,i){return new ru({shape:{points:t},draggable:!!n,cursor:e,drift:n,onmousemove:function(t){ee(t.event)},ondragend:i})}function vB(t){var e=t.get(\"hoverLinkOnHandle\");return!!(null==e?t.get(\"realtime\"):e)}function mB(t){return\"vertical\"===t?\"ns-resize\":\"ew-resize\"}var _B={type:\"selectDataRange\",event:\"dataRangeSelected\",update:\"update\"},xB=function(t,e){e.eachComponent({mainType:\"visualMap\",query:t},(function(e){e.setSelected(t.selected)}))},bB=[{createOnAllSeries:!0,reset:function(t,e){var n=[];return e.eachComponent(\"visualMap\",(function(e){var i,r,o,a,s,l=t.pipelineContext;!e.isTargetSeries(t)||l&&l.large||n.push((i=e.stateList,r=e.targetVisuals,o=V(e.getValueState,e),a=e.getDataDimension(t.getData()),s={},P(i,(function(t){var e=TT.prepareVisualTypes(r[t]);s[t]=e})),{progress:function(t,e){var n,i;function l(t){return vg(e,i,t)}function u(t,n){_g(e,i,t,n)}for(null!=a&&(n=e.getDimension(a));null!=(i=t.next());){var h=e.getRawDataItem(i);if(!h||!1!==h.visualMap)for(var c=null!=a?e.get(n,i):i,p=o(c),d=r[p],f=s[p],g=0,y=f.length;g<y;g++){var v=f[g];d[v]&&d[v].applyVisual(c,l,u)}}}}))})),n}},{createOnAllSeries:!0,reset:function(t,e){var n=t.getData(),i=[];e.eachComponent(\"visualMap\",(function(e){if(e.isTargetSeries(t)){var r=e.getVisualMeta(V(wB,null,t,e))||{stops:[],outerColors:[]},o=e.getDataDimension(n),a=n.getDimensionInfo(o);null!=a&&(r.dimension=a.index,i.push(r))}})),t.getData().setVisual(\"visualMeta\",i)}}];function wB(t,e,n,i){for(var r=e.targetVisuals[i],o=TT.prepareVisualTypes(r),a={color:mg(t.getData(),\"color\")},s=0,l=o.length;s<l;s++){var u=o[s],h=r[\"opacity\"===u?\"__alphaForOpacity\":u];h&&h.applyVisual(n,c,p)}return a.color;function c(t){return a[t]}function p(t,e){a[t]=e}}var SB=P;function MB(t){var e=t&&t.visualMap;F(e)||(e=e?[e]:[]),SB(e,(function(t){if(t){IB(t,\"splitList\")&&!IB(t,\"pieces\")&&(t.pieces=t.splitList,delete t.splitList);var e=t.pieces;e&&F(e)&&SB(e,(function(t){X(t)&&(IB(t,\"start\")&&!IB(t,\"min\")&&(t.min=t.start),IB(t,\"end\")&&!IB(t,\"max\")&&(t.max=t.end))}))}}))}function IB(t,e){return t&&t.hasOwnProperty&&t.hasOwnProperty(e)}var TB=!1;function CB(t){TB||(TB=!0,t.registerSubTypeDefaulter(\"visualMap\",(function(t){return t.categories||(t.pieces?t.pieces.length>0:t.splitNumber>0)&&!t.calculable?\"piecewise\":\"continuous\"})),t.registerAction(_B,xB),P(bB,(function(e){t.registerVisual(t.PRIORITY.VISUAL.COMPONENT,e)})),t.registerPreprocessor(MB))}function DB(t){t.registerComponentModel(oB),t.registerComponentView(gB),CB(t)}var AB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n._pieceList=[],n}return n(e,t),e.prototype.optionUpdated=function(e,n){t.prototype.optionUpdated.apply(this,arguments),this.resetExtent();var i=this._mode=this._determineMode();this._pieceList=[],LB[this._mode].call(this,this._pieceList),this._resetSelected(e,n);var r=this.option.categories;this.resetVisual((function(t,e){\"categories\"===i?(t.mappingMethod=\"category\",t.categories=w(r)):(t.dataExtent=this.getExtent(),t.mappingMethod=\"piecewise\",t.pieceList=O(this._pieceList,(function(t){return t=w(t),\"inRange\"!==e&&(t.visual=null),t})))}))},e.prototype.completeVisualOption=function(){var e=this.option,n={},i=TT.listVisualTypes(),r=this.isCategory();function o(t,e,n){return t&&t[e]&&t[e].hasOwnProperty(n)}P(e.pieces,(function(t){P(i,(function(e){t.hasOwnProperty(e)&&(n[e]=1)}))})),P(n,(function(t,n){var i=!1;P(this.stateList,(function(t){i=i||o(e,t,n)||o(e.target,t,n)}),this),!i&&P(this.stateList,(function(t){(e[t]||(e[t]={}))[n]=qV(n,\"inRange\"===t?\"active\":\"inactive\",r)}))}),this),t.prototype.completeVisualOption.apply(this,arguments)},e.prototype._resetSelected=function(t,e){var n=this.option,i=this._pieceList,r=(e?n:t).selected||{};if(n.selected=r,P(i,(function(t,e){var n=this.getSelectedMapKey(t);r.hasOwnProperty(n)||(r[n]=!0)}),this),\"single\"===n.selectedMode){var o=!1;P(i,(function(t,e){var n=this.getSelectedMapKey(t);r[n]&&(o?r[n]=!1:o=!0)}),this)}},e.prototype.getItemSymbol=function(){return this.get(\"itemSymbol\")},e.prototype.getSelectedMapKey=function(t){return\"categories\"===this._mode?t.value+\"\":t.index+\"\"},e.prototype.getPieceList=function(){return this._pieceList},e.prototype._determineMode=function(){var t=this.option;return t.pieces&&t.pieces.length>0?\"pieces\":this.option.categories?\"categories\":\"splitNumber\"},e.prototype.setSelected=function(t){this.option.selected=w(t)},e.prototype.getValueState=function(t){var e=TT.findPieceIndex(t,this._pieceList);return null!=e&&this.option.selected[this.getSelectedMapKey(this._pieceList[e])]?\"inRange\":\"outOfRange\"},e.prototype.findTargetDataIndices=function(t){var e=[],n=this._pieceList;return this.eachTargetSeries((function(i){var r=[],o=i.getData();o.each(this.getDataDimension(o),(function(e,i){TT.findPieceIndex(e,n)===t&&r.push(i)}),this),e.push({seriesId:i.id,dataIndex:r})}),this),e},e.prototype.getRepresentValue=function(t){var e;if(this.isCategory())e=t.value;else if(null!=t.value)e=t.value;else{var n=t.interval||[];e=n[0]===-1/0&&n[1]===1/0?0:(n[0]+n[1])/2}return e},e.prototype.getVisualMeta=function(t){if(!this.isCategory()){var e=[],n=[\"\",\"\"],i=this,r=this._pieceList.slice();if(r.length){var o=r[0].interval[0];o!==-1/0&&r.unshift({interval:[-1/0,o]}),(o=r[r.length-1].interval[1])!==1/0&&r.push({interval:[o,1/0]})}else r.push({interval:[-1/0,1/0]});var a=-1/0;return P(r,(function(t){var e=t.interval;e&&(e[0]>a&&s([a,e[0]],\"outOfRange\"),s(e.slice()),a=e[1])}),this),{stops:e,outerColors:n}}function s(r,o){var a=i.getRepresentValue({interval:r});o||(o=i.getValueState(a));var s=t(a,o);r[0]===-1/0?n[0]=s:r[1]===1/0?n[1]=s:e.push({value:r[0],color:s},{value:r[1],color:s})}},e.type=\"visualMap.piecewise\",e.defaultOption=zh(iB.defaultOption,{selected:null,minOpen:!1,maxOpen:!1,align:\"auto\",itemWidth:20,itemHeight:14,itemSymbol:\"roundRect\",pieces:null,categories:null,splitNumber:5,selectedMode:\"multiple\",itemGap:10,hoverLink:!0}),e}(iB),LB={splitNumber:function(t){var e=this.option,n=Math.min(e.precision,20),i=this.getExtent(),r=e.splitNumber;r=Math.max(parseInt(r,10),1),e.splitNumber=r;for(var o=(i[1]-i[0])/r;+o.toFixed(n)!==o&&n<5;)n++;e.precision=n,o=+o.toFixed(n),e.minOpen&&t.push({interval:[-1/0,i[0]],close:[0,0]});for(var a=0,s=i[0];a<r;s+=o,a++){var l=a===r-1?i[1]:s+o;t.push({interval:[s,l],close:[1,1]})}e.maxOpen&&t.push({interval:[i[1],1/0],close:[0,0]}),hr(t),P(t,(function(t,e){t.index=e,t.text=this.formatValueText(t.interval)}),this)},categories:function(t){var e=this.option;P(e.categories,(function(e){t.push({text:this.formatValueText(e,!0),value:e})}),this),kB(e,t)},pieces:function(t){var e=this.option;P(e.pieces,(function(e,n){X(e)||(e={value:e});var i={text:\"\",index:n};if(null!=e.label&&(i.text=e.label),e.hasOwnProperty(\"value\")){var r=i.value=e.value;i.interval=[r,r],i.close=[1,1]}else{for(var o=i.interval=[],a=i.close=[0,0],s=[1,0,1],l=[-1/0,1/0],u=[],h=0;h<2;h++){for(var c=[[\"gte\",\"gt\",\"min\"],[\"lte\",\"lt\",\"max\"]][h],p=0;p<3&&null==o[h];p++)o[h]=e[c[p]],a[h]=s[p],u[h]=2===p;null==o[h]&&(o[h]=l[h])}u[0]&&o[1]===1/0&&(a[0]=0),u[1]&&o[0]===-1/0&&(a[1]=0),o[0]===o[1]&&a[0]&&a[1]&&(i.value=o[0])}i.visual=TT.retrieveVisuals(e),t.push(i)}),this),kB(e,t),hr(t),P(t,(function(t){var e=t.close,n=[[\"<\",\"≤\"][e[1]],[\">\",\"≥\"][e[0]]];t.text=t.text||this.formatValueText(null!=t.value?t.value:t.interval,!1,n)}),this)}};function kB(t,e){var n=t.inverse;(\"vertical\"===t.orient?!n:n)&&e.reverse()}var PB=function(t){function e(){var n=null!==t&&t.apply(this,arguments)||this;return n.type=e.type,n}return n(e,t),e.prototype.doRender=function(){var t=this.group;t.removeAll();var e=this.visualMapModel,n=e.get(\"textGap\"),i=e.textStyleModel,r=i.getFont(),o=i.getTextColor(),a=this._getItemAlign(),s=e.itemSize,l=this._getViewData(),u=l.endsText,h=Q(e.get(\"showLabel\",!0),!u);u&&this._renderEndsText(t,u[0],s,h,a),P(l.viewPieceList,(function(i){var l=i.piece,u=new Ei;u.onclick=V(this._onItemClick,this,l),this._enableHoverLink(u,i.indexInModelPieceList);var c=e.getRepresentValue(l);if(this._createItemSymbol(u,c,[0,0,s[0],s[1]]),h){var p=this.visualMapModel.getValueState(c);u.add(new cs({style:{x:\"right\"===a?-n:s[0]+n,y:s[1]/2,text:l.text,verticalAlign:\"middle\",align:a,font:r,fill:o,opacity:\"outOfRange\"===p?.5:1}}))}t.add(u)}),this),u&&this._renderEndsText(t,u[1],s,h,a),Ec(e.get(\"orient\"),t,e.get(\"itemGap\")),this.renderBackground(t),this.positionGroup(t)},e.prototype._enableHoverLink=function(t,e){var n=this;t.on(\"mouseover\",(function(){return i(\"highlight\")})).on(\"mouseout\",(function(){return i(\"downplay\")}));var i=function(t){var i=n.visualMapModel;i.option.hoverLink&&n.api.dispatchAction({type:t,batch:hB(i.findTargetDataIndices(e),i)})}},e.prototype._getItemAlign=function(){var t=this.visualMapModel,e=t.option;if(\"vertical\"===e.orient)return uB(t,this.api,t.itemSize);var n=e.align;return n&&\"auto\"!==n||(n=\"left\"),n},e.prototype._renderEndsText=function(t,e,n,i,r){if(e){var o=new Ei,a=this.visualMapModel.textStyleModel;o.add(new cs({style:{x:i?\"right\"===r?n[0]:0:n[0]/2,y:n[1]/2,verticalAlign:\"middle\",align:i?r:\"center\",text:e,font:a.getFont(),fill:a.getTextColor()}})),t.add(o)}},e.prototype._getViewData=function(){var t=this.visualMapModel,e=O(t.getPieceList(),(function(t,e){return{piece:t,indexInModelPieceList:e}})),n=t.get(\"text\"),i=t.get(\"orient\"),r=t.get(\"inverse\");return(\"horizontal\"===i?r:!r)?e.reverse():n&&(n=n.slice().reverse()),{viewPieceList:e,endsText:n}},e.prototype._createItemSymbol=function(t,e,n){t.add(fy(this.getControllerVisual(e,\"symbol\"),n[0],n[1],n[2],n[3],this.getControllerVisual(e,\"color\")))},e.prototype._onItemClick=function(t){var e=this.visualMapModel,n=e.option,i=w(n.selected),r=e.getSelectedMapKey(t);\"single\"===n.selectedMode?(i[r]=!0,P(i,(function(t,e){i[e]=e===r}))):i[r]=!i[r],this.api.dispatchAction({type:\"selectDataRange\",from:this.uid,visualMapId:this.visualMapModel.id,selected:i})},e.type=\"visualMap.piecewise\",e}(sB);function OB(t){t.registerComponentModel(AB),t.registerComponentView(PB),CB(t)}var RB={label:{enabled:!0},decal:{show:!1}},NB=kr(),zB={};function EB(t,e){var n=t.getModel(\"aria\");if(n.get(\"enabled\")){var i=w(RB);S(i.label,t.getLocaleModel().get(\"aria\"),!1),S(n.option,i,!1),function(){if(n.getModel(\"decal\").get(\"show\")){var e=ht();t.eachSeries((function(t){if(t.useColorPaletteOnData){var n=e.get(t.type);n||(n={},e.set(t.type,n)),NB(t).scope=n}})),t.eachRawSeries((function(e){if(!t.isSeriesFiltered(e))if(\"function\"!=typeof e.enableAriaDecal){var n=e.getData();if(e.useColorPaletteOnData){var i=e.getRawData(),r={},o=NB(e).scope;n.each((function(t){var e=n.getRawIndex(t);r[e]=t}));var a=i.count();i.each((function(t){var s=r[t],l=i.getName(t)||t+\"\",h=xp(e.ecModel,l,o,a),c=n.getItemVisual(s,\"decal\");n.setItemVisual(s,\"decal\",u(c,h))}))}else{var s=xp(e.ecModel,e.name,zB,t.getSeriesCount()),l=n.getVisual(\"decal\");n.setVisual(\"decal\",u(l,s))}}else e.enableAriaDecal();function u(t,e){var n=t?I(I({},e),t):e;return n.dirty=!0,n}}))}}(),function(){var i=t.getLocaleModel().get(\"aria\"),o=n.getModel(\"label\");if(o.option=T(o.option,i),!o.get(\"enabled\"))return;var a=e.getZr().dom;if(o.get(\"description\"))return void a.setAttribute(\"aria-label\",o.get(\"description\"));var s,l=t.getSeriesCount(),u=o.get([\"data\",\"maxCount\"])||10,h=o.get([\"series\",\"maxCount\"])||10,c=Math.min(l,h);if(l<1)return;var p=function(){var e=t.get(\"title\");e&&e.length&&(e=e[0]);return e&&e.text}();if(p){var d=o.get([\"general\",\"withTitle\"]);s=r(d,{title:p})}else s=o.get([\"general\",\"withoutTitle\"]);var f=[],g=l>1?o.get([\"series\",\"multiple\",\"prefix\"]):o.get([\"series\",\"single\",\"prefix\"]);s+=r(g,{seriesCount:l}),t.eachSeries((function(e,n){if(n<c){var i=void 0,a=e.get(\"name\")?\"withName\":\"withoutName\";i=r(i=l>1?o.get([\"series\",\"multiple\",a]):o.get([\"series\",\"single\",a]),{seriesId:e.seriesIndex,seriesName:e.get(\"name\"),seriesType:(_=e.subType,t.getLocaleModel().get([\"series\",\"typeNames\"])[_]||\"自定义图\")});var s=e.getData();if(s.count()>u)i+=r(o.get([\"data\",\"partialData\"]),{displayCnt:u});else i+=o.get([\"data\",\"allData\"]);for(var h=[],p=0;p<s.count();p++)if(p<u){var d=s.getName(p),g=Md(s,p),y=o.get([\"data\",d?\"withName\":\"withoutName\"]);h.push(r(y,{name:d,value:g}))}var v=o.get([\"data\",\"separator\",\"middle\"]),m=o.get([\"data\",\"separator\",\"end\"]);i+=h.join(v)+m,f.push(i)}var _}));var y=o.getModel([\"series\",\"multiple\",\"separator\"]),v=y.get(\"middle\"),m=y.get(\"end\");s+=f.join(v)+m,a.setAttribute(\"aria-label\",s)}()}function r(t,e){if(\"string\"!=typeof t)return t;var n=t;return P(e,(function(t,e){n=n.replace(new RegExp(\"\\\\{\\\\s*\"+e+\"\\\\s*\\\\}\",\"g\"),t)})),n}}function VB(t){if(t&&t.aria){var e=t.aria;null!=e.show&&(e.enabled=e.show),e.label=e.label||{},P([\"description\",\"general\",\"series\",\"data\"],(function(t){null!=e[t]&&(e.label[t]=e[t])}))}}var BB={value:\"eq\",\"<\":\"lt\",\"<=\":\"lte\",\">\":\"gt\",\">=\":\"gte\",\"=\":\"eq\",\"!=\":\"ne\",\"<>\":\"ne\"},FB=function(){function t(t){if(null==(this._condVal=H(t)?new RegExp(t):$(t)?t:null)){var e=\"\";0,vr(e)}}return t.prototype.evaluate=function(t){var e=typeof t;return\"string\"===e?this._condVal.test(t):\"number\"===e&&this._condVal.test(t+\"\")},t}(),GB=function(){function t(){}return t.prototype.evaluate=function(){return this.value},t}(),HB=function(){function t(){}return t.prototype.evaluate=function(){for(var t=this.children,e=0;e<t.length;e++)if(!t[e].evaluate())return!1;return!0},t}(),WB=function(){function t(){}return t.prototype.evaluate=function(){for(var t=this.children,e=0;e<t.length;e++)if(t[e].evaluate())return!0;return!1},t}(),UB=function(){function t(){}return t.prototype.evaluate=function(){return!this.child.evaluate()},t}(),XB=function(){function t(){}return t.prototype.evaluate=function(){for(var t=!!this.valueParser,e=(0,this.getValue)(this.valueGetterParam),n=t?this.valueParser(e):null,i=0;i<this.subCondList.length;i++)if(!this.subCondList[i].evaluate(t?n:e))return!1;return!0},t}();function YB(t,e){if(!0===t||!1===t){var n=new GB;return n.value=t,n}var i=\"\";return jB(t)||vr(i),t.and?ZB(\"and\",t,e):t.or?ZB(\"or\",t,e):t.not?function(t,e){var n=t.not,i=\"\";0;jB(n)||vr(i);var r=new UB;r.child=YB(n,e),r.child||vr(i);return r}(t,e):function(t,e){for(var n=\"\",i=e.prepareGetValue(t),r=[],o=E(t),a=t.parser,s=a?Od(a):null,l=0;l<o.length;l++){var u=o[l];if(\"parser\"!==u&&!e.valueGetterAttrMap.get(u)){var h=dt(BB,u)?BB[u]:u,c=t[u],p=s?s(c):c,d=Vd(h,p)||\"reg\"===h&&new FB(p);d||vr(n),r.push(d)}}r.length||vr(n);var f=new XB;return f.valueGetterParam=i,f.valueParser=s,f.getValue=e.getValue,f.subCondList=r,f}(t,e)}function ZB(t,e,n){var i=e[t],r=\"\";F(i)||vr(r),i.length||vr(r);var o=\"and\"===t?new HB:new WB;return o.children=O(i,(function(t){return YB(t,n)})),o.children.length||vr(r),o}function jB(t){return X(t)&&!k(t)}var qB=function(){function t(t,e){this._cond=YB(t,e)}return t.prototype.evaluate=function(){return this._cond.evaluate()},t}();var KB={type:\"echarts:filter\",transform:function(t){for(var e,n,i,r=t.upstream,o=(n=t.config,i={valueGetterAttrMap:ht({dimension:!0}),prepareGetValue:function(t){var e=\"\",n=t.dimension;dt(t,\"dimension\")||vr(e);var i=r.getDimensionInfo(n);return i||vr(e),{dimIdx:i.index}},getValue:function(t){return r.retrieveValueFromItem(e,t.dimIdx)}},new qB(n,i)),a=[],s=0,l=r.count();s<l;s++)e=r.getRawDataItem(s),o.evaluate()&&a.push(e);return{data:a}}};var $B={type:\"echarts:sort\",transform:function(t){var e=t.upstream,n=t.config,i=\"\",r=xr(n);r.length||vr(i);var o=[];P(r,(function(t){var n=t.dimension,r=t.order,a=t.parser,s=t.incomparable;if(null==n&&vr(i),\"asc\"!==r&&\"desc\"!==r&&vr(i),s&&\"min\"!==s&&\"max\"!==s){var l=\"\";0,vr(l)}if(\"asc\"!==r&&\"desc\"!==r){var u=\"\";0,vr(u)}var h=e.getDimensionInfo(n);h||vr(i);var c=a?Od(a):null;a&&!c&&vr(i),o.push({dimIdx:h.index,parser:c,comparator:new zd(r,s)})}));var a=e.sourceFormat;a!==$c&&a!==Jc&&vr(i);for(var s=[],l=0,u=e.count();l<u;l++)s.push(e.getRawDataItem(l));return s.sort((function(t,n){for(var i=0;i<o.length;i++){var r=o[i],a=e.retrieveValueFromItem(t,r.dimIdx),s=e.retrieveValueFromItem(n,r.dimIdx);r.parser&&(a=r.parser(a),s=r.parser(s));var l=r.comparator.evaluate(a,s);if(0!==l)return l}return 0})),{data:s}}};var JB=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"dataset\",e}return n(e,t),e.prototype.init=function(e,n,i){t.prototype.init.call(this,e,n,i),this._sourceManager=new Zd(this),jd(this)},e.prototype.mergeOption=function(e,n){t.prototype.mergeOption.call(this,e,n),jd(this)},e.prototype.optionUpdated=function(){this._sourceManager.dirty()},e.prototype.getSourceManager=function(){return this._sourceManager},e.type=\"dataset\",e.defaultOption={seriesLayoutBy:np},e}(Xc),QB=function(t){function e(){var e=null!==t&&t.apply(this,arguments)||this;return e.type=\"dataset\",e}return n(e,t),e.type=\"dataset\",e}(wf);Qm([function(t){t.registerPainter(\"canvas\",uw)}]),Qm([function(t){t.registerPainter(\"svg\",tw)}]),Qm([function(t){t.registerChartView(Uw),t.registerSeriesModel(hw),t.registerLayout(Xw(\"line\",!0)),t.registerVisual({seriesType:\"line\",reset:function(t){var e=t.getData(),n=t.getModel(\"lineStyle\").getLineStyle();n&&!n.stroke&&(n.stroke=e.getVisual(\"style\").fill),e.setVisual(\"legendLineStyle\",n)}}),t.registerProcessor(t.PRIORITY.PROCESSOR.STATISTIC,jw(\"line\"))},function(t){t.registerChartView(nS),t.registerSeriesModel(Kw),t.registerLayout(t.PRIORITY.VISUAL.LAYOUT,B(lx,\"bar\")),t.registerLayout(t.PRIORITY.VISUAL.PROGRESSIVE_LAYOUT,ux),t.registerProcessor(t.PRIORITY.PROCESSOR.STATISTIC,jw(\"bar\")),t.registerAction({type:\"changeAxisOrder\",event:\"changeAxisOrder\",update:\"update\"},(function(t,e){var n=t.componentType||\"series\";e.eachComponent({mainType:n,query:t},(function(e){t.sortInfo&&e.axis.setCategorySortInfo(t.sortInfo)}))}))},function(t){t.registerChartView(SS),t.registerSeriesModel(TS),ey(\"pie\",t.registerAction),t.registerLayout(B(gS,\"pie\")),t.registerProcessor(yS(\"pie\"))},function(t){Qm(IM),t.registerSeriesModel(CS),t.registerChartView(kS),t.registerLayout(Xw(\"scatter\"))},function(t){Qm(BM),t.registerChartView(LM),t.registerSeriesModel(kM),t.registerLayout(TM),t.registerProcessor(yS(\"radar\")),t.registerPreprocessor(AM)},function(t){Qm(MI),t.registerChartView(uI),t.registerSeriesModel(hI),t.registerLayout(pI),t.registerProcessor(t.PRIORITY.PROCESSOR.STATISTIC,cI),ey(\"map\",t.registerAction)},function(t){t.registerChartView(zI),t.registerSeriesModel(nT),t.registerLayout(rT),t.registerVisual(oT),function(t){t.registerAction({type:\"treeExpandAndCollapse\",event:\"treeExpandAndCollapse\",update:\"update\"},(function(t,e){e.eachComponent({mainType:\"series\",subType:\"tree\",query:t},(function(e){var n=t.dataIndex,i=e.getData().tree.getNodeByDataIndex(n);i.isExpand=!i.isExpand}))})),t.registerAction({type:\"treeRoam\",event:\"treeRoam\",update:\"none\"},(function(t,e){e.eachComponent({mainType:\"series\",subType:\"tree\",query:t},(function(e){var n=wI(e.coordinateSystem,t);e.setCenter&&e.setCenter(n.center),e.setZoom&&e.setZoom(n.zoom)}))}))}(t)},function(t){t.registerSeriesModel(uT),t.registerChartView(wT),t.registerVisual(BT),t.registerLayout(JT),function(t){for(var e=0;e<sT.length;e++)t.registerAction({type:sT[e],update:\"updateView\"},aT);t.registerAction({type:\"treemapRootToNode\",update:\"updateView\"},(function(t,e){e.eachComponent({mainType:\"series\",subType:\"treemap\",query:t},(function(e,n){var i=JI(t,[\"treemapZoomToNode\",\"treemapRootToNode\"],e);if(i){var r=e.getViewRoot();r&&(t.direction=tT(r,i.node)?\"rollUp\":\"drillDown\"),e.resetViewRoot(i.node)}}))}))}(t)},function(t){t.registerChartView($C),t.registerSeriesModel(rD),t.registerProcessor(rC),t.registerVisual(oC),t.registerVisual(sC),t.registerLayout(vC),t.registerLayout(t.PRIORITY.VISUAL.POST_CHART_LAYOUT,MC),t.registerLayout(TC),t.registerCoordinateSystem(\"graphView\",{dimensions:fI.dimensions,create:CC}),t.registerAction({type:\"focusNodeAdjacency\",event:\"focusNodeAdjacency\",update:\"series:focusNodeAdjacency\"},(function(){})),t.registerAction({type:\"unfocusNodeAdjacency\",event:\"unfocusNodeAdjacency\",update:\"series:unfocusNodeAdjacency\"},(function(){})),t.registerAction(oD,(function(t,e){e.eachComponent({mainType:\"series\",query:t},(function(e){var n=wI(e.coordinateSystem,t);e.setCenter&&e.setCenter(n.center),e.setZoom&&e.setZoom(n.zoom)}))}))},function(t){t.registerChartView(hD),t.registerSeriesModel(cD)},function(t){t.registerChartView(fD),t.registerSeriesModel(gD),t.registerLayout(yD),t.registerProcessor(yS(\"funnel\"))},function(t){Qm(HA),t.registerChartView(vD),t.registerSeriesModel(wD),t.registerVisual(t.PRIORITY.VISUAL.BRUSH,ID)},function(t){t.registerChartView(XA),t.registerSeriesModel(YA),t.registerLayout(ZA),t.registerVisual(oL),t.registerAction({type:\"dragNode\",event:\"dragnode\",update:\"update\"},(function(t,e){e.eachComponent({mainType:\"series\",subType:\"sankey\",query:t},(function(e){e.setNodePosition(t.dataIndex,[t.localX,t.localY])}))}))},function(t){t.registerSeriesModel(sL),t.registerChartView(lL),t.registerVisual(fL),t.registerLayout(yL),t.registerTransform(vL)},function(t){t.registerChartView(_L),t.registerSeriesModel(LL),t.registerPreprocessor(kL),t.registerVisual(zL),t.registerLayout(VL)},function(t){t.registerChartView(HL),t.registerSeriesModel(WL),t.registerLayout(Xw(\"effectScatter\"))},function(t){t.registerChartView($L),t.registerSeriesModel(ek),t.registerLayout(KL),t.registerVisual(ik)},function(t){t.registerChartView(ak),t.registerSeriesModel(sk)},function(t){t.registerChartView(ck),t.registerSeriesModel(Ak),t.registerLayout(B(lx,\"pictorialBar\"))},function(t){t.registerChartView(Lk),t.registerSeriesModel(kk),t.registerLayout(Pk),t.registerProcessor(yS(\"themeRiver\"))},function(t){t.registerChartView(Ek),t.registerSeriesModel(Vk),t.registerLayout(B(Gk,\"sunburst\")),t.registerProcessor(B(yS,\"sunburst\")),t.registerVisual(Wk),function(t){t.registerAction({type:Nk,update:\"updateView\"},(function(t,e){e.eachComponent({mainType:\"series\",subType:\"sunburst\",query:t},(function(e,n){var i=JI(t,[Nk],e);if(i){var r=e.getViewRoot();r&&(t.direction=tT(r,i.node)?\"rollUp\":\"drillDown\"),e.resetViewRoot(i.node)}}))})),t.registerAction({type:zk,update:\"none\"},(function(t,e,n){t=I({},t),e.eachComponent({mainType:\"series\",subType:\"sunburst\",query:t},(function(e){var n=JI(t,[zk],e);n&&(t.dataIndex=n.node.dataIndex)})),n.dispatchAction(I(t,{type:\"highlight\"}))})),t.registerAction({type:\"sunburstUnhighlight\",update:\"updateView\"},(function(t,e,n){t=I({},t),n.dispatchAction(I(t,{type:\"downplay\"}))}))}(t)},function(t){t.registerChartView(FP),t.registerSeriesModel(BP)}]),Qm((function(t){Qm(IM),Qm(aR)})),Qm((function(t){Qm(aR),dM.registerAxisPointerClass(\"PolarAxisPointer\",sR),t.registerCoordinateSystem(\"polar\",xR),t.registerComponentModel(uR),t.registerComponentView(zR),BS(t,\"angle\",cR,RR),BS(t,\"radius\",pR,NR),t.registerComponentView(IR),t.registerComponentView(AR),t.registerLayout(B(OR,\"bar\"))})),Qm(MI),Qm((function(t){Qm(aR),dM.registerAxisPointerClass(\"SingleAxisPointer\",qR),t.registerComponentView(QR),t.registerComponentView(FR),t.registerComponentModel(HR),BS(t,\"single\",HR,HR.defaultOption),t.registerCoordinateSystem(\"single\",YR)})),Qm(HA),Qm((function(t){t.registerComponentModel(tN),t.registerComponentView(rN),t.registerCoordinateSystem(\"calendar\",aN)})),Qm((function(t){t.registerComponentModel(cN),t.registerComponentView(pN),t.registerPreprocessor(hN)})),Qm((function(t){t.registerComponentModel(VN),t.registerComponentView(FN),zN(\"saveAsImage\",GN),zN(\"magicType\",UN),zN(\"dataView\",$N),zN(\"dataZoom\",_z),zN(\"restore\",nz),Qm(ON)})),Qm((function(t){Qm(aR),t.registerComponentModel(wz),t.registerComponentView(Wz),t.registerAction({type:\"showTip\",event:\"showTip\",update:\"tooltip:manuallyShowTip\"},(function(){})),t.registerAction({type:\"hideTip\",event:\"hideTip\",update:\"tooltip:manuallyHideTip\"},(function(){}))})),Qm(aR),Qm((function(t){t.registerComponentView(cE),t.registerComponentModel(pE),t.registerPreprocessor(jz),t.registerVisual(t.PRIORITY.VISUAL.BRUSH,aE),t.registerAction({type:\"brush\",event:\"brush\",update:\"updateVisual\"},(function(t,e){e.eachComponent({mainType:\"brush\",query:t},(function(e){e.setAreas(t.areas)}))})),t.registerAction({type:\"brushSelect\",event:\"brushSelected\",update:\"none\"},(function(){})),t.registerAction({type:\"brushEnd\",event:\"brushEnd\",update:\"none\"},(function(){})),zN(\"brush\",gE)})),Qm((function(t){t.registerComponentModel(yE),t.registerComponentView(vE)})),Qm((function(t){t.registerComponentModel(_E),t.registerComponentView(ME),t.registerSubTypeDefaulter(\"timeline\",(function(){return\"slider\"})),function(t){t.registerAction({type:\"timelineChange\",event:\"timelineChanged\",update:\"prepareAndUpdate\"},(function(t,e){var n=e.getComponent(\"timeline\");return n&&null!=t.currentIndex&&(n.setCurrentIndex(t.currentIndex),!n.get(\"loop\",!0)&&n.isIndexMax()&&n.setPlayState(!1)),e.resetOption(\"timeline\",{replaceMerge:n.get(\"replaceMerge\",!0)}),T({currentIndex:n.option.currentIndex},t)})),t.registerAction({type:\"timelinePlayChange\",event:\"timelinePlayChanged\",update:\"update\"},(function(t,e){var n=e.getComponent(\"timeline\");n&&null!=t.playState&&n.setPlayState(t.playState)}))}(t),t.registerPreprocessor(CE)})),Qm((function(t){t.registerComponentModel(RE),t.registerComponentView(XE),t.registerPreprocessor((function(t){LE(t.series,\"markPoint\")&&(t.markPoint=t.markPoint||{})}))})),Qm((function(t){t.registerComponentModel(YE),t.registerComponentView(QE),t.registerPreprocessor((function(t){LE(t.series,\"markLine\")&&(t.markLine=t.markLine||{})}))})),Qm((function(t){t.registerComponentModel(tV),t.registerComponentView(lV),t.registerPreprocessor((function(t){LE(t.series,\"markArea\")&&(t.markArea=t.markArea||{})}))})),Qm((function(t){Qm(xV),Qm(CV)})),Qm((function(t){Qm(BV),Qm(jV)})),Qm(BV),Qm(jV),Qm((function(t){Qm(DB),Qm(OB)})),Qm(DB),Qm(OB),Qm((function(t){t.registerPreprocessor(VB),t.registerVisual(t.PRIORITY.VISUAL.ARIA,EB)})),Qm((function(t){t.registerTransform(KB),t.registerTransform($B)})),Qm((function(t){t.registerComponentModel(JB),t.registerComponentView(QB)})),t.Axis=hb,t.ChartView=Tf,t.ComponentModel=Xc,t.ComponentView=wf,t.List=L_,t.Model=Oh,t.PRIORITY=Gv,t.SeriesModel=ff,t.color=tn,t.connect=function(t){if(F(t)){var e=t;t=null,Rv(e,(function(e){null!=e.group&&(t=e.group)})),t=t||\"g_\"+Pm++,Rv(e,(function(e){e.group=t}))}return Lm[t]=!0,t},t.dataTool={},t.dependencies={zrender:\"5.1.1\"},t.disConnect=Rm,t.disconnect=Nm,t.dispose=function(t){\"string\"==typeof t?t=Am[t]:t instanceof ym||(t=zm(t)),t instanceof ym&&!t.isDisposed()&&t.dispose()},t.env=a,t.extendChartView=function(t){var e=Tf.extend(t);return Tf.registerClass(e),e},t.extendComponentModel=function(t){var e=Xc.extend(t);return Xc.registerClass(e),e},t.extendComponentView=function(t){var e=wf.extend(t);return wf.registerClass(e),e},t.extendSeriesModel=function(t){var e=ff.extend(t);return ff.registerClass(e),e},t.format=Jx,t.getCoordinateSystemDimensions=function(t){var e=Ap.get(t);if(e)return e.getDimensionsInfo?e.getDimensionsInfo():e.dimensions.slice()},t.getInstanceByDom=zm,t.getInstanceById=function(t){return Am[t]},t.getMap=function(t){return kv(t)},t.graphic=$x,t.helper=jx,t.init=function(t,e,n){var i=zm(t);if(i)return i;var r=new ym(t,e,n);return r.id=\"ec_\"+km++,Am[r.id]=r,Vr(t,Om,r.id),hm(r),Rv(Mm,(function(t){t(r)})),r},t.innerDrawElementOnCanvas=ky,t.matrix=qn,t.number=qx,t.parseGeoJSON=vv,t.parseGeoJson=vv,t.registerAction=Hm,t.registerCoordinateSystem=Wm,t.registerLayout=Um,t.registerLoading=jm,t.registerLocale=Wh,t.registerMap=qm,t.registerPostInit=Fm,t.registerPostUpdate=Gm,t.registerPreprocessor=Vm,t.registerProcessor=Bm,t.registerTheme=Em,t.registerTransform=Km,t.registerVisual=Xm,t.setCanvasCreator=function(t){m(\"createCanvas\",t)},t.throttle=Nf,t.time=Kx,t.use=Qm,t.util=Qx,t.vector=Et,t.version=\"5.1.2\",t.zrUtil=gt,t.zrender=Ui,Object.defineProperty(t,\"__esModule\",{value:!0})}));"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Clickable SVG Image/html.html",
    "content": "<div style=\"width:100%;margin:0 auto;\">\n    <!-- your widget template -->\n    <div id=\"main\" style=\"width:100%;height:90vh;\"></div>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Client side pagination/README.md",
    "content": "This code can be used to add client side pagination to the widget. \nif there are a lot of records on a customized widget and you prefer using client side pagination instead of server side pagination, use this method me"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Client side pagination/script.js",
    "content": "//client side pagination in widgets\n//html code\n\n//boundary links show the first and last pages as arrows\n//max size shows the number of total page numbers visible to the user in case of several pages\n<div>\n    <uib-pagination total-items=\"10\" ng-model=\"currentPage\" items-per-page=\"5\"\n    ng-click=\"pageUpdated()\" previous-text=\"&lsaquo;\" next-text=\"&rsaquo;\" first-text=\"&laquo;\" last-text=\"&raquo;\"\n    max-size=\"3\" class=\"\" force-ellipses=\"true\" boundary-links = \"true\"></uib-pagination>\n</div>\n\n//client side script\nfunction($scope) {\n\n    //we can have variables here for total items and items per page in a dynamic way \n    //and then use them in the html side\n    $scope.currentPage = 1;\n\n    $scope.pageUpdated = function(){\n        console.log('this page is updated to: ' + $scope.currentPage);\n    }\n\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Configurable Card Widget/CSS-SCSS.scss",
    "content": "$secondary-color: #000000;\n$text-color;\n$text-muted;\n\n.card{\n  border-radius: 10px;\n  padding: 20px;\n  box-shadow: 0 0 1rem #e5e5e5;\n  margin-bottom: 10px;\n}\n\n.tag{  \n  background-color: $secondary-color;\n  color: #ffffff;\n  padding: 2px 8px;\n  border-radius: 10px;\n}\n\n.heading{\n  margin-top: 1.1rem;\n  margin-bottom: 1.1rem;\n  color:$text-color;\n}\n\n.description{\n  color: $text-muted;\n}\n\n.button{\n  color: #ffffff;\n  background-color: #ffffff;\n  padding: 0.5em 1em;\n  border-radius: 6px;\n  font-weight: 500;\n  display: inline-flex;\n  align-items: center;\n  border: 2px solid $secondary-color;\n  margin-top: 10px;\n  box-shadow: inset 0 0 0 0 #ffffff;\n  transition: ease-out 0.4s;\n  cursor: pointer;\n\n  &:hover{\n    background-color: #ffffff !important;\n    color: #000000;\n    box-shadow: inset 400px 0 0 0 #ffffff;\n  }\n}\n\n.w-100{\n  width: 100%;\n}\n\n.bg{\n  height: 20rem;\n  background: #ffffff;\n  border-radius: 10px;\n  background-position: center;\n  background-repeat: no-repeat;\n  background-size: cover;\n}\n\n.position-absolute{\n position: absolute;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Configurable Card Widget/Client Script.js",
    "content": "api.controller=function() {\n  /* widget controller */\n  var c = this;\n c.image = c.options.background_image;\n c.image = \"url('\" + c.image + \"')\";\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Configurable Card Widget/HTML Template.html",
    "content": "<div class=\"card\" ng-style=\"{'background': c.options.background_color}\">\n  <span ng-if=\"::c.options.feature\" class=\"tag\" ng-style=\"{'background-color': c.options.feature_color}\" ng-class=\"{'position-absolute': c.options.background_image && c.options.background_image != ''}\">{{::c.options.feature}}</span>\n  <div ng-if=\"::c.options.background_image\" class=\"bg\" ng-style=\"{'background-image': c.image}\"></div>\n  <h2 class=\"heading\">\n   {{c.options.heading}}\n  </h2>\n  <p class=\"description\">\n    {{c.options.description}}\n  </p>\n  <div>\n    <a ng-href=\"{{c.options.redirect_url}}\" class=\"button\" ng-style=\"{'border-color': c.options.feature_color, 'background-color': c.options.feature_color}\">\n      Learn More &nbsp;&nbsp;<span class=\"glyphicon glyphicon-menu-right\" aria-hidden=\"true\"></span>\n    </a>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Configurable Card Widget/README.md",
    "content": "# Configurable Card Widget\nThis widget can be used to show card-based pieces of information and can  easily be modified in its content and style using widget options.\n\n![alt text](https://raw.githubusercontent.com/debendu-das/code-snippets/service-portal-widget-configurable-card/Service%20Portal%20Widgets/Configurable%20Card%20Widget/image.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Configurable Card Widget/Widget Options.json",
    "content": "[{\"hint\":\"#000000\",\"name\":\"feature_color\",\"section\":\"Behavior\",\"default_value\":\"#000000\",\"label\":\"Feature Color\",\"type\":\"string\"},{\"name\":\"heading\",\"section\":\"Data\",\"default_value\":\"Heading\",\"label\":\"Heading\",\"type\":\"string\"},{\"name\":\"description\",\"section\":\"Data\",\"default_value\":\"Description\",\"label\":\"Description\",\"type\":\"string\"},{\"hint\":\"https://www.google.com\",\"name\":\"redirect_url\",\"section\":\"Data\",\"default_value\":\"\",\"label\":\"Redirect URL\",\"type\":\"string\"},{\"name\":\"background_image\",\"section\":\"Data\",\"default_value\":\"\",\"label\":\"Background Image\",\"type\":\"string\"},{\"hint\":\"#FFFFFF\",\"name\":\"background_color\",\"section\":\"Behavior\",\"default_value\":\"#FFFFFF\",\"label\":\"Background Color\",\"type\":\"string\"},{\"name\":\"feature\",\"section\":\"Data\",\"label\":\"Feature\",\"type\":\"string\"}]\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using GoJS library/README.md",
    "content": "# Creating productionProcess using gojs\n## Introduction\nIn this snippet you will create a custom process using a custom page and populating data using GoJS native library\n\n## Step 1: Create a new Widget\n***Go to Service Portal > Widget > Click New***\n- Name: Custom productionProcess\n- Id: custom-gojs-productionProcess\n- Click on `submit`\n\n***Body HTML template***\n- Copy and paste below `HTML Code` in Widget's HTML Template section\n```HTML\n<div id=\"myDiagramDiv\" style=\"border: solid 1px black; width:70%; height:550px; display: inline-block; vertical-align: top\"></div>\n```\n\n***CSS/SCSS***\n- Copy and paste below `CSS` in Widget's CSS/SCSS Section\n```CSS\n#infobox {\n  width: 256px;\n  background: #757575;\n  color: #FFF;\n  padding: 20px;\n}\n\n#myDiagramDiv * {\n  outline: none;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */\n}\n```\n***Client Side Scripts***\n- Copy and Paste below `Script` in Widget's Client Side Section\n```javascript\n// build GraphObject\n  var $ = go.GraphObject.make;  // for conciseness in defining templates\n  myDiagram = $(go.Diagram, \"myDiagramDiv\",  // create a Diagram for the DIV HTML element\n    {\n      maxSelectionCount: 1, // users can select only one part at a time\n      \"toolManager.hoverDelay\": 10,  // how quickly tooltips are shown\n      initialAutoScale: go.Diagram.Uniform,  // scale to show all of the contents\n      \"ChangedSelection\": onSelectionChanged, // view additional information\n    });\n\n  function infoString(obj) {\n    var part = obj.part;\n    if (part instanceof go.Adornment) part = part.adornedPart;\n    var msg = \"\";\n    if (part instanceof go.Link) {\n      msg = \"\";\n    } else if (part instanceof go.Node) {\n      msg = part.data.text + \":\\n\\n\" + part.data.description;\n    }\n    return msg;\n  }\n\n  var colors = {\n    \"red\": \"#be4b15\",\n    \"green\": \"#52ce60\",\n    \"blue\": \"#6ea5f8\",\n    \"lightred\": \"#fd8852\",\n    \"lightblue\": \"#afd4fe\",\n    \"lightgreen\": \"#b9e986\",\n    \"pink\": \"#faadc1\",\n    \"purple\": \"#d689ff\",\n    \"orange\": \"#fdb400\",\n  };\n\n  // A data binding conversion function. Given an name, return the Geometry.\n  // If there is only a string, replace it with a Geometry object, which can be shared by multiple Shapes.\n  function geoFunc(geoname) {\n    var geo = icons[geoname];\n    if (typeof geo === \"string\") {\n      geo = icons[geoname] = go.Geometry.parse(geo, true);\n    }\n    return geo;\n  }\n\n  myDiagram.nodeTemplate =\n    $(go.Node, \"Spot\",\n      {\n        locationObjectName: 'main',\n        locationSpot: go.Spot.Center,\n        toolTip:\n          $(go.Adornment, \"Auto\",\n            $(go.Shape, { fill: \"#CCFFCC\" }),\n            $(go.TextBlock, { margin: 4, width: 140 }, //https://gojs.net/latest/intro/toolTips.html\n              new go.Binding(\"text\", \"\", infoString).ofObject()))\n        /*toolTip:\n          $(\"ToolTip\",\n            $(go.TextBlock, { margin: 4, width: 140 }, //https://gojs.net/latest/intro/toolTips.html\n              new go.Binding(\"text\", \"\", infoString).ofObject())\n          )*/\n      },\n      new go.Binding(\"location\", \"pos\", go.Point.parse).makeTwoWay(go.Point.stringify),\n      // The main element of the Spot panel is a vertical panel housing an optional icon,\n      // plus a rectangle that acts as the port\n      $(go.Panel, \"Vertical\",\n        $(go.Shape, {\n          name: 'icon',\n          width: 1, height: 1,\n          stroke: null, strokeWidth: 0,\n          fill: colors.blue\n        },\n          new go.Binding(\"fill\", \"color\", function (c) { return colors[c]; }),\n          new go.Binding(\"width\", \"iconWidth\"),\n          new go.Binding(\"height\", \"iconHeight\"),\n          new go.Binding(\"geometry\", \"icon\", geoFunc)),\n        $(go.Shape, {\n          name: 'main',\n          width: 40, height: 40,\n          margin: new go.Margin(-1, 0, 0, 0),\n          portId: \"\",\n          stroke: null, strokeWidth: 0,\n          fill: colors.blue\n        },\n          new go.Binding(\"fill\", \"color\", function (c) { return colors[c]; }),\n          new go.Binding(\"width\", \"portWidth\"),\n          new go.Binding(\"height\", \"portHeight\"))\n      ),\n\n      $(go.TextBlock, {\n        font: \"Bold 14px Lato, sans-serif\",\n        textAlign: \"center\",\n        margin: 3,\n        maxSize: new go.Size(100, NaN),\n        alignment: go.Spot.TopCenter,\n        alignmentFocus: go.Spot.BottomCenter\n      },\n        new go.Binding(\"text\"))\n\n    );\n\n  // Some links need a custom to or from spot\n  function spotConverter(dir) {\n    if (dir === \"left\") return go.Spot.LeftSide;\n    if (dir === \"right\") return go.Spot.RightSide;\n    if (dir === \"top\") return go.Spot.TopSide;\n    if (dir === \"bottom\") return go.Spot.BottomSide;\n    if (dir === \"rightsingle\") return go.Spot.Right;\n  }\n\n  myDiagram.linkTemplate =\n    $(go.Link, {\n      toShortLength: -2,\n      fromShortLength: -2,\n      layerName: \"Background\",\n      routing: go.Link.Orthogonal,\n      corner: 15,\n      fromSpot: go.Spot.RightSide,\n      toSpot: go.Spot.LeftSide\n    },\n      // make sure links come in from the proper direction and go out appropriately\n      new go.Binding(\"fromSpot\", \"fromSpot\", function (d) { return spotConverter(d); }),\n      new go.Binding(\"toSpot\", \"toSpot\", function (d) { return spotConverter(d); }),\n\n      new go.Binding(\"points\").makeTwoWay(),\n      // mark each Shape to get the link geometry with isPanelMain: true\n      $(go.Shape, { isPanelMain: true, stroke: colors.lightblue, strokeWidth: 10 },\n        new go.Binding(\"stroke\", \"color\", function (c) { return colors[c]; })),\n      $(go.Shape, { isPanelMain: true, stroke: \"white\", strokeWidth: 3, name: \"PIPE\", strokeDashArray: [20, 40] })\n    );\n\n\n  // build model\n  var model = $(go.GraphLinksModel);\n\n  model.nodeDataArray =\n    [\n      {\n        key: 1,\n        pos: '-180 -57',\n        icon: 'natgas',\n        iconWidth: 30,\n        iconHeight: 60,\n        portHeight: 20,\n        text: 'Gas\\nCompanies',\n        description: 'Provides natural gas liquids (NGLs).',\n        caption: 'Gas Drilling Well',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/BarnettShaleDrilling-9323.jpg/256px-BarnettShaleDrilling-9323.jpg'\n      },\n      {\n        key: 2,\n        pos: '-180 100',\n        icon: 'oil',\n        iconWidth: 40,\n        iconHeight: 60,\n        portHeight: 20,\n        text: 'Oil\\nCompanies',\n        description: 'Provides associated petroleum gas (APG). This type of gas used to be released as waste from oil drilling, but is now commonly captured for processing.',\n        caption: 'Offshore oil well',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Oil_platform_P-51_%28Brazil%29.jpg/512px-Oil_platform_P-51_%28Brazil%29.jpg'\n      },\n      {\n        key: 3,\n        pos: '-80 100',\n        icon: 'gasprocessing',\n        iconWidth: 40,\n        iconHeight: 40,\n        text: 'Gas Processing',\n        description: 'APG processing turns associated petrolium gas into natural gas liquids (NGLs) and stable natural gas (SGN).',\n        caption: 'Natural gas plant',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Solohiv_natural_gas_plant_-_fragment.jpg/256px-Solohiv_natural_gas_plant_-_fragment.jpg'\n      },\n      {\n        key: 4,\n        pos: '30 -50',\n        icon: 'fractionation',\n        iconWidth: 40,\n        iconHeight: 60,\n        text: 'Gas Fractionation',\n        description: 'Natural gas liquids are separated into individual hydrocarbons like propane and butanes, hydrocarbon mixtures (naphtha) and liquefied petroleum gases (LPGs).',\n        caption: 'Gas Plant',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Gasblok.jpg/256px-Gasblok.jpg'\n      },\n      {\n        key: 5,\n        pos: '130 -50',\n        icon: 'pyrolysis',\n        iconWidth: 40,\n        iconHeight: 40,\n        color: 'orange',\n        text: 'Pyrolysis (Cracking)',\n        description: 'Liquefied petroleum gases (LPGs) are transformed into Ethylene, propylene, benzene, and other by-products.',\n        caption: 'Pyrolysis plant',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/6/6c/Guelph.jpg'\n      },\n      {\n        key: 6,\n        pos: '330 -140',\n        icon: 'polymerization',\n        iconWidth: 40,\n        iconHeight: 40,\n        portHeight: 12,\n        color: 'red',\n        text: 'Basic Polymers',\n        description: 'Ethylene and propylene (monomers) are processed into end products using various methods (polymerization).',\n        caption: 'Plastics engineering-Polymer products',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Plastics_engineering-Polymer_products.jpg/256px-Plastics_engineering-Polymer_products.jpg'\n      },\n      {\n        key: 7,\n        pos: '330 -55',\n        icon: 'polymerization',\n        iconWidth: 40,\n        iconHeight: 40,\n        portHeight: 12,\n        color: 'green',\n        text: 'Plastics',\n        description: 'Polymerization produces PET, glycols, alcohols, expandable polystyrene, acrylates, BOPP-films and geosynthetics.',\n        caption: 'Lego Color Bricks',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/Lego_Color_Bricks.jpg/256px-Lego_Color_Bricks.jpg'\n      },\n      {\n        key: 8,\n        pos: '330 40',\n        icon: 'polymerization',\n        iconWidth: 40,\n        iconHeight: 40,\n        portHeight: 12,\n        color: 'lightgreen',\n        text: 'Synthetic Rubbers',\n        description: 'Polymerization produces commodity and specialty rubbers and thermoplastic elastomers.',\n        caption: 'Sheet of synthetic rubber coming off the rolling mill at the plant of Goodrich',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Sheet_of_synthetic_rubber_coming_off_the_rolling_mill_at_the_plant_of_Goodrich.jpg/512px-Sheet_of_synthetic_rubber_coming_off_the_rolling_mill_at_the_plant_of_Goodrich.jpg'\n      },\n      {\n        key: 9,\n        pos: '330 115',\n        color: 'orange',\n        portHeight: 22,\n        text: 'Intermediates',\n        description: 'Produced Ethylene, Propylene, Butenes, Benzene, and other by-products.',\n        caption: 'Propylene Containers',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/XVJ-12_Propylene_%288662385917%29.jpg/256px-XVJ-12_Propylene_%288662385917%29.jpg'\n      },\n      {\n        key: 10,\n        pos: '330 205',\n        icon: 'finishedgas',\n        iconWidth: 30,\n        iconHeight: 30,\n        portHeight: 15,\n        text: 'LPG, Naphtha,\\nMTBE',\n        description: 'Propane, butane, and other general purpose fuels and byproducts.',\n        caption: 'Liquid Petroleum Gas Truck',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/LPG_Truck.jpg/256px-LPG_Truck.jpg'\n      },\n      {\n        key: 11,\n        pos: '330 280',\n        icon: 'finishedgas',\n        iconWidth: 30,\n        iconHeight: 30,\n        portHeight: 15,\n        text: 'Natural Gas, NGLs',\n        description: 'Used for heating, cooking, and electricity generation',\n        caption: 'Natural Gas Flame',\n        imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/%22LPG_flame%22.JPG/512px-%22LPG_flame%22.JPG'\n      }\n    ];\n\n  model.linkDataArray = [\n    {\n      from: 1,\n      to: 4\n    },\n    {\n      from: 2,\n      to: 3,\n      label: 'APG'\n    },\n    {\n      from: 3,\n      to: 4\n    },\n    {\n      from: 3,\n      to: 5,\n      toSpot: 'bottom'\n    },\n    {\n      from: 4,\n      to: 5\n    },\n    {\n      from: 4,\n      to: 5\n    },\n    {\n      from: 3,\n      to: 11,\n      fromSpot: 'bottom'\n    },\n    {\n      from: 4,\n      to: 10,\n      fromSpot: 'bottom'\n    },\n    {\n      from: 5,\n      to: 6,\n      fromSpot: 'rightsingle',\n      color: 'orange'\n    },\n    {\n      from: 5,\n      to: 7,\n      fromSpot: 'rightsingle',\n      color: 'orange'\n    },\n    {\n      from: 5,\n      to: 8,\n      fromSpot: 'rightsingle',\n      color: 'orange'\n    },\n    {\n      from: 5,\n      to: 9,\n      fromSpot: 'rightsingle',\n      color: 'orange'\n    }\n  ];\n\n  myDiagram.model = model;\n\n  loop();  // animate some flow through the pipes\n\n  var opacity = 1;\n  var down = true;\n  function loop() {\n    var diagram = myDiagram;\n    setTimeout(function () {\n      var oldskips = diagram.skipsUndoManager;\n      diagram.skipsUndoManager = true;\n      diagram.links.each(function (link) {\n        var shape = link.findObject(\"PIPE\");\n        var off = shape.strokeDashOffset - 3;\n        // animate (move) the stroke dash\n        shape.strokeDashOffset = (off <= 0) ? 60 : off;\n        // animte (strobe) the opacity:\n        if (down) opacity = opacity - 0.01;\n        else opacity = opacity + 0.003;\n        if (opacity <= 0) { down = !down; opacity = 0; }\n        if (opacity > 1) { down = !down; opacity = 1; }\n        shape.opacity = opacity;\n      });\n      diagram.skipsUndoManager = oldskips;\n      loop();\n    }, 60);\n  }\n\n  function onSelectionChanged(e) {\n    var node = e.diagram.selection.first();\n    if (!(node instanceof go.Node)) return;\n    var data = node.data;\n    var image = document.getElementById('Image');\n    var title = document.getElementById('Title');\n    var description = document.getElementById('Description');\n\n    if (data.imgsrc) {\n      image.src = data.imgsrc;\n      image.alt = data.caption;\n    } else {\n      image.src = \"\";\n      image.alt = \"\";\n    }\n    title.textContent = data.text;\n    description.textContent = data.description;\n\n  }\n```\n\n## Step 2: Add native GoJS library to your widget as widget dependencies\n***Go to Service Portal > Widget ***\n- Search for your previous widget created \"Custom productionProcess\" (custom-gojs-productionProcess) and open the record. \n- On the related tab Dependencies, click on `Edit` button.\n- Search for gojs and add to your list\n- Click on `Save` button to save the change. \n\n## Step 3: Create a new Page\n***Go to Service Portal > Page > Click New***\n- Name: productionProcess - Test Page\n- ID: productionProcess\n- Click on `Submit` button.\n- Once submitted, Click on `Open in Page Designer` related link\n- In Page designer, Place `custom-gojs-productionProcess` widget inside a container > row > Column at top location.\n- View paget from following link `http://instance-name.service-now.com/sp?id=productionProcess`. \n\n## Sources\n***Any of following links are not under my surveilance or maintenance***\n\nhttps://github.com/NorthwoodsSoftware/GoJS/blob/master/samples/productionProcess.html\nhttps://gojs.net/latest/intro/toolTips.html\nhttp://g-mops.net/epica_gojs/api/symbols/Diagram.html"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using GoJS library/body.html",
    "content": "<div id=\"myDiagramDiv\" style=\"border: solid 1px black; width:70%; height:550px; display: inline-block; vertical-align: top\"></div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using GoJS library/client.js",
    "content": "// build GraphObject\nvar $ = go.GraphObject.make;  // for conciseness in defining templates\nmyDiagram = $(go.Diagram, \"myDiagramDiv\",  // create a Diagram for the DIV HTML element\n  {\n    maxSelectionCount: 1, // users can select only one part at a time\n    \"toolManager.hoverDelay\": 10,  // how quickly tooltips are shown\n    initialAutoScale: go.Diagram.Uniform,  // scale to show all of the contents\n    \"ChangedSelection\": onSelectionChanged, // view additional information\n  });\n\nfunction infoString(obj) {\n  var part = obj.part;\n  if (part instanceof go.Adornment) part = part.adornedPart;\n  var msg = \"\";\n  if (part instanceof go.Link) {\n    msg = \"\";\n  } else if (part instanceof go.Node) {\n    msg = part.data.text + \":\\n\\n\" + part.data.description;\n  }\n  return msg;\n}\n\nvar colors = {\n  \"red\": \"#be4b15\",\n  \"green\": \"#52ce60\",\n  \"blue\": \"#6ea5f8\",\n  \"lightred\": \"#fd8852\",\n  \"lightblue\": \"#afd4fe\",\n  \"lightgreen\": \"#b9e986\",\n  \"pink\": \"#faadc1\",\n  \"purple\": \"#d689ff\",\n  \"orange\": \"#fdb400\",\n};\n\n// A data binding conversion function. Given an name, return the Geometry.\n// If there is only a string, replace it with a Geometry object, which can be shared by multiple Shapes.\nfunction geoFunc(geoname) {\n  var geo = icons[geoname];\n  if (typeof geo === \"string\") {\n    geo = icons[geoname] = go.Geometry.parse(geo, true);\n  }\n  return geo;\n}\n\nmyDiagram.nodeTemplate =\n  $(go.Node, \"Spot\",\n    {\n      locationObjectName: 'main',\n      locationSpot: go.Spot.Center,\n      toolTip:\n        $(go.Adornment, \"Auto\",\n          $(go.Shape, { fill: \"#CCFFCC\" }),\n          $(go.TextBlock, { margin: 4, width: 140 }, //https://gojs.net/latest/intro/toolTips.html\n            new go.Binding(\"text\", \"\", infoString).ofObject()))\n      /*toolTip:\n        $(\"ToolTip\",\n          $(go.TextBlock, { margin: 4, width: 140 }, //https://gojs.net/latest/intro/toolTips.html\n            new go.Binding(\"text\", \"\", infoString).ofObject())\n        )*/\n    },\n    new go.Binding(\"location\", \"pos\", go.Point.parse).makeTwoWay(go.Point.stringify),\n    // The main element of the Spot panel is a vertical panel housing an optional icon,\n    // plus a rectangle that acts as the port\n    $(go.Panel, \"Vertical\",\n      $(go.Shape, {\n        name: 'icon',\n        width: 1, height: 1,\n        stroke: null, strokeWidth: 0,\n        fill: colors.blue\n      },\n        new go.Binding(\"fill\", \"color\", function (c) { return colors[c]; }),\n        new go.Binding(\"width\", \"iconWidth\"),\n        new go.Binding(\"height\", \"iconHeight\"),\n        new go.Binding(\"geometry\", \"icon\", geoFunc)),\n      $(go.Shape, {\n        name: 'main',\n        width: 40, height: 40,\n        margin: new go.Margin(-1, 0, 0, 0),\n        portId: \"\",\n        stroke: null, strokeWidth: 0,\n        fill: colors.blue\n      },\n        new go.Binding(\"fill\", \"color\", function (c) { return colors[c]; }),\n        new go.Binding(\"width\", \"portWidth\"),\n        new go.Binding(\"height\", \"portHeight\"))\n    ),\n\n    $(go.TextBlock, {\n      font: \"Bold 14px Lato, sans-serif\",\n      textAlign: \"center\",\n      margin: 3,\n      maxSize: new go.Size(100, NaN),\n      alignment: go.Spot.TopCenter,\n      alignmentFocus: go.Spot.BottomCenter\n    },\n      new go.Binding(\"text\"))\n\n  );\n\n// Some links need a custom to or from spot\nfunction spotConverter(dir) {\n  if (dir === \"left\") return go.Spot.LeftSide;\n  if (dir === \"right\") return go.Spot.RightSide;\n  if (dir === \"top\") return go.Spot.TopSide;\n  if (dir === \"bottom\") return go.Spot.BottomSide;\n  if (dir === \"rightsingle\") return go.Spot.Right;\n}\n\nmyDiagram.linkTemplate =\n  $(go.Link, {\n    toShortLength: -2,\n    fromShortLength: -2,\n    layerName: \"Background\",\n    routing: go.Link.Orthogonal,\n    corner: 15,\n    fromSpot: go.Spot.RightSide,\n    toSpot: go.Spot.LeftSide\n  },\n    // make sure links come in from the proper direction and go out appropriately\n    new go.Binding(\"fromSpot\", \"fromSpot\", function (d) { return spotConverter(d); }),\n    new go.Binding(\"toSpot\", \"toSpot\", function (d) { return spotConverter(d); }),\n\n    new go.Binding(\"points\").makeTwoWay(),\n    // mark each Shape to get the link geometry with isPanelMain: true\n    $(go.Shape, { isPanelMain: true, stroke: colors.lightblue, strokeWidth: 10 },\n      new go.Binding(\"stroke\", \"color\", function (c) { return colors[c]; })),\n    $(go.Shape, { isPanelMain: true, stroke: \"white\", strokeWidth: 3, name: \"PIPE\", strokeDashArray: [20, 40] })\n  );\n\n\n// build model\nvar model = $(go.GraphLinksModel);\n\nmodel.nodeDataArray =\n  [\n    {\n      key: 1,\n      pos: '-180 -57',\n      icon: 'natgas',\n      iconWidth: 30,\n      iconHeight: 60,\n      portHeight: 20,\n      text: 'Gas\\nCompanies',\n      description: 'Provides natural gas liquids (NGLs).',\n      caption: 'Gas Drilling Well',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/5d/BarnettShaleDrilling-9323.jpg/256px-BarnettShaleDrilling-9323.jpg'\n    },\n    {\n      key: 2,\n      pos: '-180 100',\n      icon: 'oil',\n      iconWidth: 40,\n      iconHeight: 60,\n      portHeight: 20,\n      text: 'Oil\\nCompanies',\n      description: 'Provides associated petroleum gas (APG). This type of gas used to be released as waste from oil drilling, but is now commonly captured for processing.',\n      caption: 'Offshore oil well',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/a/ab/Oil_platform_P-51_%28Brazil%29.jpg/512px-Oil_platform_P-51_%28Brazil%29.jpg'\n    },\n    {\n      key: 3,\n      pos: '-80 100',\n      icon: 'gasprocessing',\n      iconWidth: 40,\n      iconHeight: 40,\n      text: 'Gas Processing',\n      description: 'APG processing turns associated petrolium gas into natural gas liquids (NGLs) and stable natural gas (SGN).',\n      caption: 'Natural gas plant',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/6/68/Solohiv_natural_gas_plant_-_fragment.jpg/256px-Solohiv_natural_gas_plant_-_fragment.jpg'\n    },\n    {\n      key: 4,\n      pos: '30 -50',\n      icon: 'fractionation',\n      iconWidth: 40,\n      iconHeight: 60,\n      text: 'Gas Fractionation',\n      description: 'Natural gas liquids are separated into individual hydrocarbons like propane and butanes, hydrocarbon mixtures (naphtha) and liquefied petroleum gases (LPGs).',\n      caption: 'Gas Plant',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/20/Gasblok.jpg/256px-Gasblok.jpg'\n    },\n    {\n      key: 5,\n      pos: '130 -50',\n      icon: 'pyrolysis',\n      iconWidth: 40,\n      iconHeight: 40,\n      color: 'orange',\n      text: 'Pyrolysis (Cracking)',\n      description: 'Liquefied petroleum gases (LPGs) are transformed into Ethylene, propylene, benzene, and other by-products.',\n      caption: 'Pyrolysis plant',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/6/6c/Guelph.jpg'\n    },\n    {\n      key: 6,\n      pos: '330 -140',\n      icon: 'polymerization',\n      iconWidth: 40,\n      iconHeight: 40,\n      portHeight: 12,\n      color: 'red',\n      text: 'Basic Polymers',\n      description: 'Ethylene and propylene (monomers) are processed into end products using various methods (polymerization).',\n      caption: 'Plastics engineering-Polymer products',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/4/4c/Plastics_engineering-Polymer_products.jpg/256px-Plastics_engineering-Polymer_products.jpg'\n    },\n    {\n      key: 7,\n      pos: '330 -55',\n      icon: 'polymerization',\n      iconWidth: 40,\n      iconHeight: 40,\n      portHeight: 12,\n      color: 'green',\n      text: 'Plastics',\n      description: 'Polymerization produces PET, glycols, alcohols, expandable polystyrene, acrylates, BOPP-films and geosynthetics.',\n      caption: 'Lego Color Bricks',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/3/32/Lego_Color_Bricks.jpg/256px-Lego_Color_Bricks.jpg'\n    },\n    {\n      key: 8,\n      pos: '330 40',\n      icon: 'polymerization',\n      iconWidth: 40,\n      iconHeight: 40,\n      portHeight: 12,\n      color: 'lightgreen',\n      text: 'Synthetic Rubbers',\n      description: 'Polymerization produces commodity and specialty rubbers and thermoplastic elastomers.',\n      caption: 'Sheet of synthetic rubber coming off the rolling mill at the plant of Goodrich',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/23/Sheet_of_synthetic_rubber_coming_off_the_rolling_mill_at_the_plant_of_Goodrich.jpg/512px-Sheet_of_synthetic_rubber_coming_off_the_rolling_mill_at_the_plant_of_Goodrich.jpg'\n    },\n    {\n      key: 9,\n      pos: '330 115',\n      color: 'orange',\n      portHeight: 22,\n      text: 'Intermediates',\n      description: 'Produced Ethylene, Propylene, Butenes, Benzene, and other by-products.',\n      caption: 'Propylene Containers',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/2/2e/XVJ-12_Propylene_%288662385917%29.jpg/256px-XVJ-12_Propylene_%288662385917%29.jpg'\n    },\n    {\n      key: 10,\n      pos: '330 205',\n      icon: 'finishedgas',\n      iconWidth: 30,\n      iconHeight: 30,\n      portHeight: 15,\n      text: 'LPG, Naphtha,\\nMTBE',\n      description: 'Propane, butane, and other general purpose fuels and byproducts.',\n      caption: 'Liquid Petroleum Gas Truck',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/5/52/LPG_Truck.jpg/256px-LPG_Truck.jpg'\n    },\n    {\n      key: 11,\n      pos: '330 280',\n      icon: 'finishedgas',\n      iconWidth: 30,\n      iconHeight: 30,\n      portHeight: 15,\n      text: 'Natural Gas, NGLs',\n      description: 'Used for heating, cooking, and electricity generation',\n      caption: 'Natural Gas Flame',\n      imgsrc: 'https://upload.wikimedia.org/wikipedia/commons/thumb/0/03/%22LPG_flame%22.JPG/512px-%22LPG_flame%22.JPG'\n    }\n  ];\n\nmodel.linkDataArray = [\n  {\n    from: 1,\n    to: 4\n  },\n  {\n    from: 2,\n    to: 3,\n    label: 'APG'\n  },\n  {\n    from: 3,\n    to: 4\n  },\n  {\n    from: 3,\n    to: 5,\n    toSpot: 'bottom'\n  },\n  {\n    from: 4,\n    to: 5\n  },\n  {\n    from: 4,\n    to: 5\n  },\n  {\n    from: 3,\n    to: 11,\n    fromSpot: 'bottom'\n  },\n  {\n    from: 4,\n    to: 10,\n    fromSpot: 'bottom'\n  },\n  {\n    from: 5,\n    to: 6,\n    fromSpot: 'rightsingle',\n    color: 'orange'\n  },\n  {\n    from: 5,\n    to: 7,\n    fromSpot: 'rightsingle',\n    color: 'orange'\n  },\n  {\n    from: 5,\n    to: 8,\n    fromSpot: 'rightsingle',\n    color: 'orange'\n  },\n  {\n    from: 5,\n    to: 9,\n    fromSpot: 'rightsingle',\n    color: 'orange'\n  }\n];\n\nmyDiagram.model = model;\n\nloop();  // animate some flow through the pipes\n\nvar opacity = 1;\nvar down = true;\nfunction loop() {\n  var diagram = myDiagram;\n  setTimeout(function () {\n    var oldskips = diagram.skipsUndoManager;\n    diagram.skipsUndoManager = true;\n    diagram.links.each(function (link) {\n      var shape = link.findObject(\"PIPE\");\n      var off = shape.strokeDashOffset - 3;\n      // animate (move) the stroke dash\n      shape.strokeDashOffset = (off <= 0) ? 60 : off;\n      // animte (strobe) the opacity:\n      if (down) opacity = opacity - 0.01;\n      else opacity = opacity + 0.003;\n      if (opacity <= 0) { down = !down; opacity = 0; }\n      if (opacity > 1) { down = !down; opacity = 1; }\n      shape.opacity = opacity;\n    });\n    diagram.skipsUndoManager = oldskips;\n    loop();\n  }, 60);\n}\n\nfunction onSelectionChanged(e) {\n  var node = e.diagram.selection.first();\n  if (!(node instanceof go.Node)) return;\n  var data = node.data;\n  var image = document.getElementById('Image');\n  var title = document.getElementById('Title');\n  var description = document.getElementById('Description');\n\n  if (data.imgsrc) {\n    image.src = data.imgsrc;\n    image.alt = data.caption;\n  } else {\n    image.src = \"\";\n    image.alt = \"\";\n  }\n  title.textContent = data.text;\n  description.textContent = data.description;\n\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using GoJS library/style.css",
    "content": "#infobox {\n  width: 256px;\n  background: #757575;\n  color: #FFF;\n  padding: 20px;\n}\n\n#myDiagramDiv * {\n  outline: none;\n  -webkit-tap-highlight-color: rgba(255, 255, 255, 0); /* mobile webkit */\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using Highcharts library/README.md",
    "content": "# Creating spider web using Highcharts\n## Introduction\nIn this snippet you will create a custom spider web using a custom page and populating data using Highcharts native library\n\n## Step 1: Create a new Widget\n***Go to Service Portal > Widget > Click New***\n- Name: Custom productionProcess\n- Id: custom-gojs-productionProcess\n- Click on `submit`\n\n***Body HTML template***\n- Copy and paste below `HTML Code` in Widget's HTML Template section\n```HTML\n<div>  \n\t<!-- chart -->\n    <div id=\"container\"></div>\n    <p class=\"highcharts-description\">\n        A spiderweb chart or radar chart is a variant of the polar chart.\n        Spiderweb charts are commonly used to compare multivariate data sets,\n        like this demo using six variables of comparison.\n    </p>\n</div>\n```\n\n***CSS/SCSS***\n- Copy and paste below `CSS` in Widget's CSS/SCSS Section\n```CSS\n/* to be completed */\n```\n***Client Side Scripts***\n- Copy and Paste below `Script` in Widget's Client Side Section\n```javascript\napi.controller=function($rootScope, $scope, $window, $interval, spUtil) {\n  /* widget controller */\n\tvar c = this;\n\n\t/** Chart source: https://www.highcharts.com/demo/polar-spider*/\n\tvar options = {\n        credits: {\n            enabled: false\n        },\n\n        chart: {\n            renderTo: 'container', // change chart_id if needed\n            polar: true,\n            type: 'line'\n        },\n\n    accessibility: {\n        description: 'A spiderweb chart compares the allocated budget against actual spending within an organization. The spider chart has six spokes. Each spoke represents one of the 6 departments within the organization: sales, marketing, development, customer support, information technology and administration. The chart is interactive, and each data point is displayed upon hovering. The chart clearly shows that 4 of the 6 departments have overspent their budget with Marketing responsible for the greatest overspend of $20,000. The allocated budget and actual spending data points for each department are as follows: Sales. Budget equals $43,000; spending equals $50,000. Marketing. Budget equals $19,000; spending equals $39,000. Development. Budget equals $60,000; spending equals $42,000. Customer support. Budget equals $35,000; spending equals $31,000. Information technology. Budget equals $17,000; spending equals $26,000. Administration. Budget equals $10,000; spending equals $14,000.'\n    },\n\n    title: {\n        text: 'Budget vs spending',\n        x: -80\n    },\n\n    pane: {\n        size: '80%'\n    },\n\n    xAxis: {\n        categories: ['Sales', 'Marketing', 'Development', 'Customer Support',\n            'Information Technology', 'Administration'],\n        tickmarkPlacement: 'on',\n        lineWidth: 0\n    },\n\n    yAxis: {\n        gridLineInterpolation: 'polygon',\n        lineWidth: 0,\n        min: 0\n    },\n\n    tooltip: {\n        shared: true,\n        pointFormat: '<span style=\"color:{series.color}\">{series.name}: <b>${point.y:,.0f}</b><br/>'\n    },\n\n    legend: {\n        align: 'right',\n        verticalAlign: 'middle',\n        layout: 'vertical'\n    },\n\n    series: [{\n        name: 'Allocated Budget',\n        data: [43000, 19000, 60000, 35000, 17000, 10000],\n        pointPlacement: 'on'\n    }, {\n        name: 'Actual Spending',\n        data: [50000, 39000, 42000, 31000, 26000, 14000],\n        pointPlacement: 'on'\n    }],\n\n    responsive: {\n        rules: [{\n            condition: {\n                maxWidth: 500\n            },\n            chartOptions: {\n                legend: {\n                    align: 'center',\n                    verticalAlign: 'bottom',\n                    layout: 'horizontal'\n                },\n                pane: {\n                    size: '70%'\n                }\n            }\n        }]\n    }\n};\n\t\n  /*Generate chart*/\n\tvar chart = new Highcharts.Chart(options);\n  \n  /* improvements: next step would be to have a ng-selector in HTML and use record watcher to keep data up do date */\n\t\n};\n```\n\n## Step 2: Add native Highcharts library to your widget as widget dependencies\n***Go to Service Portal > Widget ***\n- Search for your previous widget created \"Custom Spider Web\" (custom-spider-web) and open the record. \n- On the related tab Dependencies, click on `Edit` button.\n- Search for PA Widget (4fbe3df5673322002c658aaad485ef29) and add to your list.\n- Click on `Save` button to save the change. \n\n## Step 3: Create a new Page\n***Go to Service Portal > Page > Click New***\n- Name: spiderweb - Test Page\n- ID: spiderweb\n- Click on `Submit` button.\n- Once submitted, Click on `Open in Page Designer` related link\n- In Page designer, Place `custom-spider-web` widget inside a container > row > Column at top location.\n- View paget from following link `http://instance-name.service-now.com/sp?id=spiderweb`. \n\n## Sources\n***Any of following links are not under my surveilance or maintenance***\n\nhttps://github.com/NorthwoodsSoftware/GoJS/blob/master/samples/productionProcess.html\nhttps://gojs.net/latest/intro/toolTips.html\nhttp://g-mops.net/epica_gojs/api/symbols/Diagram.html"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using Highcharts library/body.html",
    "content": "<div>  \n\t<!-- chart -->\n    <div id=\"container\"></div>\n    <p class=\"highcharts-description\">\n        A spiderweb chart or radar chart is a variant of the polar chart.\n        Spiderweb charts are commonly used to compare multivariate data sets,\n        like this demo using six variables of comparison.\n    </p>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Create diagram using Highcharts library/client.js",
    "content": "api.controller=function($rootScope, $scope, $window, $interval, spUtil) {\n  /* widget controller */\n\tvar c = this;\n\n\t/** Chart source: https://www.highcharts.com/demo/polar-spider*/\n\tvar options = {\n        credits: {\n            enabled: false\n        },\n\n        chart: {\n            renderTo: 'container', // change chart_id if needed\n            polar: true,\n            type: 'line'\n        },\n\n    accessibility: {\n        description: 'A spiderweb chart compares the allocated budget against actual spending within an organization. The spider chart has six spokes. Each spoke represents one of the 6 departments within the organization: sales, marketing, development, customer support, information technology and administration. The chart is interactive, and each data point is displayed upon hovering. The chart clearly shows that 4 of the 6 departments have overspent their budget with Marketing responsible for the greatest overspend of $20,000. The allocated budget and actual spending data points for each department are as follows: Sales. Budget equals $43,000; spending equals $50,000. Marketing. Budget equals $19,000; spending equals $39,000. Development. Budget equals $60,000; spending equals $42,000. Customer support. Budget equals $35,000; spending equals $31,000. Information technology. Budget equals $17,000; spending equals $26,000. Administration. Budget equals $10,000; spending equals $14,000.'\n    },\n\n    title: {\n        text: 'Budget vs spending',\n        x: -80\n    },\n\n    pane: {\n        size: '80%'\n    },\n\n    xAxis: {\n        categories: ['Sales', 'Marketing', 'Development', 'Customer Support',\n            'Information Technology', 'Administration'],\n        tickmarkPlacement: 'on',\n        lineWidth: 0\n    },\n\n    yAxis: {\n        gridLineInterpolation: 'polygon',\n        lineWidth: 0,\n        min: 0\n    },\n\n    tooltip: {\n        shared: true,\n        pointFormat: '<span style=\"color:{series.color}\">{series.name}: <b>${point.y:,.0f}</b><br/>'\n    },\n\n    legend: {\n        align: 'right',\n        verticalAlign: 'middle',\n        layout: 'vertical'\n    },\n\n    series: [{\n        name: 'Allocated Budget',\n        data: [43000, 19000, 60000, 35000, 17000, 10000],\n        pointPlacement: 'on'\n    }, {\n        name: 'Actual Spending',\n        data: [50000, 39000, 42000, 31000, 26000, 14000],\n        pointPlacement: 'on'\n    }],\n\n    responsive: {\n        rules: [{\n            condition: {\n                maxWidth: 500\n            },\n            chartOptions: {\n                legend: {\n                    align: 'center',\n                    verticalAlign: 'bottom',\n                    layout: 'horizontal'\n                },\n                pane: {\n                    size: '70%'\n                }\n            }\n        }]\n    }\n};\n\t\n  /*Generate chart*/\n\tvar chart = new Highcharts.Chart(options);\n  \n  /* improvements: next step would be to have a ng-selector in HTML and use record watcher to keep data up do date */\n\t\n};"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Custom Greetings in portal homepage/README.md",
    "content": "This code snippet will help you to provide customize greetings to end user when they login to the portal. And the time will be reflected as browser's time zone. By default javascript uses browser's time zone and which is further converted into hours as per the method I used in my clint script code. Below steps needs to be performed to achive this.\n1. Clone Homepage-search widget of portal.\n2. Update the client script of the cloned widget as per the code prvided in homepage-search-clint.js.\n3. Update the HTML as per homepage-search.html.\n4. Replace the Homepage-search widget with Custom Homepage-search widget from portal home page by going to Page in designer.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Custom Greetings in portal homepage/homepage-search-client.js",
    "content": "//Below code will be used in client script of portal.\napi.controller=function() {\n  /* widget controller */\n  var c = this;\n\t\n\tvar message ='';\n\tvar date = new Date(); //get the current date.\n\tvar hours = date.getHours(); //get the current hour\n\n// calculations based on hour to get the correct greeting\nif(hours > 4 && hours <= 12) {\n    message = message+ '${Good Morning}';\n} else if(hours > 12 && hours <= 16) {\n    message = message+ '${Good Afternoon}';\n} else if(hours > 16 && hours <= 20) {\n    message = message+ '${Good Evening}';\n} else if(hours > 20 || hours <= 4) {\n    message = message+ '${Good Night}';\n}\n\t\n\tc.data.greeting = 'Hi '+ scope.user.first_name +', '+ message +'. ' +'How can we help you?';\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Custom Greetings in portal homepage/homepage-search.html",
    "content": "<div id=\"homepage-search\" class=\"hidden-xs wrapper-xl\">\n  <div class=\"wrapper-xl\">\n  \t<I><h2 class=\"text-center text-3x m-b-lg sp-tagline-color\" ng-bind=\"data.greeting\"></h2></I>\n  \t<div ng-if=\"options.short_description\" class=\"text-center h4 m-b-lg sp-tagline-color\" ng-bind=\"options.short_description\"></div>\n  \t<sp-widget widget=\"data.typeAheadSearch\" />\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Custom attachment variable/README.md",
    "content": "# Attachment variable widget\n\nThis widget lets you use the oob attachment picker from the oob catalog item widget as a custom type variable giving you more control over attachment behaviour, you can for example use ui policies to hide and show the attachment picker. \n\n## Usage example\n\nCreate new widget [sp_widget]\nCopy template.html in the Body HTML template field\nCopy controller.js in the Client controller field\nAdd custom type variable using newly created widget positioned as last variable\nSet hide attachment for catalog item as true\nOpen cat item in portal and adjust widget as needed\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Custom attachment variable/controller.js",
    "content": "api.controller = function ($scope, nowAttachmentHandler, spUtil, spAttachmentUpload, $timeout, cabrillo, spModal) {\n\tvar c = this;\n\tc.isNative = cabrillo.isNative()\n  /*\n   * change table and guid if you want to attach to some other record\n   */\n\t$scope.table = $scope.page.g_form.recordTableName;\n\t$scope.guid = $scope.$parent.c.getAttachmentGuid();\n  // \n\t$scope.data.maxAttachmentSize = 24;\n\tvar ah = $scope.attachmentHandler = new nowAttachmentHandler(setAttachments, appendError);\n\tah.setParams($scope.table, $scope.guid, 1024 * 1024 * $scope.data.maxAttachmentSize);\n\n  // implement logic to show drag and drop picker or clip icon with text\n\t$scope.showDragAndDrop = function () {\n\t\tif (true)\n\t\t\treturn true;\n\t\telse\n\t\t\treturn false;\n\t}\n  /*\n   * callback function called after attachment action happens\n   * e.g. implement mandatory attachment\n   */\n\tfunction setAttachments(attachments, action) {\n\t\tif (!angular.equals($scope.attachments, attachments))\n\t\t\t$scope.attachments = attachments;\n\t\tif (action === \"added\") {\n\t\t\t// custom attachment added logic\n\t\t}\n\t\tif (action === \"renamed\") {\n\t\t\t// custom attachment renamed logic\n\t\t}\n\t\tif (action === \"deleted\") {\n\t\t\t// custom attachment deleted logic\n\t\t}\n\t\tspUtil.get($scope, {\n\t\t\taction: \"from_attachment\"\n\t\t});\n\t}\n  /*\n   * callback function called on error\n   */\n\tfunction appendError(error) {\n\t\tspUtil.addErrorMessage(error.msg + error.fileName);\n\t}\n\n  // drag & drop handler\n\t$scope.dropFiles = function (files) {\n\t\tif (files && files.length > 0) {\n\t\t\t$scope.attachmentUploadInProgress = true;\n\t\t\t$scope.totalFilesBeingUploaded++;\n\t\t\tspAttachmentUpload.uploadAttachments($scope.attachmentHandler, files);\n\t\t}\n\t\t$timeout(function () {\n\t\t\tif ($scope.attachmentUploadInProgress != false)\n\t\t\t\tspUtil.addInfoMessage(\"The attachment upload is in progress. Note that some actions are deactivated during the file upload process\");\n\t\t}, 2000);\n\t\t$scope.$on('attachment.upload.idle', function () {\n\t\t\t$scope.attachmentUploadInProgress = false;\n\t\t\t$scope.totalFilesBeingUploaded = 0;\n\t\t});\n\t};\n  //confirm delete dialog\n\t$scope.confirmDeleteAttachment = function (attachment) {\n\t\tif (c.isNative) {\n\t\t\tif (confirm(\"delete attachment?\")) {\n\t\t\t\t$scope.data.attachment_action_in_progress = true;\n\t\t\t\t$scope.attachmentHandler.deleteAttachment(attachment);\n\t\t\t}\n\t\t} else {\n\t\t\tspModal.confirm(\"delete attachment?\").then(function () {\n\t\t\t\t$scope.data.attachment_action_in_progress = true;\n\t\t\t\t$scope.attachmentHandler.deleteAttachment(attachment);\n\t\t\t});\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Custom attachment variable/template.html",
    "content": "<div ng-if=\"true\" class=\"wrapper-md row no-margin\" role=\"region\" data-label=\"Attachments\"\n   aria-label=\"${Attachments}\">\n   <div\n      ng-class=\"{'flex-center attachment-height': c.isNative == 'true', 'flex-start': c.isNative != 'true'}\">\n      <div ng-if=\"!submitting && !submitted\" style=\"font-weight:normal;cursor:default;margin-bottom:2rem;\">\n         <sp-attachment-button ng-if=\"::!showDragAndDrop()\" modal=\"true\"\n            required=\"{{data.mandatory_attachment}}\"></sp-attachment-button>\n         <sp-attachment-button ng-if=\"::showDragAndDrop()\" modal=\"true\"\n            required=\"{{data.mandatory_attachment}}\"\n            ng-class=\"{'hidden-xs': false, 'hidden-sm': true, 'hidden-md': true, 'hidden-lg': true}\"></sp-attachment-button>\n         <span class=\"fa fa-asterisk mandatory\" ng-if=\"data.mandatory_attachment\"\n            ng-class=\"{'mandatory-filled': data.mandatory_attachment && (data.attachment_submitted || attachments.length > 0)}\"\n            style=\"vertical-align:super\" aria-hidden=\"true\"></span>\n         <span ng-class=\"{'attachment-text' : options.native_mobile == 'true'}\" aria-hidden=\"true\">${Add\n         attachments}</span>\n      </div>\n   </div>\n   <div ng-if=\"::showDragAndDrop()\" class=\"panel panel-{{options.color}} b drag-and-drop-area\"\n      ng-class=\"{'hidden-xs': true}\" aria-hidden=\"true\">\n      <sp-attachment-picker on-file-pick=\"dropFiles($files)\"></sp-attachment-picker>\n   </div>\n   <span ng-if=\"attachmentUploadInProgress\">\n      ${Uploading attachments}\n      <div class=\"sp-loading-indicator la-sm\" style=\"color:black;display:inline\">\n         <div></div>\n         <div></div>\n         <div></div>\n      </div>\n   </span>\n   <now-attachments-list template=\"sp_attachment_single_line\"></now-attachments-list>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Digital Clock/README.md",
    "content": "# Digital Clock Widget for ServiceNow Portal\nThis widget displays a simple digital clock with a black background and white text. It shows the current time in hours, minutes, and seconds and updates every second.\n\n## Features\nDisplays the current time in HH:MM:SS format.\nBlack background with white text for easy readability.\nAutomatic time update every second.\n\n## Usage\nAdd this widget to any ServiceNow Portal page where you want to display the digital clock.\nThe clock automatically updates every second without requiring any additional configuration.\n\n## Customization\nBackground Color: You can change the background-color property in CSS to a different color if desired.\nFont Color: Modify the color property to set a custom text color."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Digital Clock/index.html",
    "content": "<div class=\"digital-clock\">\n    <span>{{ currentTime }}</span>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Digital Clock/script.js",
    "content": "api.controller = function($scope, $interval) {\n    function updateTime() {\n        var now = new Date();\n        $scope.currentTime = now.toLocaleTimeString();\n    }\n\n    // Initialize the clock on load\n    updateTime();\n\n    // Update the clock every second\n    $interval(updateTime, 1000);\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Digital Clock/style.css",
    "content": ".digital-clock {\n    display: flex;\n    justify-content: center;\n    align-items: center;\n    height: 100px;\n    width: 200px;\n    margin: auto;\n    background-color: #000;\n    color: #fff;\n    font-family: 'Courier New', Courier, monospace;\n    font-size: 2em;\n    border-radius: 5px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drag & drop Widget/README.md",
    "content": "# Drag and Drop Widget\n\n## Description\nThis widget allows users to interactively move objects from a side to another within Service Portal.\n\nThe core benefit of this widget is the drag and drop functionality that provide the user a way to dinamically drag and drop objects in the Service Portal.\nStarting from this demo, through the customization they could be built even more interesting features for clients. \n\n\n## Installation\nTo install and use the Drag and Drop Widget in your ServiceNow ServicePortal:\n\n1. Create a new Service Portal widget\n2. Include the widget on the desired portal page.\n3. Copy and paste the code\n4. Upload the \"x\" icon to the images (db_image)\n5. Done! Now you can try the page\n\n## Usage\nOn this demo, it's just possible to drag and drop the items from \"Side 1\" to \"Side 2\" column, with the possibility to also remove it simply by clicking on the \"x\" icon.\n\n## Customization\nConsidering to customize this widget in order to get full benefit from drag and drop in Service portal, it will take the potential to the next level:\nfor example, it could be used for a to-do tasks list, to reorder items and cards, priorities objects and much more...\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drag & drop Widget/client.js",
    "content": "function($scope) {\n    var c = this;\n    $scope.onLoad = function() {\n        c.server.update().then(function(response) {\n            $scope.showhide = {\n                value: \"block\",\n                name: 'showhide'\n            };\n        });\n    }\n\n    c.dragStart = function(event) {\n\n        event.target.style.opacity = '0.7';\n        event.target.style.cursor = 'grabbing';\n        event.dataTransfer.effectAllowed = 'move';\n        event.dataTransfer.setData('text/html', event.target.innerHTML);\n        c.draggedNode = event.target.id;\n\n        document.querySelectorAll('.task').forEach(function(cur) {\n            if (cur.id !== event.target.id) {\n                cur.classList.add('disabled');\n            }\n        })\n    };\n\n    c.dragEnd = function(event) {\n        event.target.style.opacity = '1';\n        event.target.style.cursor = 'grab';\n        var cols = document.querySelectorAll('.task-col-content');\n        cols.forEach(function(cur) {\n            cur.classList.remove('over')\n        });\n        document.querySelectorAll('.task').forEach(function(cur) {\n            cur.classList.remove('disabled');\n        })\n    };\n\n    c.dragEnter = function(event) {\n        if (event.target && event.target.classList) {\n            event.target.classList.add('over');\n        }\n    };\n\n    c.dragLeave = function(event) {\n        if (event.target && event.target.classList) {\n            event.target.classList.remove('over');\n        }\n    };\n\n    c.dragOver = function(event) {\n        if (event.preventDefault) {\n            event.preventDefault();\n        }\n        event.dataTransfer.dropEffect = 'move';\n        return false;\n    };\n\n    function findColType(element) {\n        var maxLevels = 4;\n        return checkElement(element, 1);\n\n        function checkElement(element, level) {\n            if (!element) {\n                return null;\n            }\n            if (level >= maxLevels) {\n                return null;\n            }\n            if (element.classList && element.classList.contains('task-col')) {\n                return element.id;\n            }\n            return checkElement(element.parentElement, level++);\n        }\n    }\n\n    c.drop = function(event) {\n\t\t\t\n        if (event.stopPropagation) {\n            event.stopPropagation();\n        }\n        c.taskid = event.target.id;\n        c.data.isPushable = true;\n\t\t\t\n        var colType = findColType(event.target);\n        var tasksarr = c.data.tasks;\n        var states;\n\t\t\t\n        for (var k = 0; k < tasksarr.length; k++) {\n            states = c.data.tasks[parseInt(k)].state;\n            var index_element = states.indexOf(colType);\n            if (index_element > -1) {\n                c.data.isPushable = false;\n                break;\n            }\n        }\n\n        if (c.data.isPushable) {\n            c.data.tasks[parseInt(c.draggedNode)].state.push(colType);\n        }\n\n        $scope.$apply();\n    };\n\n\n    c.removeElement = function(ev) {\n\n        c.taskid = ev.target.id;\n        var colType = findColType(ev.target);\n        var states = c.data.tasks[parseInt(c.taskid)].state;\n        var index_element = states.indexOf(colType);\n        states.splice(index_element, 1);\n        c.data.tasks[parseInt(c.taskid)].state = states;\n        $scope.$apply();\n    };\n\n    c.data.savebtn = false;\n\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drag & drop Widget/html.html",
    "content": "<div class=\"panel-heading\" ng-init=\"onLoad()\">\n   <h3 class=\"h4 panel-title\">\n      <span>\n         <fa name=\"{{::c.options.glyph}}\"></fa>\n      </span>\n   </h3>\n</div>\n<div id=\"drag-n-drop\">\n   <script>\n      function getScope() {\n          return angular.element('#drag-n-drop').scope().c;\n      }\n   </script>\n   <div id=\"taskboard-container\">\n      <div class=\"taskboard-header\">\n         <h1>Drag and drop widget</h1>\n      </div>\n      <div class=\"task-col right-border\" id=\"todo\">\n         <div class=\"task-col-header\">\n            Side 1\n         </div>\n         <div class=\"task-col-content\">\n            <div class=\"task\" ng-repeat=\"task in c.data.tasks\" id=\"{{task.id}}\"\n               draggable=\"true\" ondragstart=\"getScope().dragStart(event)\" ondragend=\"getScope().dragEnd(event)\">\n               <div class=\"task-header\">\n                  {{task.title}}\n                  <div ng-if=\"task.type=='reference'\" style=\"display: inline;\">\n                     <button id=\"{{task.id}}\" onmouseover=\"getScope().getReferenceFields(event)\" class=\"ref-btn\">></button>\n                  </div>\n               </div>\n               <div id=\"container-{{task.id}}\" class=\"dropdown-container\" >\n               </div>\n            </div>\n         </div>\n      </div>\n      <div class=\"task-col right-border\">\n         <div class=\"task-col-header\">\n            Side 2\n         </div>\n         <div class=\"form-row\">\n            <div class=\"form-group\">\n               <div ng-repeat=\"x in data.arrfields\">\n                  <div class=\"task-col right-border\" id=\"{{x.label_field}}\">\n                     <div id=\"{{x.label_field}}\" class=\"task-col-content droppable\" ondragenter=\"getScope().dragEnter(event)\" \n                        ondragleave=\"getScope().dragLeave(event)\" \n                        ondragover=\"getScope().dragOver(event)\" \n                        ondrop=\"getScope().drop(event)\" >\n                        {{x.label_field}}\n                     </div>\n                     <div class=\"task-pdf\" ng-repeat=\"task in c.data.tasks\" ng-if=\"task.state.indexOf(x.label_field) > -1\" id=\"{{task.id}}\"\n                        ondragstart=\"getScope().dragStart(event)\" ondragend=\"getScope().dragEnd(event)\">\n                        <div class=\"task-header\">\n                           {{task.title}} <a class=\"x-btn\" id=\"{{task.id}}\" onclick=\"getScope().removeElement(event)\">x</a>\n                        </div>\n                     </div>\n                  </div>\n               </div>\n            </div>\n         </div>\n      </div>\n   </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drag & drop Widget/server.js",
    "content": "(function() {\n    if (input) {\n\t\t\t\n        var arrfields = [];\n        arrfields.push({\n            \"label_field\": \"Field A\",\n            \"internal_name\": \"field_a\"\n        }, {\n            \"label_field\": \"Field B\",\n            \"internal_name\": \"field_b\"\n        }, {\n            \"label_field\": \"Field C\",\n            \"internal_name\": \"field_c\"\n        }, {\n            \"label_field\": \"Field D\",\n            \"internal_name\": \"field_d\"\n        }, {\n            \"label_field\": \"Field E\",\n            \"internal_name\": \"field_e\"\n        }, {\n            \"label_field\": \"Field F\",\n            \"internal_name\": \"field_f\"\n        }, {\n            \"label_field\": \"Field G\",\n            \"internal_name\": \"field_g\"\n        });\n\n        data.arrfields = arrfields;\n        var arr_fields = [];\n\n        arr_fields = [];\n\t\t\t\n        arr_fields.push({\n            \"label\": \"Field1\",\n            \"name\": \"field_1\"\n        }, {\n            \"label\": \"Field2\",\n            \"name\": \"field_2\"\n        }, {\n            \"label\": \"Field3\",\n            \"name\": \"field_3\"\n        }, {\n            \"label\": \"Field4\",\n            \"name\": \"field_4\"\n        }, {\n            \"label\": \"Field5\",\n            \"name\": \"field_5\"\n        });\n\n        var tasks = [];\n        var arrs = [];\n        var i;\n        var h;\n        arrs[0] = \"todo\";\n\t\t\t\n        for (i = 0; i < arr_fields.length; i++) {\n            tasks.push({\n                \"id\": i,\n                \"name\": arr_fields[i].name,\n                \"title\": arr_fields[i].label,\n                \"state\": arrs\n            });\n        }\n\t\t\t\n        data.tasks = tasks;\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drawer Buttons/README.md",
    "content": "To use this widget, follow the below steps:\n\n1. Create a new widget and copy the html, style in the widget.\n2. Add the widget on the homepage .\n4. Change the width and placement according to your need.\n\n\nHere is Page Content structure\n\n<img width=\"326\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5fe8fcb7-858c-431a-91f9-5e31537c656d\">\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drawer Buttons/html_template.html",
    "content": "<!--drawer buttons-->\n<div id=\"escButtons\" >\n  <a href=\"#\" id=\"policies\">Policies</a>\n  <a href=\"#\" id=\"divisions\">Divisions</a>\n  <a href=\"#\" id=\"contact\">Contact</a>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Drawer Buttons/style.css",
    "content": "\n//css for styling of all the buttons\n#escButtons a {\n  position: absolute;\n  right: -90px;\n  transition: 0.3s;\n  border:2px solid white;\n  border-radius: 20px;\n  padding: 12px;\n  width: 140px;\n  font-size: 20px;\n  color: white;\n  \n}\n//css for hover on the buttons\n#escButtons a:hover {\n  right: 0;\n  border:3px solid #fc0366;\n  color:#fc0366;\n}\n//css for button policies\n#policies {\n  top: 20px;\n  background-color: #9bf542;\n}\n//css for button divisions\n#divisions {\n  top: 80px;\n  background-color: #2577db;\n}\n//css for button contact\n#contact {\n  top: 140px;\n  background-color:#fcdb03;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dropdown Widget/README.md",
    "content": "# FAQ Dropdown Widget\n\nThis is a simple FAQ dropdown widget for ServiceNow Portal that allows users to click on questions to reveal their answers. The widget is designed to enhance user experience by providing easy access to frequently asked questions.\n\n## Features\n\n- Interactive dropdown for questions and answers.\n- Animated arrow indicators that change based on the answer's visibility.\n- Simple and clean design that fits seamlessly into the ServiceNow Portal.\n\n## Usage\n\nAdd the Widget to a Page\nOnce the widget is created, add it to a desired page in your ServiceNow portal.\nInteract with the FAQ\nLoad the portal to view the widget and click on the questions to reveal the answers.\n\n\n## Customization\n\n\nYou can modify the faqs array in the Client Script to add your own questions and answers.\nCustomize the CSS styles to match your portal's design."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dropdown Widget/index.html",
    "content": "<div class=\"faq-widget\">\n    <h2>Frequently Asked Questions</h2>\n    <div ng-repeat=\"faq in faqs\" class=\"faq-item\">\n        <div class=\"faq-question\" ng-click=\"faq.show = !faq.show\">\n            <h3>{{ faq.question }}</h3>\n            <span ng-class=\"{'arrow-up': faq.show, 'arrow-down': !faq.show}\"></span>\n        </div>\n        <div class=\"faq-answer\" ng-show=\"faq.show\">\n            <p>{{ faq.answer }}</p>\n        </div>\n    </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dropdown Widget/script.js",
    "content": "api.controller = function($scope) {\n    // Sample FAQs\n    $scope.faqs = [\n        {\n            question: 'What is ServiceNow?',\n            answer: 'ServiceNow is a cloud-based platform that helps companies manage their digital workflows.'\n        },\n        {\n            question: 'How do I create a ticket?',\n            answer: 'You can create a ticket by navigating to the Service Desk and filling out the form.'\n        },\n        {\n            question: 'What are the system requirements?',\n            answer: 'ServiceNow is a cloud-based solution, so it only requires a modern web browser to access.'\n        },\n        {\n            question: 'How do I reset my password?',\n            answer: 'You can reset your password by clicking on the \"Forgot Password\" link on the login page.'\n        }\n    ];\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dropdown Widget/style.css",
    "content": ".faq-widget {\n    max-width: 600px;\n    margin: auto;\n    font-family: Arial, sans-serif;\n}\n\nh2 {\n    text-align: center;\n    color: #333;\n}\n\n.faq-item {\n    border: 1px solid #ccc;\n    border-radius: 5px;\n    margin-bottom: 10px;\n    overflow: hidden;\n    transition: box-shadow 0.2s ease;\n}\n\n.faq-item:hover {\n    box-shadow: 0 0 10px rgba(0, 0, 0, 0.2);\n}\n\n.faq-question {\n    background-color: #007bff; /* Header background color */\n    color: white;\n    padding: 15px;\n    cursor: pointer;\n    display: flex;\n    justify-content: space-between; /* Align question and arrow */\n    align-items: center; /* Center vertically */\n}\n\n.faq-question h3 {\n    margin: 0; /* Remove default margin */\n}\n\n.faq-answer {\n    padding: 15px;\n    background-color: #f9f9f9; /* Answer background color */\n    color: #333;\n}\n    \n.arrow-up {\n    border: solid white;\n    border-width: 0 4px 4px 0;\n    display: inline-block;\n    padding: 3px;\n    transform: rotate(-135deg);\n}\n\n.arrow-down {\n    border: solid white;\n    border-width: 0 4px 4px 0;\n    display: inline-block;\n    padding: 3px;\n    transform: rotate(45deg);\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dynamic Table and Record Selector/Client Side.js",
    "content": "api.controller=function($scope, $http, spUtil, $rootScope, $timeout, spModal) {\n\t/* widget controller */\n\tvar c = this;\n\n\t// Initialize scope variables\n\t$scope.tablename = {\n\t\tdisplayValue: '',\n\t\tvalue: '',\n\t\tname: 'tablename'\n\t};\n\t$scope.record = {\n\t\tdisplayValue: '',\n\t\tvalue: '',\n\t\tname: 'record'\n\t};\n\t$scope.selectedTable = '';\n\t$scope.TableSysId = '';\n\n\t// Handle field changes (table/record)\n\t$scope.$on(\"field.change\", function(evt, parms) {\n\t\tif (parms.field.name === 'tablename') {\n\t\t\t// Get sys_id of selected table → fetch actual table name & label\n\t\t\tvar sysId = parms.newValue;\n\t\t\tvar url = '/api/now/table/sys_db_object/' + sysId + '?sysparm_fields=name,label';\n\t\t\t$http.get(url).then(function(res) {\n\t\t\t\tif (res.data.result) {\n\t\t\t\t\t$scope.selectedTable = res.data.result.name;\n\t\t\t\t\t$scope.selectedTableLabel = res.data.result.label;\n\t\t\t\t\tc.getDisplayField($scope.selectedTable, sysId); // fetch display field\n\t\t\t\t}\n\t\t\t});\n\t\t} else if (parms.field.name === 'record') {\n\t\t\t// Save selected record sys_id\n\t\t\t$scope.TableSysId = parms.newValue;\n\t\t}\n\t});\n\n\t// Get display field for a table (recursive if needed)\n\tc.getDisplayField = function(tableName, tablesysId) {\n\t\tvar url = '/api/now/table/sys_dictionary' +\n\t\t\t\t'?sysparm_query=name=' + tableName + '^display=true' +\n\t\t\t\t'&sysparm_fields=element' +\n\t\t\t\t'&sysparm_limit=1';\n\n\t\t$http.get(url).then(function(response) {\n\t\t\tif (response.data.result && response.data.result.length > 0) {\n\t\t\t\t// Found display field\n\t\t\t\t$scope.recorddisplayValue = response.data.result[0].element;\n\t\t\t} else {\n\t\t\t\t// Check parent table\n\t\t\t\tvar parentsysIdUrl = '/api/now/table/sys_db_object/' + tablesysId + '?sysparm_fields=super_class';\n\t\t\t\t$http.get(parentsysIdUrl).then(function(parentRes) {\n\t\t\t\t\tvar parentTable = parentRes.data.result.super_class.value;\n\n\t\t\t\t\tif (!parentTable) {\n\t\t\t\t\t\t// No parent - fallback checks\n\t\t\t\t\t\tvar nameCheckUrl = '/api/now/table/sys_dictionary' +\n\t\t\t\t\t\t\t\t'?sysparm_query=name=' + tableName + '^element=name' +\n\t\t\t\t\t\t\t\t'&sysparm_fields=element&sysparm_limit=1';\n\n\t\t\t\t\t\t$http.get(nameCheckUrl).then(function(nameRes) {\n\t\t\t\t\t\t\tif (nameRes.status == 200) {\n\t\t\t\t\t\t\t\t$scope.recorddisplayValue = 'name';\n\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\tvar numberCheckUrl = '/api/now/table/sys_dictionary' +\n\t\t\t\t\t\t\t\t\t\t'?sysparm_query=name=' + tableName + '^element=number' +\n\t\t\t\t\t\t\t\t\t\t'&sysparm_fields=element&sysparm_limit=1';\n\n\t\t\t\t\t\t\t\t$http.get(numberCheckUrl).then(function(numberRes) {\n\t\t\t\t\t\t\t\t\tif (numberRes.status == 200) {\n\t\t\t\t\t\t\t\t\t\t$scope.recorddisplayValue = 'number';\n\t\t\t\t\t\t\t\t\t} else {\n\t\t\t\t\t\t\t\t\t\t$scope.recorddisplayValue = 'sys_id'; // Final fallback\n\t\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t\t});\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\n\t\t\t\t\t} else {\n\t\t\t\t\t\t// Parent exists - recurse\n\t\t\t\t\t\tvar parentNameUrl = '/api/now/table/sys_db_object/' + parentTable + '?sysparm_fields=name';\n\t\t\t\t\t\t$http.get(parentNameUrl).then(function(parentResname) {\n\t\t\t\t\t\t\tvar parentTableName = parentResname.data.result.name;\n\t\t\t\t\t\t\tc.getDisplayField(parentTableName, parentTable); // recursive lookup\n\t\t\t\t\t\t});\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t}\n\t\t}, function(error) {\n\t\t\tspModal.alert(\"Error fetching display field: \" + error);\n\t\t});\n\t};\n\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dynamic Table and Record Selector/HTML.html",
    "content": "<div class=\"row\">\n  <div class=\"col-md-6\">\n    <label class=\"form-label\">Select Table</label>\n    <sn-record-picker \n                      field=\"tablename\" \n                      table=\"'sys_db_object'\" \n                      display-field=\"'label'\" \n                      value-field=\"'sys_id'\" \n                      search-fields=\"'label'\" \n                      page-size=\"100\">\n    </sn-record-picker>\n  </div>\n\n  <!-- After a table is chosen, show record selector -->\n  <div class=\"col-md-6\" ng-show=\"selectedTable && recorddisplayValue\">\n    <label class=\"form-label\">Select Record</label>\n    <sn-record-picker \n                      field=\"record\" \n                      table=\"selectedTable\" \n                      display-field=\"recorddisplayValue\" \n                      value-field=\"'sys_id'\"\n                      search-fields=\"recorddisplayValue\"\n                      page-size=\"100\">\n    </sn-record-picker>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Dynamic Table and Record Selector/README.md",
    "content": "Widget Name: Dynamic Table and Record Selector\n\nOverview:\nThis ServiceNow Service Portal widget allows users to dynamically select any table and then choose a record from that table. The widget automatically determines which field should be shown as the display field for the selected table. It also handles parent table inheritance and provides fallback options for display fields.\n\nMain Features:\n\nLists all tables from the sys_db_object table.\n\nAutomatically finds the correct display field (field with display=true).\n\nSupports parent table lookup if the child table does not have a display field.\n\nProvides fallback checks for fields named \"name\", \"number\", or defaults to \"sys_id\".\n\nUses ServiceNow REST APIs to fetch metadata and record data dynamically.\n\nWorks with the standard sn-record-picker directive in Service Portal.\n\nHow It Works:\n\nThe first record picker displays all tables from sys_db_object using the label field.\n\nWhen the user selects a table, the widget fetches the actual table name and label using the sys_id.\n\nThe controller calls the getDisplayField function to determine which field should be displayed in the record picker.\n\nIt checks sys_dictionary for a field with display=true.\n\nIf found, that field is used as the display field.\n\nIf not found, it checks if the table has a parent (super_class).\n\nIf a parent exists, it recursively checks the parent table.\n\nIf there is no parent, it uses fallback checks for \"name\", then \"number\", and finally \"sys_id\".\n\nThe second record picker then displays the records from the selected table using the determined display field.\n\nWhen the user selects a record, its sys_id is stored in the variable TableSysId.\n\nExample Flow:\n\nSelect “Incident” from the table picker.\n\nThe widget detects that the display field is “number”.\n\nThe record picker lists incident numbers.\n\nWhen a record is selected, its sys_id is saved for further use.\n\nTechnologies Used:\n\nServiceNow Service Portal\n\nAngularJS (spUtil, spModal)\n\nServiceNow REST API:\n\n/api/now/table/sys_db_object\n\n/api/now/table/sys_dictionary\n\nUse Cases:\n\nCreating dynamic reference selectors for any table.\n\nBuilding tools that link or map data between tables.\n\nCMDB record selection where tables may have inheritance.\n\nGeneric admin utilities or catalog forms needing flexible input.\n\nFile Components:\n\nHTML Template: Contains two sn-record-picker elements for selecting table and record.\n\nClient Controller (JS): Handles field change events, fetches table metadata, determines display fields, and manages recursion logic.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Emoji Replacer Widget/CSS-SCSS.css",
    "content": ".card{\n  max-width:600px;\n  margin: auto;\n  box-shadow: 0 2px 6px rgba(0,0,0,0.1);\n  border-radius: 12px;\n}\ntextarea{\n resize: none; \n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Emoji Replacer Widget/Client Script.js",
    "content": "api.controller=function($scope) {\n  /* widget controller */\n  var c = this;\n\t\n\tc.emojiMap ={\n\t\t':smile:' :'😊',\n\t\t':sad:':'😓',\n\t\t\":heart:\":'❤️',\n\t\t\":thumbsup:\":'👍',\n\t\t\":laugh:\":\"😀\",\n\t\t\":wink:\":\"😉\",\n\t\t\":clap:\":\"👏\",\n\t\t\":party:\" :\"🥳\"\n\t};\n\t\n\tc.replaceEmojis = function(){\n\t\tvar text = $scope.data.inputText || '';\n\t\t\n\t\tfor(var key in c.emojiMap){\n\t\t\tvar regex = new RegExp(key.replace(/([.*+?^${}()|\\[\\]\\/\\\\])/g,\"\\\\$1\"),'g');\n\t\t\ttext = text.replace(regex,c.emojiMap[key]);\n\t\t}\n\t\tc.outputText= text;\n\t}\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Emoji Replacer Widget/HTML.html",
    "content": "<div class=\"card p-3\">\n  <h4>😊\n    Emoji Replacer\n  </h4>\n  <p class=\"text-muted\">\n    Type something using emoji shortcuts like <code>:smile:</code>,<code>:heart:</code>, or<code>:thumbsup:</code>\n  </p>\n  <textarea class=\"form-control\" rows=\"4\" ng-model=\"data.inputText\" ng-change=\"c.replaceEmojis()\"\n        placeholder=\"Type your message here...\"></textarea>\n  <div class =\"mt-3\">\n    <h5>\n      Output Preview:\n    </h5>\n    <div class=\"p-2 border rounded bg-light\" ng-bind-html=\"c.outputText\">\n      \n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Emoji Replacer Widget/README.md",
    "content": "## Emoji Replacer Widget\n\nThis widget enhances the user experience by automatically converting emojis code into visual emojis while typing - adding personality and clarity to text communication.\n## How It works\n- User types in a text box:\n- \"Great job team!:tada::thumbsup:\"\n- Script will detects matching emoji code using regex.\n- The widget replaces them with real emojis:\n- \"Great job team!🎉👍\n## Available Emoji in Widget\n \":smile:\" :😊,\n   \":sad:\":😓,\n\t  \":heart:\":❤️,\n\t\t\":thumbsup:\":👍,\n\t\t\":laugh:\":😀,\n\t\t\":wink:\":😉,\n\t\t\":clap:\":👏,\n\t\t\":party:\":🥳,\n\t\t\":tada:\":🎉\n## Output\n\n![Emoji Output](emoji.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Export table in portal/README.md",
    "content": "This widget can be used to download/export a table based on the given query and list view from a button click on the widget"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Export table in portal/export.html",
    "content": "<div>\n\n    <a ng-attr-target=\"{{ '_blank' }}\"\n       ng-attr-download = \"{{ fileName }}\" \n       ng-href = \"{{ link }}\">\n      <span ng-click=\"exportToCsv()\">\n        Export to CSV\n      </span>\n    </a>\n\n  </div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Export table in portal/export.js",
    "content": "function ($scope) {\n\n    $scope.exportToCsv = function () {\n    //https://<myinstance>.service-now.com/<TableName>.do?XML&useUnloadFormat=true&sysparm_view=<ViewName>&sysparm_query=<yourQueryHere>\n    $scope.link = '/test_table_list.do?CSV&useUnloadFormat=true&sysparm_view=exportview&sysparm_query=id%3Dtest';\n    //download file name\n    $scope.fileName = 'Test.csv';\n};\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Fill survey or item from url/README.md",
    "content": "# Auto-fill form from base64encoded data\n\nThis widget automatically populates a form on service portal using base64-encoded JSON passed via a URL parameter. Use the variable name as key or for surveys use the sys_id of the [asmt_assessment_instance_question]\n\n## How to\n\n1. Create a new widget and add the server script and client controller from the *Survey or form filler widget.js* file\n2. Add the widget for example as a catalog item variable or add it anywhere on your survey taking page such as *take_survey*\n3. Encode json of form fields and values key-value pairs and add it as a parameter called data into your url\n4. Navigate to page and form autofills\n\n## To note\nFor catalog items the variable name is the key, for surveys the key to use is the sys_id of the [asmt_assessment_instance_question]\n\n\n## Example usage\n\n```javascript\nvar obj = {\n\tis_this_a_replacement_for_a_lost_or_broken_iphone: \"yes\",\n\twhat_was_the_original_phone_number: \"1234567980\",\n\tmonthly_data_allowance: \"Unlimited\",\n\tcolor: \"red\",\n\tstorage: \"256\",\n}\n\ngs.info(GlideStringUtil.base64Encode(JSON.stringify(obj)))\n/*\n*** Script: eyJpc190aGlzX2FfcmVwbGFjZW1lbnRfZm9yX2FfbG9zdF9vcl9icm9rZW5faXBob25lIjoieWVzIiwid2hhdF93YXNfdGhlX29yaWdpbmFsX3Bob25lX251bWJlciI6IjEyMzQ1Njc5ODAiLCJtb250aGx5X2RhdGFfYWxsb3dhbmNlIjoiVW5saW1pdGVkIiwiY29sb3IiOiJyZWQiLCJzdG9yYWdlIjoiMjU2In0=\n-->\nhttps://{instancename}.service-now.com/esc?id=sc_cat_item&sys_id=ec80c13297968d1021983d1e6253af32&data=eyJpc190aGlzX2FfcmVwbGFjZW1lbnRfZm9yX2FfbG9zdF9vcl9icm9rZW5faXBob25lIjoieWVzIiwid2hhdF93YXNfdGhlX29yaWdpbmFsX3Bob25lX251bWJlciI6IjEyMzQ1Njc5ODAiLCJtb250aGx5X2RhdGFfYWxsb3dhbmNlIjoiVW5saW1pdGVkIiwiY29sb3IiOiJyZWQiLCJzdG9yYWdlIjoiMjU2In0%3D\n*/\nvar arr = {\n\t\"b3bf8ec283683210557ff0d6feaad327\": 1,\n\t\"bbbf8ec283683210557ff0d6feaad326\": 2,\n\t\"b7bf8ec283683210557ff0d6feaad327\": 3,\n\t\"bfbf8ec283683210557ff0d6feaad326\": 4,\n\t\"fbbf8ec283683210557ff0d6feaad325\": \"it is good\"\n}\ngs.print(GlideStringUtil.base64Encode(JSON.stringify(arr)))\n/*\n*** Script: eyJiM2JmOGVjMjgzNjgzMjEwNTU3ZmYwZDZmZWFhZDMyNyI6MSwiYmJiZjhlYzI4MzY4MzIxMDU1N2ZmMGQ2ZmVhYWQzMjYiOjIsImI3YmY4ZWMyODM2ODMyMTA1NTdmZjBkNmZlYWFkMzI3IjozLCJiZmJmOGVjMjgzNjgzMjEwNTU3ZmYwZDZmZWFhZDMyNiI6NCwiZmJiZjhlYzI4MzY4MzIxMDU1N2ZmMGQ2ZmVhYWQzMjUiOiJpdCBpcyBnb29kIn0=\n-->\nhttps://{instancename}.service-now.com/esc?id=take_survey&type_id=cf6e97d35d371200964f58e4abb23f18&data=eyJiM2JmOGVjMjgzNjgzMjEwNTU3ZmYwZDZmZWFhZDMyNyI6MSwiYmJiZjhlYzI4MzY4MzIxMDU1N2ZmMGQ2ZmVhYWQzMjYiOjIsImI3YmY4ZWMyODM2ODMyMTA1NTdmZjBkNmZlYWFkMzI3IjozLCJiZmJmOGVjMjgzNjgzMjEwNTU3ZmYwZDZmZWFhZDMyNiI6NCwiZmJiZjhlYzI4MzY4MzIxMDU1N2ZmMGQ2ZmVhYWQzMjUiOiJpdCBpcyBnb29kIn0%3D\n*/\n```\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Fill survey or item from url/Survey or form filler widget.js",
    "content": "//Server script:\n(function () {\n  //get the data parameter with base64encoded json with field names for keys and matching values\n\tdata.encodedData = $sp.getParameter(\"data\");\n\ttry {\n    //turn the data back into a js object\n\t\tdata.decodedData = JSON.parse(GlideStringUtil.base64Decode(data.encodedData));\n\t} catch (e) {\n\t\t//bad json; do nothing\n\t\treturn;\n\t}\n})();\n\n\n//Client controller: \napi.controller = function ($scope, $rootScope) {\n\n\tvar c = this,\n\t\tg_form = $scope.page.g_form,\n\t\tanswerMap = c.data.decodedData;\n  // return if the answermap is not a valid object\n\tif (!answerMap instanceof Object)\n\t\treturn;\n  // if we have g_form set values from the data parameter to form\n\tif (g_form) {\n\t\tfillAnswers();\n\t}\n  //loops through the object with field name keys and corresponding values and sets values\n\tfunction fillAnswers() {\n\t\tfor (key in answerMap) {\n\t\t\tif (!answerMap.hasOwnProperty(key) && !g_form.hasField(key))\n\t\t\t\tcontinue;\n\t\t\tg_form.setValue(key, answerMap[key]);\n\t\t}\n\t}\n  //get gform from the spmodel\n\t$rootScope.$on('spModel.gForm.initialized', function (e, gFormInstance) {\n\t\tg_form = gFormInstance;\n\t\tfillAnswers();\n\t});\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Floater Feedback Widget/README.md",
    "content": "## Floater Feedback Widget\n\nThe attached widget can be used to have a floating link widget on the portal homepage when clicks opens a specific catalog item or a record producer. \nThis can be used to gather feedback using a catalog item or create incidents from the portal.\n\nSimply add this widget to the portal homepage anywhere and you can find this present on the side of the homepage. Refer screenshot.\n\n![A test image](demo.JPG)\n\n\n*Note: You need to update the sys_id of your catalog item. Refer HTML in the .js widget file.*\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Floater Feedback Widget/feeback_floater.js",
    "content": "[HTML]\n\n<button id=\"popup\" class=\"feedback-button\" onclick=\"window.location.href = '?id=sc_cat_item&sys_id=ADD YOUR CATALOG ITEM SYS ID HERE';\">Feedback</button>\n\n[Server Script]\n\n(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n\n})();\n\n[Client Script]\n\napi.controller=function() {\n  /* widget controller */\n  var c = this;\n};\n\n[CSS]\n\n.feedback-button {\n  height: 50px;\n  border: none;\n  background: #80B6A1;\n  width: 100px;\n  line-height: 32px;\n  -webkit-transform: rotate(-90deg);\n  color: white;\n  transform: rotate(-90deg);\n  -ms-transform: rotate(-90deg);\n  -moz-transform: rotate(-90deg);\n  text-align: center;\n  font-size: 16px;\n  position: fixed;\n  right: -8px;\n  top: 45%;\n  font-family: \"Segoe UI\", Frutiger, \"Frutiger Linotype\", \"Dejavu Sans\", \"Helvetica Neue\", Arial, sans-serif;\n  z-index: 999;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Generate QrCode/Dependencies/qrcode.js",
    "content": "//install the code from this repository https://github.com/davidshimjs/qrcodejs/blob/master/qrcode.min.js"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Generate QrCode/README.md",
    "content": "# Generate QRCode on Service Portal\nThis example widget is a quick demonstration of what can be create a QRCode on portal\n\n# Steps to add external dependencies to widget\n\n1 - Go to widget > Dependencies\n\n2 - Click New\n\n3 - I named mine qrcode for both \"name\" and \"angular module name\"\n\n4 - Click Save\n\n5 - Click new under JS Includes\n\n6 - Select URL for Source\n\n7 - Input https://github.com/davidshimjs/qrcodejs/blob/master/qrcode.min.js\n\n8 - Click Submit\n\n9 - So now the external dependency will be added to your component\n\n10 - Now to use the library you can use the code in your widget's client script\n\n   - basic usage \n\n    ```javascript\n    <div id=\"qrcode\"></div>\n    <script type=\"text/javascript\">\n        new QRCode(document.getElementById(\"qrcode\"), \"http://jindo.dev.naver.com/collie\");\n    </script> \n    ```\n\n   - or with some options\n\n    ```javascript \n    var qrcode = new QRCode(\"test\", {\n        text: \"http://jindo.dev.naver.com/collie\",\n        width: 128,\n        height: 128,\n        colorDark : \"#000000\",\n        colorLight : \"#ffffff\",\n        correctLevel : QRCode.CorrectLevel.H\n    }); \n    ```\n\n    - and you can use some methods\n    ```javascript \n    qrcode.clear(); // clear the code.\n    qrcode.makeCode(\"http://naver.com\"); // make another code.  \n    ```\n# Oficial qrcodejs docs and lib https://www.npmjs.com/package/qrcodejs"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Generate QrCode/qrcode.html",
    "content": "<div id=\"qrcode\"></div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Generate QrCode/qrcode_client.js",
    "content": "api.controller=function() {\n    /* widget controller */\n    var c = this;\n      var contentToRender = \"Ryan\";\n      var qrCodeInstance = new QRCode(document.getElementById(\"qrcode\"), contentToRender);\n  };"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Guest Login Modal/README.md",
    "content": "# Guest Login Modal Widget\n\nThis widget provides a modal dialog prompting guest users to either login or continue browsing as a guest.  This is intended to be used on public facing Service Portal pages, and as such the widget should also be made public.\n\n## Example of Widget\n\n![Guest Login Modal Widget Example](./guest_login_modal_example.png)\n\n## How it Works\n\nThe widget works by checking if a user is logged in or not when accessing the page it is placed on.  If the user is not logged in, the modal will prompt the user to either login or continue as a guest.\n\nWhen the user clicks **Continue as Guest**, a browser session storage token is created to track that the guest has agreed to stay a guest, so they will not be prompted again during the session.\n\n## Configurable Instance Options\n\nWhile there is certainly room to expand upon instance configuration options, the following are already setup for this widget:\n\n1. Header Image - set to any image name (from db_images)\n2. Login Button Text\n3. Login Help Text\n4. Guest Button Text\n5. Guest Help Text\n\n## Setup the widget\n\n> The entire widget and a demo page has been provided in an update set, `Service Portal - Guest Login Modal Widget.xml`, but the follow the steps below to set up the widget from scratch\n\n1. Create a new widget and set **Public** to **True (checked)**\n2. Copy the contents of `server.js` to the **Server script** window\n3. Copy the contents of `client.js` to the **Client controller** window\n4. Copy the contents of `optionSchema.json` to the **Option schema** window\n5. Create a new **Angular ng-template** for the widget with **ID** set to `guest-login-content`, and then copy the contents of `ng-template.html` to the **Template** window.\n6. Drag the newly created widget onto a **Public** Service Portal page."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Guest Login Modal/Service Portal - Guest Login Modal Widget.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><unload unload_date=\"2021-10-09 23:22:38\">\n<sys_remote_update_set action=\"INSERT_OR_UPDATE\">\n<application display_value=\"Global\">global</application>\n<application_name>Global</application_name>\n<application_scope>global</application_scope>\n<application_version/>\n<collisions/>\n<commit_date/>\n<deleted/>\n<description/>\n<inserted/>\n<name>Service Portal - Guest Login Modal Widget</name>\n<origin_sys_id/>\n<parent display_value=\"\"/>\n<release_date/>\n<remote_base_update_set display_value=\"\"/>\n<remote_parent_id/>\n<remote_sys_id>43e1c8aa1b8fb010c297c803604bcba4</remote_sys_id>\n<state>loaded</state>\n<summary/>\n<sys_class_name>sys_remote_update_set</sys_class_name>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>a67f54e61b03f010c297c803604bcb25</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<update_set display_value=\"\"/>\n<update_source display_value=\"\"/>\n<updated/>\n</sys_remote_update_set>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"guest-login-modal-widget\">06bc2eeb1bf83010c297c803604bcb23</application>\n<category>customer</category>\n<comments/>\n<name>sp_widget_49eceae71b3c3010c297c803604bcb8b</name>\n<payload>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;&lt;record_update table=\"sp_widget\"&gt;&lt;sp_widget action=\"INSERT_OR_UPDATE\"&gt;&lt;category&gt;custom&lt;/category&gt;&lt;client_script&gt;&lt;![CDATA[api.controller=function($scope, $uibModal) {\n  /* widget controller */\n  var c = this;\n\tc.authenticated = c.data.authenticated;\n\tc.continueAsGuest = sessionStorage.continueAsGuest;\n\t\n\tc.login_button_text = c.data.login_button_text;\n\t\n\tif (!c.authenticated) {\n\t\tif (!c.continueAsGuest) { // if user hasn't already clicked Continue as Guest\n\t\t\tc.modalInstance = $uibModal.open({\n\t\t\t\ttemplateUrl: 'guest-login-content',\n\t\t\t\tscope: $scope,\n\t\t\t\tbackdrop: 'static',\n\t\t\t\twindowTopClass: 'modal-center-override',\n\t\t\t\tariaLabelledBy: 'title'\n\t\t\t});\n\t\t}\n\t}\n\t\n\t$scope.openLogin = function () {\n\t\twindow.location = '/sp';\n\t};\n\t\n\t// When Guest button is clicked, store in sessionStorage and close modal\n\t$scope.continueAsGuest = function () {\n\t\tsessionStorage.continueAsGuest = true;\n\t\tc.modalInstance.close();\n\t}\n};]]&gt;&lt;/client_script&gt;&lt;controller_as&gt;c&lt;/controller_as&gt;&lt;css/&gt;&lt;data_table&gt;sp_instance&lt;/data_table&gt;&lt;demo_data/&gt;&lt;description/&gt;&lt;docs/&gt;&lt;field_list/&gt;&lt;has_preview&gt;true&lt;/has_preview&gt;&lt;id&gt;guest-login-modal&lt;/id&gt;&lt;internal&gt;false&lt;/internal&gt;&lt;link&gt;&lt;![CDATA[function link(scope, element, attrs, controller) {  }]]&gt;&lt;/link&gt;&lt;name&gt;Guest Login Modal&lt;/name&gt;&lt;option_schema&gt;[{\"name\":\"login_button_text\",\"section\":\"Presentation\",\"default_value\":\"Log in\",\"label\":\"Login Button Text\",\"type\":\"string\"},{\"name\":\"login_button_help\",\"section\":\"Presentation\",\"default_value\":\"to get help or view additional search results\",\"label\":\"Login Button Help\",\"type\":\"string\"},{\"name\":\"guest_button_text\",\"section\":\"Presentation\",\"default_value\":\"Continue as Guest\",\"label\":\"Guest Button Text\",\"type\":\"string\"},{\"name\":\"guest_button_help\",\"section\":\"Presentation\",\"default_value\":\"to get help or view public information\",\"label\":\"Guest Button Help\",\"type\":\"string\"},{\"displayValue\":\"Images\",\"name\":\"header_image\",\"section\":\"Presentation\",\"default_value\":\"la_jolla_sp_logo.png\",\"label\":\"Header Image\",\"type\":\"string\",\"value\":\"db_image\",\"ed\":{\"reference\":\"db_image\"}}]&lt;/option_schema&gt;&lt;public&gt;true&lt;/public&gt;&lt;roles/&gt;&lt;script&gt;&lt;![CDATA[(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n\n\tdata.authenticated = gs.isLoggedIn();\t\t\n\t\n})();]]&gt;&lt;/script&gt;&lt;servicenow&gt;false&lt;/servicenow&gt;&lt;sys_class_name&gt;sp_widget&lt;/sys_class_name&gt;&lt;sys_created_by&gt;admin&lt;/sys_created_by&gt;&lt;sys_created_on&gt;2021-06-23 15:53:07&lt;/sys_created_on&gt;&lt;sys_id&gt;49eceae71b3c3010c297c803604bcb8b&lt;/sys_id&gt;&lt;sys_mod_count&gt;54&lt;/sys_mod_count&gt;&lt;sys_name&gt;Guest Login Modal&lt;/sys_name&gt;&lt;sys_package display_value=\"guest-login-modal-widget\" source=\"06bc2eeb1bf83010c297c803604bcb23\"&gt;06bc2eeb1bf83010c297c803604bcb23&lt;/sys_package&gt;&lt;sys_policy/&gt;&lt;sys_scope display_value=\"guest-login-modal-widget\"&gt;06bc2eeb1bf83010c297c803604bcb23&lt;/sys_scope&gt;&lt;sys_update_name&gt;sp_widget_49eceae71b3c3010c297c803604bcb8b&lt;/sys_update_name&gt;&lt;sys_updated_by&gt;kcuster&lt;/sys_updated_by&gt;&lt;sys_updated_on&gt;2021-10-09 23:14:49&lt;/sys_updated_on&gt;&lt;template/&gt;&lt;/sp_widget&gt;&lt;/record_update&gt;</payload>\n<payload_hash>-418821739</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>227f54e61b03f010c297c803604bcb27</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c67566b120000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name>Guest Login Modal</target_name>\n<type>Widget</type>\n<update_domain>global</update_domain>\n<update_guid>40bd5c26c103f010f009da7b1e05d80a</update_guid>\n<update_guid_history>40bd5c26c103f010f009da7b1e05d80a:-418821739,317d1c268f03f010787a429cd97fca53:998559497,bbfc54266203f01054380df297d1a053:-1279583543,21fc1c26e203f010ab7537680063d826:465368328,fa6cd0269803f01071c8ea2b615ceaee:998559497,ad1b18a27003f010dee02f7082fb16b5:686360518,562a1462bc03f010f7200ca3d4dcfb97:-625357242,cbc9d4627403f010da674ab8e6f7ba67:1954228023,af8994e297cfb010bdce21520f0a8191:322257018,747814ae6ccfb0106b8ad7457573611e:-1894000184,862610ae6bcfb01054fdb74bb4701697:-1027219377,37ba80e286cfb010ec6dd8ef5af707d0:1066564096,caba04e230cfb01008867e6318b0fc3d:1004249312,acba08e273cfb010f74c5feb5c228991:1617901295,978a84e2a2cfb01062cb476cf152aa4d:-900460353,847a80e258cfb010e7d68d58a312ecb8:1918644266,4d4a40e2d0cfb01027f45c597e9a4c4a:-1172717228,1b3a88a287cfb0102696a449558bcff1:1098489625,c93acca2b7cfb01061e7014c35285775:-2026151676,b72a0ca26dcfb010299460393802360e:2078592015,ee2a0ca2afcfb010302845586979ba29:-1032709077,021a8ca219cfb0107933daa34a36993b:-1092633476,841ac8a232cfb010114c421deb73b648:698668295,48f900a254cfb0106aae43d6c6758ff3:-1092633476,56e908a2e3cfb010e966e874ca6c4e5c:1215046510,8c794c62e7cfb01019392b7f9f22d0d7:1250997445,3b590c62accfb01001bf54d4eaa42e92:157382334,a8490862efcfb0101c2117a996be8f73:1432307614,cc294c62e2cfb0100c5ccaf4d1150bce:154234622,eb15c42ef98fb010dee8bab3da8f230d:-1854731309,d9a3082efe8fb01045edc9f5780fbe80:1157193150,b183ccea548fb01083b60a447e3ba803:472434505,0e2308eac48fb01062032875b51e42d3:197517919,e123c8ea538fb01064c2e1e7ba59524e:435993900,9c23ccea4e8fb0108f35d03d9d12fc49:28470063,c71380eaca8fb0101d781afdb0ee28dc:-2096681003,0213c8ea2c8fb010b4cb0c7cd0193ae2:-629834859,df03cceaf38fb01080f4dd46a400ae35:435993900,12038cea068fb010baf5a90e8686a0e0:197517919,17a2c0ea468fb010d9bed2b9f8c83c47:472434505,7c8204ea778fb01041dd97d7e2d1e9fb:430374002,75720ce2348fb0104e571c515855c138:97298626,d282f60250cb70100887f382587992e7:590171217,0522bace4a8b70100fb56fb2b1bfe4ed:289045908,ba2209d2de01301049643c228ddc4612:-1313782481,0af145d2f201301011119c90abe1051f:-480316511,98f1c1d22301301098747ad7d5f954af:-530790403,5ae105d2b90130101f1d09a70bd0ac78:-88992413,cde1c9921c013010203f6f105d16a842:1136953365,8bd1c1d2850130104d505c58503c68a7:1647053305,e4c1c9924b0130102e9cedefc86f4a78:203533984,c1683beba0fc3010b9719961625092c2:-207446025,21583bebd5fc3010f811b75880a85068:-1449051241,a6c7bfab4dfc3010aeca6673508a4871:57912099,41eceae7b13c3010dfe2bbfdea54b48f:-2132406035</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_instance_c905486e1b8fb010c297c803604bcbfd</name>\n<payload><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\"?><record_update table=\"sp_instance\"><sp_instance action=\"INSERT_OR_UPDATE\"><active>true</active><class_name/><color>default</color><css/><glyph/><id/><order>1</order><roles/><short_description/><size>md</size><sp_column display_value=\"1\">fbf4c82e1b8fb010c297c803604bcb71</sp_column><sp_widget display_value=\"Guest Login Modal\">49eceae71b3c3010c297c803604bcb8b</sp_widget><sys_class_name>sp_instance</sys_class_name><sys_created_by>kcuster</sys_created_by><sys_created_on>2021-10-09 21:27:01</sys_created_on><sys_id>c905486e1b8fb010c297c803604bcbfd</sys_id><sys_mod_count>0</sys_mod_count><sys_name/><sys_package display_value=\"Global\" source=\"global\">global</sys_package><sys_policy/><sys_scope display_value=\"Global\">global</sys_scope><sys_update_name>sp_instance_c905486e1b8fb010c297c803604bcbfd</sys_update_name><sys_updated_by>kcuster</sys_updated_by><sys_updated_on>2021-10-09 21:27:01</sys_updated_on><title/><url/><widget_parameters/></sp_instance><sys_translated_text action=\"delete_multiple\" query=\"documentkey=c905486e1b8fb010c297c803604bcbfd\"/></record_update>]]></payload>\n<payload_hash>-169321837</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>267f54e61b03f010c297c803604bcb26</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c66f3bb720000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name/>\n<type>Instance</type>\n<update_domain>global</update_domain>\n<update_guid>dd05486e5b8fb010a0159f2c06077bff</update_guid>\n<update_guid_history>dd05486e5b8fb010a0159f2c06077bff:-169321837</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_container_43f44c6e1b8fb010c297c803604bcb0d</name>\n<payload><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\"?><record_update table=\"sp_container\"><sp_container action=\"INSERT_OR_UPDATE\"><background_color/><background_image/><background_style>default</background_style><bootstrap_alt>false</bootstrap_alt><class_name/><container_class_name/><name>Guest Login Modal Demo - Container 1</name><order>1</order><sp_page display_value=\"guest_login_demo\">adc4886e1b8fb010c297c803604bcb45</sp_page><subheader>false</subheader><sys_class_name>sp_container</sys_class_name><sys_created_by>kcuster</sys_created_by><sys_created_on>2021-10-09 21:26:53</sys_created_on><sys_id>43f44c6e1b8fb010c297c803604bcb0d</sys_id><sys_mod_count>2</sys_mod_count><sys_name>Guest Login Modal Demo - Container 1</sys_name><sys_package display_value=\"Global\" source=\"global\">global</sys_package><sys_policy/><sys_scope display_value=\"Global\">global</sys_scope><sys_update_name>sp_container_43f44c6e1b8fb010c297c803604bcb0d</sys_update_name><sys_updated_by>kcuster</sys_updated_by><sys_updated_on>2021-10-09 23:18:49</sys_updated_on><title/><width>container</width></sp_container></record_update>]]></payload>\n<payload_hash>527004357</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>627f54e61b03f010c297c803604bcb26</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c675a16de0000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name>Guest Login Modal Demo - Container 1</target_name>\n<type>Container</type>\n<update_domain>global</update_domain>\n<update_guid>3e9e10a62103f0109dfab9f19866c3d0</update_guid>\n<update_guid_history>3e9e10a62103f0109dfab9f19866c3d0:527004357,237e9c667303f010f4361f5a0baf95eb:549138287,03f44c6edb8fb010bdf4e98d5b85d40f:527004357</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_row_77f4c82e1b8fb010c297c803604bcb6f</name>\n<payload><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\"?><record_update table=\"sp_row\"><sp_row action=\"INSERT_OR_UPDATE\"><class_name/><order>1</order><sp_column/><sp_container display_value=\"Guest Login Modal Demo - Container 1\">43f44c6e1b8fb010c297c803604bcb0d</sp_container><sys_class_name>sp_row</sys_class_name><sys_created_by>kcuster</sys_created_by><sys_created_on>2021-10-09 21:26:55</sys_created_on><sys_id>77f4c82e1b8fb010c297c803604bcb6f</sys_id><sys_mod_count>0</sys_mod_count><sys_name>1</sys_name><sys_package display_value=\"Global\" source=\"global\">global</sys_package><sys_policy/><sys_scope display_value=\"Global\">global</sys_scope><sys_update_name>sp_row_77f4c82e1b8fb010c297c803604bcb6f</sys_update_name><sys_updated_by>kcuster</sys_updated_by><sys_updated_on>2021-10-09 21:26:55</sys_updated_on></sp_row></record_update>]]></payload>\n<payload_hash>-1430834805</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>6e7f54e61b03f010c297c803604bcb26</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c66f3a4cc0000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name>1</target_name>\n<type>Row</type>\n<update_domain>global</update_domain>\n<update_guid>bff4c82e378fb010d7909f555a8c1f70</update_guid>\n<update_guid_history>bff4c82e378fb010d7909f555a8c1f70:-1430834805</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_column_fbf4c82e1b8fb010c297c803604bcb71</name>\n<payload><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\"?><record_update table=\"sp_column\"><sp_column action=\"INSERT_OR_UPDATE\"><class_name/><order>1</order><size>12</size><size_lg/><size_sm/><size_xs/><sp_row display_value=\"1\">77f4c82e1b8fb010c297c803604bcb6f</sp_row><sys_class_name>sp_column</sys_class_name><sys_created_by>kcuster</sys_created_by><sys_created_on>2021-10-09 21:26:55</sys_created_on><sys_id>fbf4c82e1b8fb010c297c803604bcb71</sys_id><sys_mod_count>0</sys_mod_count><sys_name>1</sys_name><sys_package display_value=\"Global\" source=\"global\">global</sys_package><sys_policy/><sys_scope display_value=\"Global\">global</sys_scope><sys_update_name>sp_column_fbf4c82e1b8fb010c297c803604bcb71</sys_update_name><sys_updated_by>kcuster</sys_updated_by><sys_updated_on>2021-10-09 21:26:55</sys_updated_on></sp_column></record_update>]]></payload>\n<payload_hash>1390422863</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>aa7f54e61b03f010c297c803604bcb25</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c66f3a4f50000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name>1</target_name>\n<type>Column</type>\n<update_domain>global</update_domain>\n<update_guid>f3f4c82e6a8fb010d0ea998d34ba1d73</update_guid>\n<update_guid_history>f3f4c82e6a8fb010d0ea998d34ba1d73:1390422863</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_page_adc4886e1b8fb010c297c803604bcb45</name>\n<payload><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\"?><record_update table=\"sp_page\"><sp_page action=\"INSERT_OR_UPDATE\"><category>custom</category><css/><draft>false</draft><dynamic_title_structure/><id>guest_login_demo</id><internal>false</internal><omit_watcher>false</omit_watcher><public>true</public><roles/><seo_script/><short_description/><sys_class_name>sp_page</sys_class_name><sys_created_by>kcuster</sys_created_by><sys_created_on>2021-10-09 21:26:37</sys_created_on><sys_id>adc4886e1b8fb010c297c803604bcb45</sys_id><sys_mod_count>0</sys_mod_count><sys_name>guest_login_demo</sys_name><sys_package display_value=\"Global\" source=\"global\">global</sys_package><sys_policy/><sys_scope display_value=\"Global\">global</sys_scope><sys_update_name>sp_page_adc4886e1b8fb010c297c803604bcb45</sys_update_name><sys_updated_by>kcuster</sys_updated_by><sys_updated_on>2021-10-09 21:26:37</sys_updated_on><title>Guest Login Modal Demo</title><use_seo_script>false</use_seo_script></sp_page><sys_translated_text action=\"delete_multiple\" query=\"documentkey=adc4886e1b8fb010c297c803604bcb45\"/></record_update>]]></payload>\n<payload_hash>-1261177682</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>aa7f54e61b03f010c297c803604bcb26</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c66f35bfe0000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name>guest_login_demo</target_name>\n<type>Page</type>\n<update_domain>global</update_domain>\n<update_guid>dfe4046e9f8fb01088d28c87c430774f</update_guid>\n<update_guid_history>dfe4046e9f8fb01088d28c87c430774f:-1261177682</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_ng_template_8f4acca21bcfb010c297c803604bcbde</name>\n<payload>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;&lt;record_update table=\"sp_ng_template\"&gt;&lt;sp_ng_template action=\"INSERT_OR_UPDATE\"&gt;&lt;id&gt;guest-login-content&lt;/id&gt;&lt;sp_widget display_value=\"Guest Login Modal\"&gt;49eceae71b3c3010c297c803604bcb8b&lt;/sp_widget&gt;&lt;sys_class_name&gt;sp_ng_template&lt;/sys_class_name&gt;&lt;sys_created_by&gt;kcuster&lt;/sys_created_by&gt;&lt;sys_created_on&gt;2021-10-09 21:50:19&lt;/sys_created_on&gt;&lt;sys_id&gt;8f4acca21bcfb010c297c803604bcbde&lt;/sys_id&gt;&lt;sys_mod_count&gt;17&lt;/sys_mod_count&gt;&lt;sys_name&gt;guest-login-content&lt;/sys_name&gt;&lt;sys_package display_value=\"Global\" source=\"global\"&gt;global&lt;/sys_package&gt;&lt;sys_policy/&gt;&lt;sys_scope display_value=\"Global\"&gt;global&lt;/sys_scope&gt;&lt;sys_update_name&gt;sp_ng_template_8f4acca21bcfb010c297c803604bcbde&lt;/sys_update_name&gt;&lt;sys_updated_by&gt;kcuster&lt;/sys_updated_by&gt;&lt;sys_updated_on&gt;2021-10-09 23:15:55&lt;/sys_updated_on&gt;&lt;template&gt;&lt;![CDATA[&lt;style lang=\"scss\"&gt;\n  .modal-center-override {\n    top: 30% !important;\n    transform: translateY(-10%) !important;\n    -webkit-transform: translateY(-10%) !important;\n    -moz-transform: translateY(-10%) !important;\n    -o-transform: translateY(-10%) !important; \n  }\n  \n  .modal-center-override:focus {\n    outline: none;\n    border: none !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n\n  .modal-content {\n    border-radius: 8px;\n  }\n\n  .modal-body-gradient {\n    background: linear-gradient(0deg, rgba(219,219,219,1) 0%, rgba(233,233,233,1) 24%, rgba(255,255,255,1) 100%);\n  }\n\n  .modal-header {\n    padding-top: 3rem;\n    padding-bottom: 3rem;\n    border-top-left-radius: 4px;\n    border-top-right-radius: 4px;\n  }\n\n  .modal-header-img {\n    max-width: 60%;\n    height: auto;\n  }\n\n  .modal-body {\n    padding-top: 3rem;\n    padding-bottom: 3rem;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n  }\n\n  .btn-login {\n    font-weight: 500;\n    font-size: 1.4em;\n    /* width: 60%; */\n  }\n\n  .btn-guest {\n    background-color: #d4d4d4 !important;\n    color: #252525 !important;\n    font-weight: 500;\n    font-size: 1.1em;\n    /* width: 40%; */\n  }\n\n  .help-text {\n    margin-top: 0.5rem;\n    color: #7c7c7c;\n    font-weight: 400;\n  }\n  \n  .login-section {\n    margin-top: 5rem;\n    margin-bottom: 5rem;\n  }\n\n  .guest-section {\n    margin-bottom: 5rem;\n  }\n\n@media (max-width: 688px) {\n  .login-section {\n    margin-top: 1rem;\n    margin-bottom: 3rem;\n  }\n\n  .guest-section {\n    margin-bottom: 1rem;\n  }\n\n  .btn-login {\n    font-size: 1.0em;\n  }\n\n  .btn-guest {\n    font-size: 0.9em;\n  }\n\n  .help-text {\n    font-size: 0.8em;\n  }\n}  \n&lt;/style&gt;\n\n&lt;div class=\"modal-header bg-primary\"&gt;\n  &lt;div class=\"text-center\"&gt;\n    &lt;img class=\"modal-header-img\" ng-src=\"{{c.options.header_image}}\" /&gt;\n  &lt;/div&gt;\n&lt;/div&gt;\n&lt;div class=\"modal-body modal-body-gradient text-center\"&gt;\n  &lt;div class=\"login-section\"&gt;\n    &lt;a class=\"button btn btn-lg btn-primary\" href ng-click=\"openLogin()\" role=\"button\"&gt;\n      {{ c.options.login_button_text }}\n    &lt;/a&gt;\n    &lt;div class=\"help-text\"&gt;\n      {{ c.options.login_button_help }}\n    &lt;/div&gt;\n  &lt;/div&gt;\n  &lt;div class=\"guest-section\"&gt;\n    &lt;a class=\"button btn btn-guest\" href ng-click=\"continueAsGuest()\" role=\"button\"&gt;\n      {{ c.options.guest_button_text }}\n    &lt;/a&gt;\n    &lt;div class=\"help-text\"&gt;\n      {{ c.options.guest_button_help }}\n    &lt;/div&gt;\n  &lt;/div&gt;\n&lt;/div&gt;]]&gt;&lt;/template&gt;&lt;/sp_ng_template&gt;&lt;/record_update&gt;</payload>\n<payload_hash>-1553290653</payload_hash>\n<remote_update_set display_value=\"Service Portal - Guest Login Modal Widget\">a67f54e61b03f010c297c803604bcb25</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>kcuster</sys_created_by>\n<sys_created_on>2021-10-09 23:22:37</sys_created_on>\n<sys_id>e67f54e61b03f010c297c803604bcb26</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c67576cb20000001</sys_recorded_at>\n<sys_updated_by>kcuster</sys_updated_by>\n<sys_updated_on>2021-10-09 23:22:37</sys_updated_on>\n<table/>\n<target_name>guest-login-content</target_name>\n<type>Angular ng-template</type>\n<update_domain>global</update_domain>\n<update_guid>dcfd14663803f01024a3a4c1223203f2</update_guid>\n<update_guid_history>dcfd14663803f01024a3a4c1223203f2:-1553290653,b57d14661203f010849b68dc6a4670a4:775978037,db1d1c269603f0104d3e735b8354a5c8:-1739668799,366cd0266303f010012ca4e38dddfef1:775978037,162c14e28c03f010e898cd0211c1f3ab:-107268809,041c54e23803f0101bc7346d01a6a997:-1944192855,52eb54e2c003f01007314c31e8f5cb93:942845174,c9aa90a2bf03f01053913aa3e95a7824:775978037,966a1c628703f010a10baf6a8919f95d:418484917,3f5a9462ad03f010611b9ad55d891723:1261375882,be4a1c626703f0101f2bb26ce2783d58:-1520469770,383a1c62b203f01097d73f4e4bd38215:-1465528543,f7f9d8624c03f010c0c4acd7f88e02cd:-1954980333,a1d994625a03f0101ce8c8fea6ec4619:-1374082360,bca9dca2e0cfb010131b407bcfb58662:-1465528543,8a2910ae12cfb010322b51dfefa7fda9:-601852701,456614ae37cfb010a5a3ac33ae380290:-1573024778,1e5a80e28bcfb01052ee063b25b167b1:1427195917</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n</unload>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Guest Login Modal/client.js",
    "content": "api.controller=function($scope, $uibModal) {\n  /* widget controller */\n  var c = this;\n\tc.authenticated = c.data.authenticated;\n\tc.continueAsGuest = sessionStorage.continueAsGuest;\n\t\n\tc.login_button_text = c.data.login_button_text;\n\t\n\tif (!c.authenticated) {\n\t\tif (!c.continueAsGuest) { // if user hasn't already clicked Continue as Guest\n\t\t\tc.modalInstance = $uibModal.open({\n\t\t\t\ttemplateUrl: 'guest-login-content',\n\t\t\t\tscope: $scope,\n\t\t\t\tbackdrop: 'static',\n\t\t\t\twindowTopClass: 'modal-center-override',\n\t\t\t\tariaLabelledBy: 'title'\n\t\t\t});\n\t\t}\n\t}\n\t\n\t$scope.openLogin = function () {\n\t\twindow.location = '/sp';\n\t};\n\t\n\t// When Guest button is clicked, store in sessionStorage and close modal\n\t$scope.continueAsGuest = function () {\n\t\tsessionStorage.continueAsGuest = true;\n\t\tc.modalInstance.close();\n\t}\n};"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Guest Login Modal/ng-template.html",
    "content": "<!-- Save this as an ng-template named \"guest-login-content\" and associate it to the widget -->\n\n<style lang=\"scss\">\n  .modal-center-override {\n    top: 30% !important;\n    transform: translateY(-10%) !important;\n    -webkit-transform: translateY(-10%) !important;\n    -moz-transform: translateY(-10%) !important;\n    -o-transform: translateY(-10%) !important; \n  }\n  \n  .modal-center-override:focus {\n    outline: none;\n    border: none !important;\n    -webkit-box-shadow: none !important;\n    box-shadow: none !important;\n  }\n\n  .modal-content {\n    border-radius: 8px;\n  }\n\n  .modal-body-gradient {\n    background: linear-gradient(0deg, rgba(219,219,219,1) 0%, rgba(233,233,233,1) 24%, rgba(255,255,255,1) 100%);\n  }\n\n  .modal-header {\n    padding-top: 3rem;\n    padding-bottom: 3rem;\n    border-top-left-radius: 4px;\n    border-top-right-radius: 4px;\n  }\n\n  .modal-header-img {\n    max-width: 60%;\n    height: auto;\n  }\n\n  .modal-body {\n    padding-top: 3rem;\n    padding-bottom: 3rem;\n    border-bottom-left-radius: 4px;\n    border-bottom-right-radius: 4px;\n  }\n\n  .btn-login {\n    font-weight: 500;\n    font-size: 1.4em;\n    /* width: 60%; */\n  }\n\n  .btn-guest {\n    background-color: #d4d4d4 !important;\n    color: #252525 !important;\n    font-weight: 500;\n    font-size: 1.1em;\n    /* width: 40%; */\n  }\n\n  .help-text {\n    margin-top: 0.5rem;\n    color: #7c7c7c;\n    font-weight: 400;\n  }\n  \n  .login-section {\n    margin-top: 5rem;\n    margin-bottom: 5rem;\n  }\n\n  .guest-section {\n    margin-bottom: 5rem;\n  }\n\n@media (max-width: 688px) {\n  .login-section {\n    margin-top: 1rem;\n    margin-bottom: 3rem;\n  }\n\n  .guest-section {\n    margin-bottom: 1rem;\n  }\n\n  .btn-login {\n    font-size: 1.0em;\n  }\n\n  .btn-guest {\n    font-size: 0.9em;\n  }\n\n  .help-text {\n    font-size: 0.8em;\n  }\n}  \n</style>\n\n<div class=\"modal-header bg-primary\">\n  <div class=\"text-center\">\n    <img class=\"modal-header-img\" ng-src=\"{{c.options.header_image}}\" />\n  </div>\n</div>\n<div class=\"modal-body modal-body-gradient text-center\">\n  <div class=\"login-section\">\n    <a class=\"button btn btn-lg btn-primary\" href ng-click=\"openLogin()\" role=\"button\">\n      {{ c.options.login_button_text }}\n    </a>\n    <div class=\"help-text\">\n      {{ c.options.login_button_help }}\n    </div>\n  </div>\n  <div class=\"guest-section\">\n    <a class=\"button btn btn-guest\" href ng-click=\"continueAsGuest()\" role=\"button\">\n      {{ c.options.guest_button_text }}\n    </a>\n    <div class=\"help-text\">\n      {{ c.options.guest_button_help }}\n    </div>\n  </div>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Guest Login Modal/optionSchema.json",
    "content": "[{\"name\":\"login_button_text\",\"section\":\"Presentation\",\"default_value\":\"Log in\",\"label\":\"Login Button Text\",\"type\":\"string\"},{\"name\":\"login_button_help\",\"section\":\"Presentation\",\"default_value\":\"to get help or view additional search results\",\"label\":\"Login Button Help\",\"type\":\"string\"},{\"name\":\"guest_button_text\",\"section\":\"Presentation\",\"default_value\":\"Continue as Guest\",\"label\":\"Guest Button Text\",\"type\":\"string\"},{\"name\":\"guest_button_help\",\"section\":\"Presentation\",\"default_value\":\"to get help or view public information\",\"label\":\"Guest Button Help\",\"type\":\"string\"},{\"displayValue\":\"Images\",\"name\":\"header_image\",\"section\":\"Presentation\",\"default_value\":\"la_jolla_sp_logo.png\",\"label\":\"Header Image\",\"type\":\"string\",\"value\":\"db_image\",\"ed\":{\"reference\":\"db_image\"}}]"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Guest Login Modal/server.js",
    "content": "(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n\n\tdata.authenticated = gs.isLoggedIn();\t\t\n\t\n})();"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HR Task Progress Bar/CSS.js",
    "content": "/*Parent container using flex to adjust width automatically*/\n.parent {\n  display: flex;\n  justify-content: space-evenly;  \n  background: cornflowerblue;\n}\n/*Text (HR task) will be shown in Red colo and green background*/\n.child{\n  color:#FF0000;\n  width:100%;\n  background: lightgreen;\n}\n/*single color when task is not in WIP*/\n.child_1{\n  width:100%;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HR Task Progress Bar/HTML.js",
    "content": "<!-- HTML to show progress bar on HRM Page -->\n<div class=\"parent\">\n  <div ng-class=\"{{tasks.state}}==18||data.state==18 ? 'child':'child_1'\" ng-repeat=\"tasks in data.taskArr\">\n   {{tasks.number}}\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HR Task Progress Bar/README.md",
    "content": "**Steps to add widget to page**\n1. Open \"hrm_ticket_page\" portal page.\n2. Create a widget with HTML, CSS, Client, Server code as per this document.\n3. Add the widget to top of \"hrm_ticket_page\" page.\n\n**Output** \n1.  If the HR case has associated tasks, those tasks will be shown as progress bar.\n2.  WIP tasks will be shown with green background and red text.\n3.  All other state tasks will be shown in black text and blue background.\n\n   \n<img width=\"2144\" height=\"1320\" alt=\"image\" src=\"https://github.com/user-attachments/assets/aaa00792-0f35-4f98-a46b-a60ef7270c33\" />\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HR Task Progress Bar/Server.js",
    "content": "(function() {\n    data.state = '';\n    data.taskArr = []; // array to return HR task fields\n    var recordId = $sp.getParameter('sys_id'); // get sys_id of HR case from URL\n    var getTask = new GlideRecord('sn_hr_core_task');\n    getTask.addEncodedQuery('parent=' + recordId); // encoded Query to get all task related to HR case\n    getTask.query();\n    while (getTask.next()) {\n        var obj = {}; // object to store HR task values as JSON\n        obj.number = getTask.getValue('number'); // add HR task number\n        obj.state = getTask.getValue('state');  // add HR task state\n        obj.sys_id = getTask.getValue('sys_id'); // add HR task sys_id\n        data.taskArr.push(obj);\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HR Task Progress Bar/client_script.js",
    "content": "api.controller = function(spUtil, $scope) {\n    /* widget controller */\n    var c = this;\n   // record watcher to show changes on progress bar dynamically\n    spUtil.recordWatch($scope, \"sn_hr_core_task\", \"active=true\", function(name) {\n        c.data.state = name.data.record.state; \n        c.server.update();\n\n    });\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HTML List Table from GlideRecord with JSon/README.md",
    "content": "A simple code snippet to get data using GlideRecord in server script and Display in HTML Table using JSON.\n\nIn order to use the widget, follow the below steps: Create a new widget and copy the html and server script in the widget.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HTML List Table from GlideRecord with JSon/ServerScript.js",
    "content": "(function() {\n\t/* populate the 'data' object */\n\t/* e.g., data.table = $sp.getValue('table'); */\n\n\n\tvar table = 'incident';\n\tvar filter = 'active=true';\n\n  /* Code to get Table and filter values from Options \n\tvar table = $sp.getValue('table');\n\tvar filter = $sp.getValue('filter'); */\n\n  /*Get Records using Glide Record and push the data using JSon */\n\tdata.inc = [];\n\tvar grInc = new GlideRecord(table);\n\tgrInc.addEncodedQuery(filter);\n\tgrInc.query();\n\twhile(grInc.next()){\n\t\tvar json = {};\n\t\tjson.number = grInc.getValue('number');\n\t\tjson.short_desc = grInc.getValue('short_description');\n\t\tjson.assign_gr = grInc.getDisplayValue('assignment_group');\n\t\tjson.state = grInc.getDisplayValue('state');\n\t\tjson.sys_id = grInc.getValue('sys_id');\n\t\tdata.inc.push(json);\n\t}\n\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/HTML List Table from GlideRecord with JSon/html.html",
    "content": "<div>\n<!-- your widget template -->\n  <h1>Simple List</h1>\n \n  <table>\n    <tr>\n      <th>Number</th>\n      <th>Short Desc</th>\n      <th>Assignment Group</th>\n      <th>State</th>\n      <th>Action</th>\n    </tr>\n    <tr ng-repeat=\"key in data.inc\">\n      <td>{{key.number}}</td>\n      <td>{{key.short_desc}}</td>\n      <td>{{key.assign_gr}}</td>\n      <td>{{key.state}}</td>\n    </tr>\n  </table>\n  \n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Image icon Menu/README.md",
    "content": "# contribution\n\nDeveloped a custom Service Portal widget named \"Image Icon Menu\" to display Quick Links.\nThis widget features icon-based navigation for key actions such as \"Rewards & Recognition\" and \"Knowledge Article Creation\". \n\n\nFunction:\n-----------\n\nThe widget displays a customizable image-based icon menu with links to various resources like \"Rewards & Recognition\" or \"Knowledge Article Creation.\" Menu items are dynamically populated based on configuration.\n\nWidget Structure:\n--------------------\n\nThe widget is composed of the following components:\n\nHTML template for structure - Dynamically renders up to 2 menu items, each showing: An image (img), A title (span), A hyperlink which Opens the target link.\n\nCSS for styling - Applies flexbox to align icons and titles, Uses brand color (#01b8f2) and bold font for titles.\n\nClient-side script for interactive behavior\n\nServer-side script for backend data handling.Populates data object with:\n\nHeader image and title.\n\nTitles, images, URLs, and targets for up to 2 menu items, based on widget options.\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Image icon Menu/Server.js",
    "content": "//Serverside code for Menu Item -\n\n\n\n(function() {\n   \n\n    var userID = gs.getUserID(); //Logged in user \n    var gr = new GlideRecord('sn_hr_core_profile');\n    gr.addQuery('user', userID);\n    gr.query();\n\n    if (gr.next()) {\n        data.saltype = gr.getValue('employee_class').toString();\n        console.log(data.saltype);\n    }\n\n    // Widget Header Stuff\n\n    data.imgImage = options.header_image;\n    data.imgTitle = options.header_title;\n\n    // Menu Item 1\n\n    data.item_1_TITLE = options.item_1_title;\n    data.item_1_IMG = options.item_1_img;\n    data.item_1_URL = options.item_1_url;\n    data.item_1_TARGET = options.item_1_target;\n\n   \n    // Menu Item 2\n\n    data.item_2_TITLE = options.item_2_title;\n    data.item_2_IMG = options.item_2_img;\n    data.item_2_URL = options.item_2_url;\n    data.item_2_TARGET = options.item_2_target;\n\n   \n   \n})();\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Image icon Menu/client.js",
    "content": "api.controller=function() {\n  /* widget controller */\n  var c = this;\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Image icon Menu/iconWidgetinstance.js",
    "content": "{\n      \"header_image\": {\n            \"value\": \"image_icon.png\",\n            \"displayValue\": \"image_icon.png\"\n      },\n      \"header_title\": {\n            \"value\": \"Quick Links\",\n            \"displayValue\": \"Quick Links\"\n      },\n     \n     \n      \"item_1_title\": {\n            \"value\": \"Request Knowledge Articles\",\n            \"displayValue\": \"Request Knowledge Articles\"\n      },\n      \"item_1_img\": {\n            \"value\": \"request_ka_icon.png\",\n            \"displayValue\": \"request_ka_icon.png\"\n      },\n      \"item_1_url\": {\n            \"value\": \"?id=sc_cat_item&sys_id=657498bf1b00c5d0fd4899f4bd4bcb1e\",\n            \"displayValue\": \"?id=sc_cat_item&sys_id=657498bf1b00c5d0fd4899f4bd4bcb1e\"\n      },\n     \n      \n      \"item_1_target\": {\n            \"value\": \"_blank\",\n            \"displayValue\": \"_blank\"\n      },\n      \"item_2_target\": {\n            \"value\": \"_blank\",\n            \"displayValue\": \"_blank\"\n      },\n     \n    \n\n      \"item_2_title\": {                                                       \n            \"value\": \"Rewards & Recognitions\",\n            \"displayValue\": \"Rewards & Recognitions\"\n      },\n      \"item_2_img\": {\n            \"value\": \"achv_icon.png\",\n            \"displayValue\": \"achv_icon.png\"\n      },\n      \"item_2_url\": {\n            \"value\": \"https://xyz.achievers.com/recent_activity\",\n            \"displayValue\": \"https://xyz.achievers.com/recent_activity\"\n     \n    },\n\n     \n     \n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Image icon Menu/iconmenu.css",
    "content": ".header-image, .image-icon-item {\n  width: 56px;\n  height: 56px;\n  object-fit: contain;\n  margin-right: 1rem;\n}\n\n.header-title {\n  font-size: 20px;\n}\n\na.list-group-item {\n  display: flex;\n  align-items: center;\n}\n\n.image-icon-title {\n  color: #01b8f2;\n  font-weight: bold;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Image icon Menu/iconmenu.html",
    "content": "<div class=\"panel b panel-default\">\n  <div class=\"panel-heading\">\n    <h4 style=\"margin-top: 0; margin-bottom: 0;\">\n      <span ng-if=\"::data.imgTitle\">\n        <img ng-src=\"{{data.imgImage}}\" class=\"header-image\" />\n      </span>\n      <span class=\"header-title\">{{data.imgTitle || \"Image Icon Menu\"}}</span>\n    </h4>\n  </div>\n  <div class=\"list-group\">\n    <a class=\"list-group-item no-border\" href=\"{{data.item_1_URL}}\" target=\"{{data.item_1_TARGET}}\">\n      <img ng-src=\"{{data.item_1_IMG}}\" class=\"image-icon-item\" />\n      <span class=\"image-icon-title\">{{data.item_1_TITLE}}</span>\n    </a>\n \n\n    <a class=\"list-group-item no-border\" href=\"{{data.item_2_URL}}\" target=\"{{data.item_2_TARGET}}\">\n      <img ng-src=\"{{data.item_2_IMG}}\" class=\"image-icon-item\" />\n      <span class=\"image-icon-title\">{{data.item_2_TITLE}}</span>\n    </a>\n\n    \n  </div>\n</div>\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/ImportXml/README.md",
    "content": "This widget can be used to import XML files into Servicenow instance.\nThis is done via portal into an existing table"
  },
  {
    "path": "Modern Development/Service Portal Widgets/ImportXml/importXml.js",
    "content": "// HTML for the widget \n\n<div>\n  \n    <span ng-click=\"import()\" class=\"btn btn-primary\">\n        Import\n    </span>\n\n\n    <script type=\"text/ng-template\" id=\"importTemplate\">\n        <div>\n            <h4>Import</h4>\n            <iframe id=\"uploadXml\" src=\"/upload.do?sysparm_referring_url=test_table_list.do&sysparm_target=test_table\" title=\"description\"> </iframe>\n            <button class=\"btn btn-primary\" ng-click=\"close()\">${Cancel}</button>\n        </div>\n    </script>\n</div>\n\n//Service Script for the widget\n\n(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n})();\n\n//Client Script for the widget\n\napi.controller=function($uibModal, $scope) {\n\t/* widget controller */\n\tvar c = this;\n  \n    var interval;\n\t$scope.import = function(){\n\t\t$scope.modal = $uibModal.open({\n\t\t\ttemplateUrl: 'importTemplate',\n\t\t\tscope: $scope\n\t\t});\n\n\t\tinterval = window.setInterval(function(){\n\t\t\tcheckIframeUrl();\n\t\t}, 1500);\n\t};\n\t\n\tcheckIframeUrl = function() {\n\t\tvar getIframe  = $('#uploadXml');\n\t\tif (getIframe.length > 0) {\n\t\t\t\n\t\t\tif (getIframe.contents().find(\"nav\").length != 0)\n\t\t\t\tgetIframe.contents().find(\"nav\").remove();\n\t\t\t\n\t\t\tvar currentURL = document.getElementById(\"uploadXml\").contentWindow.location.href;\n\t\t\tif (currentURL != '' && !currentURL.includes('upload.do')) {\n\t\t\t\t$scope.modal.close();\n\t\t\t\tclearInterval(interval);\n\t\t\t}\t\t\t\n\t\t}\n\t};\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Incident Sound Alerts/README.md",
    "content": "## Incident Sound Alerts Widget\n\nThis widget can be used to get sound alerts whenever a incident is created can be a major incident/high priority incident or a incident assigned to a particular group. We can place this widget on a portal page(which is open on a monitoring screen or system) and whenever the conditions are it will generate a sound alert.  Note: Currently the widget is configured to geneate alerts for Major incidents accepted. \n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Incident Sound Alerts/incident_alerts_widget.js",
    "content": "// HTML for the widget \n\n<div>\n  <div class=\"well\" ng-repeat=\"inc in c.data.incidents\" ng-class=\"\">\n    <div class=\"title\">\n      {{inc.number}}\n    </div>\n    <div class=\"description\">\n      {{inc.short_description}}\n    </div>\n  </div>\n</div>\n\n//Service Script for the widget\n\n(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n\n\tvar incidentList = [];\n\tvar incidentGR = new GlideRecord('incident');\n\tincidentGR.addQuery('major_incident_state=accepted');\n\tincidentGR.addActiveQuery();\n\tincidentGR.orderByDesc('sys_created_on');\n\tincidentGR.setLimit(5);\n\tincidentGR.query();\n\t\n\twhile(incidentGR.next()) {\n\t\tvar inc = {};\n\t\t$sp.getRecordDisplayValues(inc, incidentGR, 'number,short_description');\n\t\tincidentList.push(inc);\n\t}\n\t\n\tdata.incidents = incidentList;\n})();\n\n//Client Script for the widget\n\napi.controller=function(spUtil, $scope) {\n\t/* widget controller */\n\tvar c = this;\n  \n\tspUtil.recordWatch($scope, \"incident\", \"active=true^major_incident_state=accepted\", function(name) {\n\n\t\tc.server.refresh();\n\t\tvar audio = new Audio('Audio File Name.mp3'); //Add any audio file to the audio files module in the instance or use exisiting one.\n\t\taudio.play();\n\n\t});\n\t\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/JSON Beautifier/Client_side.js",
    "content": "api.controller=function($scope) {\n  /* widget controller */\n$scope.rawJson = '';\n$scope.formattedJson = '';\n$scope.error = '';\n\n\t$scope.beautifyJSON = function(){\n\t\ttry{\n\t\t\t$scope.error = '';\n\t\t\tconst parsed = JSON.parse($scope.rawJson);\n\t\t\t$scope.formattedJson = JSON.stringify(parsed,null,2);\n\t\t}catch(e){\n\t\t\t$scope.error = 'Invalid JSON' + e.message;\n\t\t\t$scope.formattedJson = '';\n\t\t}\n\t};\n\t\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/JSON Beautifier/HTML.html",
    "content": "<div class=\"container p-3\">\n  <h3>JSON Beautifier</h3>\n  <div class=\"form-group mt-3\">\n    <label>Paste JSON Here</label>\n    <textarea class=\"form-control\" rows=\"8\" ng-model=\"rawJson\" placeholder='{\"key\":\"value\"}'></textarea>\n  </div>\n  <div class=\"mt-2\">\n    <button class=\"btn btn-primary btn-sm mr-1\" ng-click=\"beautifyJSON()\">\n       Beautify\n    </button>\n  </div>\n  <div class=\"mt-3\" ng-if=\"error\">\n    <div class=\"alert alert-danger\">\n          {{error}}\n    </div>\n  </div>\n  <div class=\"mt-3\" ng-if=\"formattedJson\">\n    <label>Formatted JSON:</label>\n    <pre class=\"bg-light p-3\" style=\"overflow-x:auto\">{{formattedJson}}</pre>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/JSON Beautifier/README.md",
    "content": "## JSON Beautifier Widget\n\nThe JSON Beautifier widget is a developer-focused tool designed to make working with JSON in ServiceNow fast, easy and efficient. It helps admins, developers and testers handles JSON payloads from APIs, Integration etc.\n\n## Benefits\n- Reduces time spent manually formatting or checking JSON.\n- Helps identify error or differences between JSOn payload quickly\n  \n## Output\n![A test image](demo.png)\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Konami Code Easter Egg/KonamiCodeEasterEgg.js",
    "content": "function(spModal) {\n\t/* widget controller */\n\tvar c = this;\n\tconsole.log('Lol what are you doing here?');\n\n\t// the 'official' Konami Code sequence\n\t// a key map of allowed keys\n\tvar allowedKeys = {\n\t\t37: 'left',\n\t\t38: 'up',\n\t\t39: 'right',\n\t\t40: 'down',\n\t\t65: 'a',\n\t\t66: 'b'\n\t};\n\tvar konamiCode = ['up', 'up', 'down', 'down', 'left', 'right', 'left', 'right', 'b', 'a'];\n\tvar konamiCodePosition = 0;\n\tdocument.addEventListener('keydown', function (e) {\n\t\tvar key = allowedKeys[e.keyCode];\n\t\tvar requiredKey = konamiCode[konamiCodePosition];\n\t\tif (key == requiredKey) {\n\t\t\tkonamiCodePosition++;\n\t\t\tif (konamiCodePosition == konamiCode.length) {\n\t\t\t\tactivateCheats();\n\t\t\t\tkonamiCodePosition = 0;\n\t\t\t}\n\t\t} else {\n\t\t\tkonamiCodePosition = 0;\n\t\t}\n\t});\n\n\tfunction activateCheats() {\n\t\t//or do whatever you think would be fun here\n\t\tspModal.open({\n\t\t\tsize: 'sm',\n\t\t\ttitle: 'Cheats activated',\n\t\t\tmessage: 'Konami code entered',\n\t\t\tbuttons: [{ label: '${Close}', cancel: true }]\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Konami Code Easter Egg/KonamiCodeEasterEggV2.js",
    "content": "function(spModal) {\n    var c = this;\n    console.log('Lol what are you doing here?');\n    \n    const KONAMI_CODE = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', \n                         'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];\n    let inputSequence = [];\n    let timeoutId;\n    \n    const handleKeyPress = (e) => {\n        // Clear timeout to reset sequence if user pauses too long\n        clearTimeout(timeoutId);\n        \n        // Add key to sequence\n        inputSequence.push(e.key);\n        \n        // Keep only the last N keys (length of Konami code)\n        if (inputSequence.length > KONAMI_CODE.length) {\n            inputSequence.shift();\n        }\n        \n        // Check if current sequence matches Konami code\n        if (inputSequence.join(',') === KONAMI_CODE.join(',')) {\n            activateCheats();\n            inputSequence = []; // Reset after activation\n        }\n        \n        // Reset sequence after 2 seconds of inactivity\n        timeoutId = setTimeout(() => {\n            inputSequence = [];\n        }, 2000);\n    };\n    \n    const activateCheats = () => {\n        spModal.open({\n            size: 'sm',\n            title: 'Cheats activated',\n            message: 'Konami code entered',\n            buttons: [{ label: '${Close}', cancel: true }]\n        });\n    };\n    \n    document.addEventListener('keydown', handleKeyPress);\n    \n    // Cleanup listener when widget is destroyed\n    c.$onDestroy = function() {\n        document.removeEventListener('keydown', handleKeyPress);\n        clearTimeout(timeoutId);\n    };\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Konami Code Easter Egg/README.md",
    "content": "# Konami Code Easter Egg\n\nPut this code in the client controller of a widget to listen for the Konami Code. By default it just opens a modal notifying the user that the konami code as activated. Modify to do whatever fun things you want.\n\n## Version 2\n\n[KonamiCodeEasterEggV2.js](\"Modern Development\\Service Portal Widgets\\Konami Code Easter Egg\\KonamiCodeEasterEggV2.js\") is the same code but improved with:\n\n1. Uses e.key instead of e.keyCode (which is deprecated) with modern arrow key names\n2. Automatically tracks only the last N keypresses instead of manual position tracking\n3. Resets the sequence if the user pauses too long (more forgiving UX)\n4. Removes event listener when widget is destroyed to prevent memory leaks\n5. Uses array join comparison instead of position tracking\n6. Modern variable declarations for better scoping\n\n<img width=\"314\" height=\"205\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ea39dbdf-c252-4f7f-942d-8f26319ca6e2\" />\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Language Selector/README.md",
    "content": "# Language Selector with Flags\n\nA language selector widget for the Portal.  \nThe user can change the instance language without having to leave the Portal.\n\n<img width=\"255\" height=\"95\" alt=\"image\" src=\"https://github.com/user-attachments/assets/af130ec4-d724-4b07-a38f-afd858b7eba2\" />\n<img width=\"234\" height=\"195\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a2de8161-a922-4376-904d-b16f81dcc573\" />\n\n\n## What it does\n- Displays a dropdown with flags and language names.\n- Automatically updates the user's language in the `sys_user` table.\n- Reloads the page to apply the new language immediately.\n\n## Files\n- **HTML Template:** renders the dropdown with flag emojis and labels.  \n- **Client Script:** handles language selection and sends the PATCH request.  \n- **Server Script:** provides the current user ID and stored language.  \n\n## Example\nWhen the user selects **🇪🇸 Spanish**, the widget updates their user record and reloads the Portal in Spanish.\n\n## Prerequisites\n- The language selected **must be installed and active** in the instance.  \n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Language Selector/language-selector.client.js",
    "content": "function($http) {\n  var c = this;\n\n  c.languages = [\n    { code: 'en',    label: 'English',             flag: '🇬🇧' },\n    { code: 'pb', \t label: 'Portuguese (Brazil)', flag: '🇧🇷' },\n    { code: 'es',    label: 'Spanish',             flag: '🇪🇸' },\n    { code: 'fr',    label: 'French',           \t flag: '🇫🇷' },\n    { code: 'de',    label: 'German',            \t flag: '🇩🇪' },\n    { code: 'it',    label: 'Italian',           \t flag: '🇮🇹' }\n  ];\n\n  c.userId = c.data.user_id;\n  c.selected = c.data.language || 'en';\n\n  c.changeLang = function() {\n    $http.patch('/api/now/table/sys_user/' + c.userId, { preferred_language: c.selected })\n      .then(function(response) {\n        location.reload();\n      });\n  };\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Language Selector/language-selector.css",
    "content": ".lang-selector {\n  display: flex;\n  align-items: center;\n  gap: 8px;\n}\nselect, button {\n  padding: 6px 8px;\n  border-radius: 6px;\n  border: 1px solid #ccc;\n}\nbutton {\n  cursor: pointer;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Language Selector/language-selector.html",
    "content": "<div class=\"lang-selector\">\n  <select ng-model=\"c.selected\"\n          ng-options=\"l.code as (l.flag + ' ' + l.label) for l in c.languages\"\n          ng-change=\"c.changeLang()\">\n  </select>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Language Selector/language-selector.server.js",
    "content": "(function() {\n  var user = gs.getUser();\n  data.user_id = user.getID();\n\n  var grUser = new GlideRecord('sys_user');\n  if (grUser.get(data.user_id)) {\n    data.language = grUser.getValue('preferred_language') || 'en'; \n  }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Client Controller",
    "content": "api.controller = function($scope, $interval, spModal, $window) {\n    var c = this;\n    \n    // Initialize\n    c.counts = {};\n    c.changes = {};\n    c.lastUpdate = new Date();\n    c.isRefreshing = false;\n    c.autoRefresh = true;\n    c.soundEnabled = true;\n    c.newCritical = false;\n    var refreshInterval;\n    \n    // Load initial data\n    c.$onInit = function() {\n        c.counts = c.data.counts || {};\n        c.previousCounts = angular.copy(c.counts);\n        c.startAutoRefresh();\n    };\n    \n    // Refresh data\n    c.refresh = function() {\n        c.isRefreshing = true;\n        \n        c.server.get().then(function(response) {\n            var newCounts = response.data.counts;\n            \n            // Calculate changes\n            c.changes = {\n                critical: (newCounts.critical || 0) - (c.counts.critical || 0),\n                high: (newCounts.high || 0) - (c.counts.high || 0),\n                medium: (newCounts.medium || 0) - (c.counts.medium || 0),\n                low: (newCounts.low || 0) - (c.counts.low || 0)\n            };\n            \n            // Check for new critical tickets\n            if (c.changes.critical > 0) {\n                c.newCritical = true;\n                if (c.soundEnabled) {\n                    c.playAlertSound();\n                }\n                \n                // Remove pulse animation after 3 seconds\n                $interval(function() {\n                    c.newCritical = false;\n                }, 3000, 1);\n            }\n            \n            // Update counts\n            c.counts = newCounts;\n            c.lastUpdate = new Date();\n            c.isRefreshing = false;\n        });\n    };\n    \n    // Auto-refresh toggle\n    c.toggleAutoRefresh = function() {\n        if (c.autoRefresh) {\n            c.startAutoRefresh();\n        } else {\n            c.stopAutoRefresh();\n        }\n    };\n    \n    // Start auto-refresh\n    c.startAutoRefresh = function() {\n        if (refreshInterval) {\n            $interval.cancel(refreshInterval);\n        }\n        \n        refreshInterval = $interval(function() {\n            c.refresh();\n        }, 30000); // 30 seconds\n    };\n    \n    // Stop auto-refresh\n    c.stopAutoRefresh = function() {\n        if (refreshInterval) {\n            $interval.cancel(refreshInterval);\n        }\n    };\n    \n    // Play sound alert\n    c.playAlertSound = function() {\n        var audio = new Audio('data:audio/wav;base64,UklGRnoGAABXQVZFZm10IBAAAAABAAEAQB8AAEAfAAABAAgAZGF0YQoGAACBhYqFbF1fdJivrJBhNjVgodDbq2EcBj+a2/LDciUFLIHO8tiJNwgZaLvt559NEAxQp+PwtmMcBjiR1/LMeSwFJHfH8N2QQAoUXrTp66hVFApGn+DyvmwhBSqA0fPTgjMGHm7A7+OZRQ0PVq/m77BdGAg+ltryxnMpBSl+zPLaizsIGGS57OibUBELTqXh8bllHAU2jdXwyH0vBSZ8yfDajkULEFau5u+wXRgIPpXa8sZzKQUpfszy2Ys7CBhkuezom1ARDEyl4fG5ZRwFNo3V8Mh9LwUmfMnw2o5FDBFWrebvsF0YCD6V2vLGcykFKX7M8tmLOwgYZLns6JtQEQxMpeHxuWUcBTaN1fDIfS8FJnzJ8NqORQwRVq3m77BdGAg+ldryx3MpBSl+zPLaizsIGGS57OmbUBEMTKXh8bllHAU2jdXwyH0vBSZ8yfDajkUMEVat5u+wXRgIPpXa8sZzKQUpfszy2Ys7CBhkuezom1ARDEyl4fG5ZRwFNo3V8Mh9LwUmfMnw2o5FDBFWrebvsF0YCD6V2vLGcykFKX7M8tmLOwgYZLns6JtQEQxMpeHxuWUcBTaN1fDIfS8FJnzJ8NqORQwRVq3m77BdGAg=');\n        audio.play().catch(function(e) {\n            console.log('Could not play sound:', e);\n        });\n    };\n    \n    // Toggle sound\n    c.toggleSound = function() {\n        c.soundEnabled = !c.soundEnabled;\n        if (c.soundEnabled) {\n            spModal.alert('🔊 Sound alerts enabled');\n        } else {\n            spModal.alert('🔇 Sound alerts disabled');\n        }\n    };\n    \n    // View tickets by priority\n    c.viewTickets = function(priority) {\n        var priorityNames = {\n            '1': 'Critical',\n            '2': 'High',\n            '3': 'Medium',\n            '4': 'Low'\n        };\n        \n        // Navigate to filtered list\n        $window.location.href = '/incident_list.do?sysparm_query=priority=' + priority + '^active=true';\n    };\n    \n    // Cleanup on destroy\n    c.$onDestroy = function() {\n        c.stopAutoRefresh();\n    };\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter .css",
    "content": ".ticket-counter-widget {\n  background: white;\n  border-radius: 12px;\n  padding: 20px;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.1);\n  max-width: 800px;\n  margin: 0 auto;\n}\n\n.widget-header {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  margin-bottom: 20px;\n  border-bottom: 2px solid #f0f0f0;\n  padding-bottom: 15px;\n}\n\n.widget-header h3 {\n  margin: 0;\n  font-size: 24px;\n  color: #333;\n}\n\n.refresh-btn {\n  background: #007bff;\n  color: white;\n  border: none;\n  padding: 8px 12px;\n  border-radius: 50%;\n  font-size: 20px;\n  cursor: pointer;\n  transition: all 0.3s ease;\n}\n\n.refresh-btn:hover {\n  background: #0056b3;\n  transform: scale(1.1);\n}\n\n.refresh-btn.spinning {\n  animation: spin 1s linear infinite;\n}\n\n@keyframes spin {\n  from { transform: rotate(0deg); }\n  to { transform: rotate(360deg); }\n}\n\n.counter-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));\n  gap: 15px;\n  margin-bottom: 20px;\n}\n\n.counter-card {\n  background: white;\n  border-radius: 10px;\n  padding: 20px;\n  text-align: center;\n  cursor: pointer;\n  transition: all 0.3s ease;\n  border: 2px solid #e0e0e0;\n}\n\n.counter-card:hover {\n  transform: translateY(-5px);\n  box-shadow: 0 5px 15px rgba(0,0,0,0.2);\n}\n\n.counter-card.critical {\n  border-left: 5px solid #dc3545;\n}\n\n.counter-card.high {\n  border-left: 5px solid #fd7e14;\n}\n\n.counter-card.medium {\n  border-left: 5px solid #ffc107;\n}\n\n.counter-card.low {\n  border-left: 5px solid #28a745;\n}\n\n.counter-icon {\n  font-size: 32px;\n  margin-bottom: 10px;\n}\n\n.counter-number {\n  font-size: 48px;\n  font-weight: bold;\n  color: #333;\n  margin: 10px 0;\n}\n\n.counter-number.pulse {\n  animation: pulse 1s ease-in-out infinite;\n}\n\n@keyframes pulse {\n  0% { transform: scale(1); }\n  50% { transform: scale(1.1); color: #dc3545; }\n  100% { transform: scale(1); }\n}\n\n.counter-label {\n  font-size: 14px;\n  color: #666;\n  font-weight: 600;\n  text-transform: uppercase;\n}\n\n.counter-change {\n  margin-top: 5px;\n  padding: 3px 8px;\n  background: #ff6b6b;\n  color: white;\n  border-radius: 12px;\n  font-size: 11px;\n  display: inline-block;\n  animation: slideIn 0.5s ease;\n}\n\n@keyframes slideIn {\n  from {\n    opacity: 0;\n    transform: translateY(-10px);\n  }\n  to {\n    opacity: 1;\n    transform: translateY(0);\n  }\n}\n\n.widget-footer {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n  padding-top: 15px;\n  border-top: 1px solid #f0f0f0;\n  font-size: 12px;\n  color: #666;\n}\n\n.auto-refresh label {\n  display: flex;\n  align-items: center;\n  gap: 5px;\n  cursor: pointer;\n}\n\n.sound-toggle {\n  position: absolute;\n  top: 20px;\n  right: 70px;\n}\n\n.sound-toggle button {\n  background: #f0f0f0;\n  border: none;\n  padding: 8px 12px;\n  border-radius: 50%;\n  font-size: 20px;\n  cursor: pointer;\n  transition: all 0.3s ease;\n}\n\n.sound-toggle button.active {\n  background: #28a745;\n}\n\n.sound-toggle button:hover {\n  transform: scale(1.1);\n}\n\n@media (max-width: 600px) {\n  .counter-grid {\n    grid-template-columns: repeat(2, 1fr);\n  }\n  \n  .widget-footer {\n    flex-direction: column;\n    gap: 10px;\n  }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Live Ticket Counter.html",
    "content": "<div class=\"ticket-counter-widget\">\n  <div class=\"widget-header\">\n    <h3>🎫 Live Ticket Monitor</h3>\n    <button class=\"refresh-btn\" ng-click=\"c.refresh()\" ng-class=\"{'spinning': c.isRefreshing}\">\n      🔄\n    </button>\n  </div>\n  \n  <div class=\"counter-grid\">\n    <!-- Critical Priority -->\n    <div class=\"counter-card critical\" ng-click=\"c.viewTickets('1')\">\n      <div class=\"counter-icon\">🔴</div>\n      <div class=\"counter-number\" ng-class=\"{'pulse': c.newCritical}\">\n        {{c.counts.critical || 0}}\n      </div>\n      <div class=\"counter-label\">Critical</div>\n      <div class=\"counter-change\" ng-if=\"c.changes.critical > 0\">\n        +{{c.changes.critical}} new\n      </div>\n    </div>\n    \n    <!-- High Priority -->\n    <div class=\"counter-card high\" ng-click=\"c.viewTickets('2')\">\n      <div class=\"counter-icon\">🟠</div>\n      <div class=\"counter-number\">\n        {{c.counts.high || 0}}\n      </div>\n      <div class=\"counter-label\">High</div>\n      <div class=\"counter-change\" ng-if=\"c.changes.high > 0\">\n        +{{c.changes.high}} new\n      </div>\n    </div>\n    \n    <!-- Medium Priority -->\n    <div class=\"counter-card medium\" ng-click=\"c.viewTickets('3')\">\n      <div class=\"counter-icon\">🟡</div>\n      <div class=\"counter-number\">\n        {{c.counts.medium || 0}}\n      </div>\n      <div class=\"counter-label\">Medium</div>\n      <div class=\"counter-change\" ng-if=\"c.changes.medium > 0\">\n        +{{c.changes.medium}} new\n      </div>\n    </div>\n    \n    <!-- Low Priority -->\n    <div class=\"counter-card low\" ng-click=\"c.viewTickets('4')\">\n      <div class=\"counter-icon\">🟢</div>\n      <div class=\"counter-number\">\n        {{c.counts.low || 0}}\n      </div>\n      <div class=\"counter-label\">Low</div>\n      <div class=\"counter-change\" ng-if=\"c.changes.low > 0\">\n        +{{c.changes.low}} new\n      </div>\n    </div>\n  </div>\n  \n  <div class=\"widget-footer\">\n    <div class=\"last-update\">\n      Last updated: {{c.lastUpdate | date:'HH:mm:ss'}}\n    </div>\n    <div class=\"auto-refresh\">\n      <label>\n        <input type=\"checkbox\" ng-model=\"c.autoRefresh\" ng-change=\"c.toggleAutoRefresh()\">\n        Auto-refresh (30s)\n      </label>\n    </div>\n  </div>\n  \n  <!-- Sound Toggle -->\n  <div class=\"sound-toggle\">\n    <button ng-click=\"c.toggleSound()\" ng-class=\"{'active': c.soundEnabled}\">\n      {{c.soundEnabled ? '🔊' : '🔇'}}\n    </button>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/README.md",
    "content": "# Live Ticket Counter - Service Portal Widget.\n\n## Use Case\nThe Live Ticket Counter widget displays a real-time count of incident tickets categorized by priority (Critical, High, Medium, Low). It visually updates with an animation to indicate refresh activity and allows users to click on any priority count to view a filtered list of tickets of that priority. This helps support teams monitor ticket load dynamically and quickly access relevant tickets for faster incident management.\n\n## Why it's unique  \n- Real-time ticket count updates without page refresh   \n- Visual pulse animation highlights new critical tickets  \n- Clickable cards open filtered ticket lists by priority  \n- Beginner-friendly and easy to implement  \n\n## How it is Useful in ServiceNow  \n- Real-time updates keep support agents and managers instantly informed of critical ticket volume changes, helping prioritize work promptly.  \n- Visible counts by priority enable supervision to redistribute workloads or escalate issues proactively.  \n- Agents save time by accessing filtered ticket lists with a click instead of manually searching for tickets by priority.  \n- The widget's last updated timestamp and auto-refresh toggle give users control and confidence in data freshness.  \n- As a light, interactive component, it complements existing dashboards and pages without heavy customizations.  \n\n## How to Use\n\n1. Create Widget\n   - Go to Service Portal > Widgets  \n   - Click \"New\"  \n   - Copy-paste HTML, Client Controller, Server Script, and CSS into appropriate sections  \n\n2. Add to Page \n   - Place the widget on any Service Portal page where ticket monitoring is required  \n   - Widget ID: `live_ticket_counter_sp`  \n\n3. Test and Customize \n   - View tickets by clicking on priority cards  \n   - Toggle auto-refresh and sound alerts  \n   - Modify styling or priority levels as needed\n  \n## Compatibility\nThis Html , css , client , server code is compatible with all standard ServiceNow instances without requiring ES2021 features.\n\n## Files\n- `Live Ticket Counter.html`,`Live Ticket Counter .css`,`Server Script`,`Client Script`, — these are files.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Live Ticket Counter Service Portal Widget/Server Script",
    "content": "(function() {\n    // Get ticket counts by priority\n    data.counts = {};\n    \n    try {\n        // Count Critical (Priority 1)\n        var criticalGR = new GlideAggregate('incident');\n        criticalGR.addQuery('priority', '1');\n        criticalGR.addQuery('active', true);\n        criticalGR.addAggregate('COUNT');\n        criticalGR.query();\n        if (criticalGR.next()) {\n            data.counts.critical = parseInt(criticalGR.getAggregate('COUNT'));\n        }\n        \n        // Count High (Priority 2)\n        var highGR = new GlideAggregate('incident');\n        highGR.addQuery('priority', '2');\n        highGR.addQuery('active', true);\n        highGR.addAggregate('COUNT');\n        highGR.query();\n        if (highGR.next()) {\n            data.counts.high = parseInt(highGR.getAggregate('COUNT'));\n        }\n        \n        // Count Medium (Priority 3)\n        var mediumGR = new GlideAggregate('incident');\n        mediumGR.addQuery('priority', '3');\n        mediumGR.addQuery('active', true);\n        mediumGR.addAggregate('COUNT');\n        mediumGR.query();\n        if (mediumGR.next()) {\n            data.counts.medium = parseInt(mediumGR.getAggregate('COUNT'));\n        }\n        \n        // Count Low (Priority 4)\n        var lowGR = new GlideAggregate('incident');\n        lowGR.addQuery('priority', '4');\n        lowGR.addQuery('active', true);\n        lowGR.addAggregate('COUNT');\n        lowGR.query();\n        if (lowGR.next()) {\n            data.counts.low = parseInt(lowGR.getAggregate('COUNT'));\n        }\n        \n    } catch (e) {\n        gs.error('Error in Live Ticket Counter: ' + e.message);\n        data.counts = {\n            critical: 0,\n            high: 0,\n            medium: 0,\n            low: 0\n        };\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Location hierarchy/README.md",
    "content": "This widget allows you to hierarchically choose a location for the catalog item variable. \n\nWith small additional setup you can adjust it to your own structure. You also need a variable (preferably multi line text) to save the output of this widget.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Location hierarchy/client controller.js",
    "content": "api.controller=function($scope, spModal) {\n  \tvar c = this;\n\tc.newLocations = [];\n\t\n\tc.server.get({\n\t\taction: 'getChildren',\n\t\ttype: 'city',\n\t\tparent: null\n\t}).then((res) => {\n\t\tc.locations = res.data.locations;\n\t\t$scope.locations = res.data.locations.selected;\n\t\t$scope.locations.room = '';\n\t});\n\t\n\tc.changeCity = (newSysId) => {\n\t\t$scope.locations.city = c.locations.available.city.filter((city) => city.sys_id === newSysId)[0];\n\t\t$scope.locations['building/structure'] = c.locations.default['building/structure'];\n\t\t$scope.locations.floor = c.locations.default.floor;\n\t\t$scope.locations.zone = c.locations.default.zone;\n\t\t\n\t\tc.getChildren('building/structure', newSysId);\n\t}\n\t\n\tc.changeBuilding = (newSysId) => {\n\t\t$scope.locations['building/structure'] = c.locations.available['building/structure'].filter((building) => building.sys_id === newSysId)[0];\n\t\t$scope.locations.floor = c.locations.default.floor;\n\t\t$scope.locations.zone = c.locations.default.zone;\n\t\t\n\t\tc.getChildren('floor', newSysId);\n\t}\n\t\n\tc.changeFloor = (newSysId) => {\n\t\t$scope.locations.floor = c.locations.available.floor.filter((floor) => floor.sys_id === newSysId)[0];\n\t\t$scope.locations.zone = c.locations.default.zone;\n\t\t\n\t\tc.getChildren('zone', newSysId);\n\t}\n\t\n\tc.changeZone = (newSysId) => {\n\t\t$scope.locations.zone = c.locations.available.zone.filter((zone) => zone.sys_id === newSysId)[0];\n\t}\n\t\n\tc.changeRoom = () => {\n\t\t//c.newLocations.push($scope.locations.zone.name + $scope.locations.room);\n\t}\n\t\n\tc.addNewLocation = () => {\n\t\tc.newLocations.push('City: ' + $scope.locations.city.name + ', building: ' + $scope.locations['building/structure'].name + ', floor: ' + $scope.locations.floor.name + ', corridor: ' + $scope.locations.zone.name + ', room: * ' + $scope.locations.room);\n\t\tif ($scope.page.g_form) {\n\t\t\t$scope.page.g_form.setValue('locks_locations_new_locations_added', c.newLocations.join('\\n'));\n\t\t}\n\t\t$scope.locations.room = '';\n\t}\n\t\n\tc.notListedModal = (type) => {\n\t\tspModal.prompt('Please enter missing value for the ' + type)\n\t\t\t.then(value => {\n\t\t\t\t$scope.locations[type] = {name: '* ' + value, sys_id: '-2'};\n\t\t\t});\n\t}\n\t\n\tc.getChildren = (type, parent) => {\n\t\tc.server.get({\n\t\t\taction: 'getChildren',\n\t\t\ttype: type,\n\t\t\tparent: parent\n\t\t}).then((res) => {\n\t\t\tc.locations.available[type] = res.data.locations.available[type];\n\t\t});\n\t}\n\t\n\tc.allSelected = () => {\n\t\treturn $scope.locations && \n\t\t\t\t\t\t$scope.locations.city.sys_id !== '-1' && \n\t\t\t\t\t\t$scope.locations['building/structure'].sys_id !== '-1' &&\n\t\t\t\t\t\t$scope.locations.floor.sys_id !== '-1' && \n\t\t\t\t\t\t$scope.locations.zone.sys_id !== '-1'\n\t}\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Location hierarchy/css.css",
    "content": ".location-picker-input {\n  padding: 6px 12px;\n  color: #555555;\n  border: 1px solid #939393;\n  border-radius: 4px;\n  -webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n  box-shadow: inset 0 1px 1px rgba(0, 0, 0, .075);\n  -webkit-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n  -o-transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n  transition: border-color ease-in-out .15s, box-shadow ease-in-out .15s;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Location hierarchy/html.html",
    "content": "<h3>\n  Describe the new location\n</h3>\n<div class=\"form-group\">\n\t<div class=\"btn-group\" uib-dropdown>\n\t\t<button id=\"cityRef\" type=\"button\" class=\"btn btn-info\" uib-dropdown-toggle>\n\t\t\t{{locations.city.name}}&nbsp;<span class=\"caret\"></span>\n\t\t</button>\n\t\t<ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"cityRef\">\n\t\t\t<li ng-repeat=\"city in c.locations.available.city\" role=\"menuitem\" ng-click=\"c.changeCity(city.sys_id)\"><a href=\"#\">{{city.name}}</a></li>\n\t\t\t<li class=\"divider\"></li>\n\t\t\t<li role=\"menuitem\" ng-click=\"c.notListedModal('city')\"><a href=\"#\">City not listed</a></li>\n\t\t</ul>\n  </div>\n\t<div class=\"btn-group\" uib-dropdown>\n\t\t<button id=\"buildingRef\" type=\"button\" class=\"btn btn-info\" uib-dropdown-toggle ng-disabled=\"locations.city.sys_id === '-1'\">\n\t\t\t{{locations['building/structure'].name}}&nbsp;<span class=\"caret\"></span>\n\t\t</button>\n\t\t<ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"buildingRef\">\n      <li ng-repeat=\"building in c.locations.available['building/structure']\" role=\"menuitem\" ng-click=\"c.changeBuilding(building.sys_id)\"><a href=\"#\">{{building.name}}</a></li>\n\t\t\t<li class=\"divider\"></li>\n\t\t\t<li role=\"menuitem\" ng-click=\"c.notListedModal('building/structure')\"><a href=\"#\">Building not listed</a></li>\n\t\t</ul>\n  </div>\n \t<div class=\"btn-group\" uib-dropdown>\n\t\t<button id=\"floorRef\" type=\"button\" class=\"btn btn-info\" uib-dropdown-toggle ng-disabled=\"locations['building/structure'].sys_id === '-1'\">\n\t\t\t{{locations.floor.name}}&nbsp;<span class=\"caret\"></span>\n\t\t</button>\n\t\t<ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"floorRef\">\n      <li ng-repeat=\"floor in c.locations.available.floor\" role=\"menuitem\" ng-click=\"c.changeFloor(floor.sys_id)\"><a href=\"#\">{{floor.name}}</a></li>\n\t\t\t<li class=\"divider\"></li>\n\t\t\t<li role=\"menuitem\" ng-click=\"c.notListedModal('floor')\"><a href=\"#\">Floor not listed</a></li>\n\t\t</ul>\n  </div>\n\t<div class=\"btn-group\" uib-dropdown>\n\t\t<button id=\"zoneRef\" type=\"button\" class=\"btn btn-info\" uib-dropdown-toggle ng-disabled=\"locations.floor.sys_id === '-1'\">\n\t\t\t{{locations.zone.name}}&nbsp;<span class=\"caret\"></span>\n\t\t</button>\n\t\t<ul class=\"dropdown-menu\" uib-dropdown-menu role=\"menu\" aria-labelledby=\"zoneRef\">\n      <li ng-repeat=\"zone in c.locations.available.zone\" role=\"menuitem\" ng-click=\"c.changeZone(zone.sys_id)\"><a href=\"#\">{{zone.name}}</a></li>\n\t\t\t<li class=\"divider\"></li>\n\t\t\t<li role=\"menuitem\" ng-click=\"c.notListedModal('zone')\"><a href=\"#\">Corridor not listed</a></li>\n\t\t</ul>\n  </div>\n  <input id=\"roomRef\" class=\"location-picker-input\" type=\"text\" placeholder=\"Room name\" ng-disabled=\"!c.allSelected()\" ng-model=\"locations.room\" ng-blur=\"c.changeRoom()\"/>\n  <button type=\"button\" class=\"btn btn-success\" ng-click=\"c.addNewLocation()\" ng-disabled=\"locations.room === ''\">Add to the list</button>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Location hierarchy/server script.js",
    "content": "(function() {\n\tdata.locations = {\n\t\tavailable: {\n\t\t\tcity: [],\n\t\t\t'building/structure': [],\n\t\t\tfloor: [],\n\t\t\tzone: []\n\t\t},\n\t\tdefault: {\n\t\t\tcity: {name: 'Pick a city', sys_id: '-1'},\n\t\t\t'building/structure': {name: 'Pick a building', sys_id: '-1'},\n\t\t\tfloor: {name: 'Pick a floor', sys_id: '-1'},\n\t\t\tzone: {name: 'Pick a corridor', sys_id: '-1'}\n\t\t}\n\t};\n\t\n\tdata.locations.selected = data.locations.default;\n\t\n\tif (input && input.action === 'getChildren' && input.type) {\n\t\tdata.locations.available[input.type] = getData(input.type, input.parent);\n\t}\n\t\n\tfunction getData(locationType, parent) {\n\t\tlet ret = [];\n\t\tlet gq = new global.GlideQuery('cmn_location').where('cmn_location_type', locationType);\n\t\t\n\t\tif (!gs.nil(parent))\n\t\t\tgq = gq.where('parent', parent);\n\t\t\n\t\tgq\n\t\t\t.orderBy('name')\n\t\t\t.select('name')\n\t\t\t.forEach(function(childLocation){\n\t\t\t\tret.push({\n\t\t\t\t\tsys_id: childLocation.sys_id,\n\t\t\t\t\tname: childLocation.name\n\t\t\t\t});\n\t\t\t});\n\t\treturn ret;\n\t}\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Manage Delegates Widget/README.md",
    "content": "# My Delegates -  Portal Widget \n## Overview\nSimple Service Portal widget to create, edit, list, and delete OOB Delegate functionality in portal. \n\n## Files included\n- HTML : widget HTML.\n- CSS : widget CSS.\n- Client Script : widget Client contriller.\n- Server Script : widget Server Script.\n\n## How to use\n- Create a new Service Portal widget.\n- Copy paste the html, css, client and server scipts to respective fields\n- Save and add the widget to a portal page.\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Manage Delegates Widget/delegates.html",
    "content": "<div class=\"panel panel-default\">\n  <div class=\"panel-heading\">\n    <h3 class=\"panel-title\">Delegates</h3>\n  </div>\n  <div class=\"panel-body\">\n    <div ng-if=\"!c.data.delegates.length\" class=\"alert alert-info text-center\">\n      You have no active delegates.\n    </div>\n\n    <ul class=\"list-group\" ng-if=\"c.data.delegates.length > 0\">\n      <li class=\"list-group-item delegate-item\" ng-repeat=\"d in c.data.delegates\">\n        <h4>{{d.delegate_display}}</h4>\n        <p class=\"text-muted\">\n          <strong>User:</strong> {{d.user_display}} <br/>\n          <strong>Starts:</strong> {{d.starts_display || 'N/A'}} <br/>\n          <strong>Ends:</strong> {{d.ends_display || 'N/A'}}\n        </p>\n        <p>\n          <strong>Notifications:</strong>\n          <span ng-if=\"d.approvals\"> Approvals</span>\n          <span ng-if=\"d.assignments\"> Assignments</span>\n          <span ng-if=\"d.notifications\"> All</span>\n          <span ng-if=\"d.invitations\"> Invitations</span>\n        </p>\n        <div class=\"btn-row\">\n          <button class=\"btn btn-sm btn-default\" ng-click=\"c.edit(d.sys_id)\">\n            <i class=\"fa fa-edit\"></i> Edit\n          </button>\n        </div>\n      </li>\n    </ul>\n\n    <hr/>\n\n    <div class=\"delegate-form-container\">\n      <h4 ng-if=\"!c.editing\">Create a New Delegate</h4>\n      <h4 ng-if=\"c.editing\">Edit Delegate</h4>\n\n      <form name=\"delegateForm\" ng-submit=\"c.saveDelegate()\">\n        <!-- User is not editable: recorded as current user server-side -->\n\n        <div class=\"form-group\">\n          <label for=\"delegate\">Delegate <span class=\"text-danger\">*</span></label>\n          <sn-record-picker field=\"c.delegateField\" table=\"'sys_user'\" display-field=\"'name'\" value-field=\"'sys_id'\" search-fields=\"'name,email'\" page-size=\"10\" placeholder=\"Search delegate...\" required></sn-record-picker>\n        </div>\n\n        <div class=\"row\">\n          <div class=\"col-md-6\">\n            <div class=\"form-group\">\n              <label>Starts</label>\n              <input type=\"datetime-local\" class=\"form-control\" ng-model=\"c.form.starts_local\" />\n            </div>\n          </div>\n          <div class=\"col-md-6\">\n            <div class=\"form-group\">\n              <label>Ends</label>\n              <input type=\"datetime-local\" class=\"form-control\" ng-model=\"c.form.ends_local\" />\n            </div>\n          </div>\n        </div>\n\n        <div class=\"checkboxes form-group\">\n          <label class=\"checkbox-inline\"><input type=\"checkbox\" ng-model=\"c.form.approvals\"> Approvals</label>\n          <label class=\"checkbox-inline\"><input type=\"checkbox\" ng-model=\"c.form.assignments\"> Assignments</label>\n          <label class=\"checkbox-inline\"><input type=\"checkbox\" ng-model=\"c.form.notifications\"> All notifications</label>\n          <label class=\"checkbox-inline\"><input type=\"checkbox\" ng-model=\"c.form.invitations\"> Meeting invitations</label>\n        </div>\n\n        <div class=\"form-group\">\n          <button type=\"submit\" class=\"btn btn-primary\" ng-disabled=\"delegateForm.$invalid\">\n            <i class=\"fa\" ng-class=\"{'fa-plus-circle': !c.editing, 'fa-save': c.editing}\"></i>\n            {{c.editing ? 'Save' : 'Create'}}\n          </button>\n          <button type=\"button\" class=\"btn btn-default\" ng-click=\"c.resetForm()\">New</button>\n          <button type=\"button\" class=\"btn btn-danger\" ng-if=\"c.editing\" ng-click=\"c.deleteDelegate()\">\n            <i class=\"fa fa-trash\"></i> Delete\n          </button>\n        </div>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Manage Delegates Widget/delegates.scss",
    "content": ".delegate-item {\n  border-left: 4px solid #5bc0de;\n  margin-bottom: 10px;\n  padding: 10px 15px;\n}\n.delegate-item h4 {\n  margin-top: 0;\n  font-weight: bold;\n  color: #333;\n}\n.delegate-form-container {\n  margin-top: 20px;\n  padding-top: 15px;\n  border-top: 1px solid #eee;\n}\n.btn-row { margin-top: 8px; }\n.form-group { margin-bottom: 15px; }\n.panel-heading + .panel-body { padding-top: 20px; }\n.checkboxes label { margin-right: 12px; }\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Manage Delegates Widget/delegates_client.css",
    "content": "function($scope) {\n  var c = this;\n  c.form = {};\n  c.editing = false;\n  c.delegateField = { displayValue: '', value: '', name: 'delegate' };\n\n  c.$onInit = function() {\n    c.data = c.data || {};\n    c.form = {\n      approvals: false,\n      assignments: false,\n      notifications: false,\n      invitations: false\n    };\n  };\n\n  function pad(n){ return n<10 ? '0'+n : n; }\n\n  function localToGlide(local) {\n    if (!local) return '';\n    var d = new Date(local);\n    return d.getFullYear() + '-' + pad(d.getMonth()+1) + '-' + pad(d.getDate())\n      + ' ' + pad(d.getHours()) + ':' + pad(d.getMinutes()) + ':' + pad(d.getSeconds());\n  }\n\n  c.saveDelegate = function() {\n    c.form.delegate = c.delegateField.value;\n    c.form.starts = localToGlide(c.form.starts_local);\n    c.form.ends = localToGlide(c.form.ends_local);\n\n    c.data.action = 'save_delegate';\n    c.data.record = angular.copy(c.form);\n\n    c.server.update().then(function() {\n      c.data.action = undefined;\n      c.resetForm();\n      c.server.get().then(function(response) {\n        c.data = response.data;\n      });\n    }, function() {\n      alert('Failed to save delegate.');\n    });\n  };\n\n  c.edit = function(sys_id) {\n    c.server.get().then(function(response) {\n      c.data = response.data;\n      var rec = c.data.delegates.find(function(x){ return x.sys_id === sys_id; }) || {};\n      c.editing = true;\n      c.form = {\n        sys_id: rec.sys_id,\n        approvals: !!rec.approvals,\n        assignments: !!rec.assignments,\n        notifications: !!rec.notifications,\n        invitations: !!rec.invitations,\n        starts_local: rec.starts_value ? new Date(rec.starts_value.replace(' ', 'T')) : null,\n        ends_local: rec.ends_value ? new Date(rec.ends_value.replace(' ', 'T')) : null\n      };\n      c.delegateField.value = rec.delegate_sys_id || rec.delegate;\n      c.delegateField.displayValue = rec.delegate_display;\n    });\n  };\n\n  c.deleteDelegate = function() {\n    if (!c.form.sys_id) return;\n    if (!confirm('Delete delegate record?')) return;\n    c.data.action = 'delete_delegate';\n    c.data.sys_id = c.form.sys_id;\n    c.server.update().then(function() {\n      c.data.action = undefined;\n      c.resetForm();\n      c.server.get().then(function(response) { c.data = response.data; });\n    });\n  };\n\n  c.resetForm = function() {\n    c.editing = false;\n    c.form = {\n      approvals: false,\n      assignments: false,\n      notifications: false,\n      invitations: false\n    };\n    c.delegateField = { displayValue: '', value: '', name: 'delegate' };\n  };\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Manage Delegates Widget/delegates_server.js",
    "content": "(function() {\n  data.delegates = [];\n  var currentUser = gs.getUserID(); \n\n  if (input && input.action === 'save_delegate' && input.record) {\n    var rec = input.record;\n    var gr;\n    if (rec.sys_id) {\n      gr = new GlideRecord('sys_user_delegate');\n      if (!gr.get(rec.sys_id)) {\n        gr.initialize();\n      }\n    } else {\n      gr = new GlideRecord('sys_user_delegate');\n      gr.initialize();\n    }\n\n    gr.setValue('user', currentUser);\n\n    if (rec.delegate) gr.setValue('delegate', rec.delegate);\n    if (rec.starts) gr.setValue('starts', rec.starts);\n    if (rec.ends) gr.setValue('ends', rec.ends);\n\n    gr.setValue('approvals', rec.approvals ? 'true' : 'false');\n    gr.setValue('assignments', rec.assignments ? 'true' : 'false');\n    gr.setValue('notifications', rec.notifications ? 'true' : 'false');\n    gr.setValue('invitations', rec.invitations ? 'true' : 'false');\n\n    var id = gr.update();\n    data.saved_sys_id = id;\n  }\n\n  if (input && input.action === 'delete_delegate' && input.sys_id) {\n    var ddel = new GlideRecord('sys_user_delegate');\n    if (ddel.get(input.sys_id)) {\n      ddel.deleteRecord();\n      data.deleted = true;\n    } else {\n      data.deleted = false;\n    }\n  }\n\n\n  var grList = new GlideRecord('sys_user_delegate');\n  grList.addQuery('user', currentUser);\n  grList.orderByDesc('starts');\n  grList.query();\n  while (grList.next()) {\n    var obj = {};\n    obj.sys_id = grList.getUniqueValue();\n    obj.user = grList.getValue('user');\n    obj.delegate = grList.getValue('delegate');\n    obj.user_display = grList.getDisplayValue('user');\n    obj.delegate_display = grList.getDisplayValue('delegate');\n    obj.starts_display = grList.getDisplayValue('starts');\n    obj.ends_display = grList.getDisplayValue('ends');\n    obj.starts_value = grList.getValue('starts');\n    obj.ends_value = grList.getValue('ends');\n    obj.approvals = grList.getValue('approvals') === 'true' || grList.getValue('approvals') === '1';\n    obj.assignments = grList.getValue('assignments') === 'true' || grList.getValue('assignments') === '1';\n    obj.notifications = grList.getValue('notifications') === 'true' || grList.getValue('notifications') === '1';\n    obj.invitations = grList.getValue('invitations') === 'true' || grList.getValue('invitations') === '1';\n\n    obj.user_sys_id = obj.user;\n    obj.delegate_sys_id = obj.delegate;\n\n    data.delegates.push(obj);\n  }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Mouse Effect/README.md",
    "content": "# Cursor Light Ball Widget\n\nThis ServiceNow Service Portal widget creates a light blue, glowing \"light ball\" that follows the cursor with a smooth trailing effect. The light ball is offset slightly from the cursor and has a soft glow, giving a visually appealing effect on the page.\n\n## Widget Components\n\n### HTML\nThe widget contains a single `div` element representing the light ball.\n\n### CSS\nThis contains the style for the `div` element in HTML\n\n### Script\nThis function tracks the cursor's position and updates the light ball's position accordingly with a 10px offset. The mousemove event listener enables smooth, real-time cursor tracking.\n\n## Installation\n1. Add a new widget in ServiceNow and name it, for example, \"Cursor Light Ball\".\n2. Copy the HTML, CSS, and JavaScript code provided into the respective sections of the widget.\n3. Make sure you copy the JavaScript code in the Client Script section of the widget.\n4. Save the widget, and add it to your Service Portal page to see the light ball effect."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Mouse Effect/index.html",
    "content": "<div class=\"light-ball\"></div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Mouse Effect/script.js",
    "content": "api.controller=function($scope, $element) {\n    var lightBall = $element.find('.light-ball');\n    \n    // Update the position of the light ball on mouse move\n    document.addEventListener('mousemove', function(event) {\n        var mouseX = event.pageX + 10; // Offset by 10px to the right\n        var mouseY = event.pageY + 10; // Offset by 10px to the bottom\n        \n        // Apply the position to the light ball\n        lightBall.css({\n            transform: 'translate(' + (mouseX - 30) + 'px, ' + (mouseY - 30) + 'px)'\n        });\n    });\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Mouse Effect/style.css",
    "content": "/* Light Ball Styling */\n.light-ball {\n    position: fixed;\n    top: 0;\n    left: 0;\n    width: 60px;\n    height: 60px;\n    background-color: rgba(173, 216, 230, 0.8); /* Light blue with slight transparency */\n    border-radius: 50%;\n    filter: blur(25px); /* Creates a fuzzy glow effect */\n    pointer-events: none; /* Ensures it doesn't interfere with clicking */\n    transition: transform 0.5s ease; /* 500ms latency effect */\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Assets/README.md",
    "content": "# My Assets Widget\n\n## Overview\nDisplays assets assigned to the logged-in user in a clean, responsive table.  \nData is fetched from the `alm_asset` table using a secure server script.\n\n## Files\n- **HTML** – Defines the widget layout and table structure  \n- **Server Script** – Retrieves user-specific assets from `alm_asset`  \n- **CSS** – Adds modern, responsive styling\n\n## Features\n- Responsive table layout  \n- Record count badge  \n- Hover and gradient effects  \n- Empty state message  \n\n## Usage\n1. Navigate to **Service Portal > Widgets** in ServiceNow.  \n2. Create a new widget and paste the HTML, Server Script, Client Script, and CSS.  \n3. Save the widget and add it to your desired portal page.  \n4. The widget automatically displays assets assigned to the logged-in user.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Assets/my_assets.css",
    "content": ".my-assets-widget {\n  border-radius: 12px;\n  box-shadow: 0 2px 10px rgba(0,0,0,0.08);\n  overflow: hidden;\n}\n\n.my-assets-widget .panel-heading {\n  background: linear-gradient(90deg, #0078d4, #005fa3);\n  color: #fff;\n  padding: 12px 16px;\n  font-weight: 500;\n}\n\n.my-assets-widget .panel-heading .badge {\n  background-color: #fff;\n  color: #005fa3;\n  font-weight: 600;\n}\n\n.my-assets-widget .table {\n  margin-bottom: 0;\n}\n\n.my-assets-widget .table-hover tbody tr:hover {\n  background-color: #f2f8ff;\n  cursor: pointer;\n}\n\n.my-assets-widget .asset-link {\n  font-weight: 500;\n  color: #0078d4;\n  text-decoration: none;\n}\n\n.my-assets-widget .asset-link:hover {\n  text-decoration: underline;\n}\n\n.my-assets-widget .panel-body.text-center {\n  padding: 30px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Assets/my_assets.html",
    "content": "<div class=\"panel panel-default my-assets-widget\">\n  <div class=\"panel-heading d-flex justify-content-between align-items-center\">\n    <h4 class=\"panel-title mb-0\">\n      <i class=\"fa fa-desktop text-primary\"></i> My Assets\n    </h4>\n    <span class=\"badge badge-primary\">{{data.recordCount}} Asset(s)</span>\n  </div>\n\n  <div class=\"panel-body\" ng-if=\"data.assets.length > 0\">\n    <div class=\"table-responsive\">\n      <table class=\"table table-hover table-striped\">\n        <thead class=\"thead-dark\">\n          <tr>\n            <th>Asset Name</th>\n            <th>Assigned To</th>\n            <th>Action</th>\n          </tr>\n        </thead>\n        <tbody>\n          <tr ng-repeat=\"asset in data.assets\">\n            <td>\n              <i class=\"fa fa-laptop text-secondary\"></i>\n              <a href=\"nav_to.do?uri=alm_hardware.do?sys_id={{asset.sysid}}\" \n                 class=\"asset-link\">\n                 {{asset.display}}\n              </a>\n            </td>\n            <td>{{asset.assigned_to}}</td>\n            <td>\n              <a href=\"nav_to.do?uri=alm_hardware.do?sys_id={{asset.sysid}}\" \n                 class=\"btn btn-sm btn-outline-primary\">\n                 <i class=\"fa fa-eye\"></i> View\n              </a>\n            </td>\n          </tr>\n        </tbody>\n      </table>\n    </div>\n  </div>\n\n  <div class=\"panel-body text-center text-muted\" ng-if=\"data.assets.length == 0\">\n    <i class=\"fa fa-info-circle fa-2x mb-2\"></i>\n    <p>No assets assigned to you.</p>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Assets/my_assets_server_side.js",
    "content": "(function() {\n  data.userID = gs.getUserID();\n  data.assets = [];\n\n  var gr = new GlideRecordSecure('alm_asset');\n  gr.addQuery('assigned_to', gs.getUserID());\n  gr.orderBy('display_name');\n  gr.query();\n\n  data.recordCount = gr.getRowCount();\n\n  while (gr.next()) {\n    data.assets.push({\n      display: gr.getDisplayValue('display_name'),\n      assigned_to: gr.getDisplayValue('assigned_to'),\n      sysid: gr.getUniqueValue()\n    });\n  }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Mentioned Items/CSS.js",
    "content": "/*\nlist css to show border-bottom and padding.\n*/\nli{\n  padding: 1.2rem 0;\n  border-bottom: .1rem solid #DADDE2;\n  list-style:none;\n}\n\n/*\nset background color of widget to white.\n*/\n.main-cont{\n  background:#ffffff;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Mentioned Items/HTML.js",
    "content": "<div class=\"main-cont\">\n  <div class=\"panel-heading b-b\">\n    <h2 class=\"panel-title ng-binding\">${My Mentions}</h2>\n  </div>\n  <div>\n    <div class=\"page-container\">\n      <ul>\n        <li ng-repeat=\"item in data.mentionArr\">\n          <span>${You have been mentioned in }</span><a href = {{item.url}} target=\"_blank\">{{item.record}}</a> <span>${by }{{item.user_from}}</span>\n        </li>\n      </ul>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Mentioned Items/README.md",
    "content": "**How to use**\n1. Add this widget to portal homepage or any other page.\n2. It will display the top 5 records where user is mentioned in.\n3. It will also display the user who has mentioned the logged in user.\n\n**Use Case**\n1. User will receive the mentioned items on portal homepage.\n2. User can directly go to the record and reply.\n3. The link will land the user on tickets page.\n\n<img width=\"338\" height=\"957\" alt=\"my mentions\" src=\"https://github.com/user-attachments/assets/68499e33-8d57-4c08-8228-b152203fbf4e\" />\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Mentioned Items/server script.js",
    "content": "(function() {\n    /*\n    This code will display the records wher user is mentioned in (@user in Jurnal fields).\n    This will also provide the link to record.\n    Only top 5 mentions will be displayed.\n    */\n    data.mentionArr = []; // array to store mentions.\n    var mentionRec = new GlideRecord('live_notification');\n    mentionRec.addEncodedQuery('user=' + gs.getUserID()); // get only logged-in user's records\n    mentionRec.orderBy('sys_created_on'); // get by created date.\n    mentionRec.setLimit(5);\n    mentionRec.query();\n    while (mentionRec.next()) {\n        tempval = {}; // temp object.\n        tempval.record = mentionRec.getValue('title');\n        tempval.user = mentionRec.user.name.toString();\n        tempval.user_from = mentionRec.user_from.name.toString();\n        tempval.url = '/' + $sp.getValue('url_suffix') + '?id=ticket&sys_id=' + mentionRec.getValue('document') + '&table=' + mentionRec.getValue('table');\n        data.mentionArr.push(tempval);\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Reminders/README.md",
    "content": "# Service Portal Reminder Widget\n\nA simple custom ServiceNow Service Portal widget for viewing and creating personal reminders.\n\n## Features\n\n-   This uses **ServiceNow's OOB Reminder table**. Table name : 'reminder'\n-   Displays a list of reminders for the current user.\n-   Provides a form to create new reminders.\n-   Allows associating reminders with any Task record.\n-   Auto-refreshes the list after a new reminder is created.\n\n\n## How to create\n\n1.  Navigate to **Service Portal > Widgets**.\n2.  Click **Create a new widget**.\n3.  Set the **Widget Name** (e.g. 'My Reminders') and **ID** (e.g., 'reminder-widget').\n4.  Copy and paste the provided HTML, CSS, Client Script, and Server Script into their respective tabs.\n5.  Save the widget.\n\n## How to Use\n\n1.  Open your target portal page in the Service Portal Designer.\n2.  Find your widget in the \"Widgets\" filter on the left.\n3.  Drag and dropp the widget onto the page.\n\n## Screenshots\n<img width=\"1831\" height=\"396\" alt=\"image\" src=\"https://github.com/user-attachments/assets/bdb124a8-9634-4884-8d4b-cfb79225f07e\" />\n<img width=\"1728\" height=\"731\" alt=\"image\" src=\"https://github.com/user-attachments/assets/9bef4295-0321-4806-a4e5-465d80881bdc\" />\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Reminders/reminders.html",
    "content": "<div class=\"panel panel-default\">\n  <div class=\"panel-heading\">\n    <h3 class=\"panel-title\">My Reminders</h3>\n  </div>\n  <div class=\"panel-body\">\n    <div ng-if=\"!c.data.reminders.length\" class=\"alert alert-info text-center\">\n      You have no active reminders.\n    </div>\n\n    <ul class=\"list-group\" ng-if=\"c.data.reminders.length > 0\">\n      <li class=\"list-group-item reminder-item\" ng-repeat=\"reminder in c.data.reminders\">\n        <h4>{{reminder.subject}}</h4>\n        <p class=\"text-muted\">\n          <strong>Task:</strong> {{reminder.task_display || 'N/A'}} <br/>\n          <strong>Action:</strong> Send an {{reminder.using}} {{reminder.remind_me}} minutes before {{reminder.field_display}}.\n        </p>\n        <p ng-if=\"reminder.notes\"><strong>Notes:</strong> {{reminder.notes}}</p>\n      </li>\n    </ul>\n\n    <hr/>\n\n    <div class=\"reminder-form-container\">\n      <h4>Create a New Reminder</h4>\n      <form name=\"reminderForm\" ng-submit=\"c.createReminder()\">\n        <div class=\"form-group\">\n          <label for=\"task\">Task (Optional)</label>\n          <sn-record-picker field=\"c.taskField\" table=\"'task'\" display-field=\"'number'\" value-field=\"'sys_id'\" search-fields=\"'number,short_description'\" page-size=\"10\" placeholder=\"Search for a task...\"></sn-record-picker>\n        </div>\n\n        <div class=\"form-group\">\n          <label for=\"subject\">Subject <span class=\"text-danger\">*</span></label>\n          <input type=\"text\" id=\"subject\" class=\"form-control\" ng-model=\"c.newReminder.subject\" required>\n        </div>\n\n        <div class=\"form-group\">\n          <label for=\"notes\">Notes</label>\n          <textarea id=\"notes\" class=\"form-control\" ng-model=\"c.newReminder.notes\" rows=\"3\"></textarea>\n        </div>\n\n        <div class=\"row\">\n          <div class=\"col-md-6\">\n            <div class=\"form-group\">\n              <label for=\"remind_me\">Remind Me... <span class=\"text-danger\">*</span></label>\n              <select id=\"remind_me\" class=\"form-control\" ng-model=\"c.newReminder.remind_me\" required>\n                <option value=\"\" disabled selected>-- Select Time --</option>\n                <option value=\"15\">15 Minutes</option>\n                <option value=\"30\">30 Minutes</option>\n                <option value=\"60\">1 Hour</option>\n                <option value=\"120\">2 Hours</option>\n              </select>\n            </div>\n          </div>\n          <div class=\"col-md-6\">\n            <div class=\"form-group\">\n              <label for=\"field\">...Before <span class=\"text-danger\">*</span></label>\n              <select id=\"field\" class=\"form-control\" ng-model=\"c.newReminder.field\" required>\n                <option value=\"\" disabled selected>-- Select Date Field --</option>\n                <option value=\"activity_due\">Activity Due</option>\n                <option value=\"due_date\">Due Date</option>\n                <option value=\"follow_up\">Follow Up</option>\n                <option value=\"sla_due\">SLA Due</option>\n              </select>\n            </div>\n          </div>\n        </div>\n\n        <div class=\"form-group\">\n          <label for=\"using\">Using... <span class=\"text-danger\">*</span></label>\n          <select id=\"using\" class=\"form-control\" ng-model=\"c.newReminder.using\" required>\n            <option value=\"\" disabled selected>-- Select Method --</option>\n            <option value=\"email\">Send an email</option>\n            <option value=\"outlook\">Outlook calendar invite</option>\n          </select>\n        </div>\n\n        <div class=\"form-group\">\n          <button type=\"submit\" class=\"btn btn-primary\" ng-disabled=\"reminderForm.$invalid\">\n            <i class=\"fa fa-plus-circle\"></i> Create Reminder\n          </button>\n        </div>\n      </form>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Reminders/reminders.scss",
    "content": ".reminder-item {\n  border-left: 4px solid #337ab7; \n  margin-bottom: 10px;\n  padding: 10px 15px;\n}\n\n.reminder-item h4 {\n  margin-top: 0;\n  font-weight: bold;\n  color: #333;\n}\n\n.reminder-item p {\n  margin-bottom: 5px;\n}\n\n.reminder-form-container {\n  margin-top: 20px;\n  padding-top: 15px;\n  border-top: 1px solid #eee;\n}\n\n.form-group {\n  margin-bottom: 15px;\n}\n\n.panel-heading + .panel-body {\n    padding-top: 20px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Reminders/reminders_client.js",
    "content": "function($scope) {\n  var c = this;\n\n  // Object to hold data for the new reminder form\n  c.newReminder = {};\n\n  // Special object for the sn-record-picker directive\n  c.taskField = {\n    displayValue: '',\n    value: '',\n    name: 'task'\n  };\n\n  // Function to submit the new reminder\n  c.createReminder = function() {\n    // Check if the form is valid before submitting\n    if ($scope.reminderForm.$invalid) {\n      return;\n    }\n\n    // Set the task sys_id from the record picker into our submission object\n    c.newReminder.task = c.taskField.value;\n    c.data.newReminder = c.newReminder;\n\n    // Set an action for the server to identify the request\n    c.data.action = 'create_reminder';\n\n    // Call the server script to insert the record\n    c.server.update().then(function(response) {\n      // Clear the action and the form model after successful submission\n      c.data.action = undefined;\n      c.newReminder = {};\n      c.taskField.displayValue = '';\n      c.taskField.value = '';\n\n      // Refresh the reminder list by reloading server data\n      c.server.get().then(function(response) {\n        c.data = response.data;\n      });\n    });\n  };\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/My Reminders/reminders_server.js",
    "content": "(function() {\n  \n  var currentUserId = gs.getUserID();\n  data.reminders = [];\n\n  \n  if (input && input.action === 'create_reminder') {\n    var newReminder = new GlideRecord('reminder');\n    newReminder.initialize();\n    newReminder.setValue('user', currentUserId);\n    newReminder.setValue('task', input.newReminder.task);\n    newReminder.setValue('subject', input.newReminder.subject);\n    newReminder.setValue('notes', input.newReminder.notes);\n    newReminder.setValue('remind_me', input.newReminder.remind_me);\n    newReminder.setValue('field', input.newReminder.field);\n    newReminder.setValue('using', input.newReminder.using);\n    newReminder.insert();\n  }\n\n  \n  var reminderGR = new GlideRecord('reminder');\n  reminderGR.addQuery('user', currentUserId);\n  reminderGR.orderByDesc('sys_created_on'); // Show newest first\n  reminderGR.query();\n\n  while (reminderGR.next()) {\n    var reminderObj = {};\n    reminderObj.sys_id = reminderGR.getUniqueValue();\n    reminderObj.subject = reminderGR.getValue('subject');\n    reminderObj.notes = reminderGR.getValue('notes');\n    reminderObj.remind_me = reminderGR.getValue('remind_me');\n    reminderObj.field_display = reminderGR.getDisplayValue('field'); // Get user-friendly display value\n    reminderObj.using = reminderGR.getValue('using');\n    reminderObj.task_display = reminderGR.getDisplayValue('task'); // Get task number/display value\n    data.reminders.push(reminderObj);\n  }\n\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Open in Platform/README.md",
    "content": "**This is an enhancement to the current code**\n1. Added \"open in Workspace\" Button to open the record in workspace(defined in option schema), since it is gaining popularity now.\n2. Enhanced the visibility condition so that the button is only visible on pages having sys_id and table in url.\n3. Enhanced css to improve visibility of button.\n4. This button wll look for the table to workspace mapping (in option schema) and create the URL to open record in respective workspace. If now mapping is found, the record is opened in SOW workspace(default).\n5. The button name has been changed to generic title \"Open In Workspace\"\n\n**Sample**\n{\n\"name\":\"define_workspace\",\n\"type\":\"json\",\n\"label\":\"Define Table Workspace JSON Mapping\",\n\"value\":{\n  \"sn_grc_issue\":\"risk/privacy\", // will open issue records in RISK workspace\n  \"sn_si_incident\":\"sir\" // will open security incidents in SIR workspace.\n}\n}\n\nWidget will create a button that will only be visible to users with the itil role that will take them to the same record in platform. will work with the form and standard ticket pages (or anywhere with the table and sysId in the url.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Open in Platform/body.html",
    "content": "<div>\n  <span class=\"btn-cntr\" ng-if=\"::data.role==true\">\n    <a href=\"{{::data.platform_url}}\" target='_blank'class='btn btn-default'>{{::options.open_in_platform}}</a><br> <!--Platform button configuration-->\n    <a href=\"{{::data.workspace_url}}\" target='_blank'class='btn btn-default'>{{::options.open_in_workspace}}</a>  <!--Workspace button-->\n  </span>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Open in Platform/css.js",
    "content": "/*\nThis styling will align buttons on 2 ends of the div.\n*/\n.btn-cntr{\ndisplay: flex;\njustify-content: space-between;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Open in Platform/option schema.js",
    "content": "[\n{\n\"name\":\"open_in_platform\",\n\"type\":\"string\",\n\"label\":\"Name for Plarform Button\",\n\"default_value\":\"Open in Platform\"\n},\n{\n\"name\":\"open_in_workspace\",\n\"type\":\"string\",\n\"label\":\"Name for Workspace Button\",\n\"default_value\":\"Open In workspace\"\n},\n{\n\"name\":\"define_workspace\",\n\"type\":\"json\",\n\"label\":\"Define Table Workspace JSON Mapping\",\n\"value\":{\n  \"sn_grc_issue\":\"risk/privacy\",\n  \"sn_si_incident\":\"sir\"\n}\n}\n]\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Open in Platform/server.js",
    "content": "(function() {\n    /*\n    Code will get table and sys_id parameter from url and create url for platform and workspace(defined in option schema).\n    This widget can be used in any page having sys_id and table in url , eg: ticket page.\n    */\n    data.table = $sp.getParameter(\"table\"); // get table from url\n    data.sys_id = $sp.getParameter(\"sys_id\"); // get sys_id from url\n\n    var tableWorkspaceMapping = JSON.parse(options.define_workspace); // get the table to workspace mapping from instance options.\n    Object.keys(tableWorkspaceMapping).forEach(function(key) {\n        if (key == data.table)\n            data.workspace_url = \"now/\" + tableWorkspaceMapping[key] + \"/record/\" + data.table + \"/\" + data.sys_id; // if table to workspce mapping is found, the create workspace URL.\n        else\n            data.workspace_url = \"now/sow/record/\" + data.table + \"/\" + data.sys_id; // open in SOW\n    });\n    data.platform_url = \"/nav_to.do?uri=\" + data.table + \".do?sys_id=\" + data.sys_id;\n\n    data.role = false;\n    if (gs.hasRole(\"itil\") && data.table && data.sys_id) { // only visible to users with itil role and if url has required parameters.\n        data.role = true;\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Paginated Data/README.md",
    "content": "Nuanced pagination functionality:\n\n<img width=\"370\" alt=\"image\" src=\"https://github.com/user-attachments/assets/99bc0b60-96c7-4488-93a6-eaa473daae66\">\n\npaging up displays more numbered pages\n\n<img width=\"341\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5864449f-dc17-476a-a47d-33e977263fb2\">\n\nand then shows an ... when the gaps become too large on each other side\n\n<img width=\"341\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7b9c5a92-7499-41cd-8c31-d488f8aee20e\">\n\nconfiguration is done within the server script of the widget\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Paginated Data/paginated-data-server-script.js",
    "content": "(function() {\n\t// Configuration\n\tdata.recordsTable = 'task';\n\tdata.recordsQuery = ''; // The encoded query to filter records (empty means all records)\n\tdata.recordsFields = ['number', 'short_description']; // The fields to retrieve for each record\n\tdata.recordsPerPage = parseInt($sp.getParameter('display')) || 10;\n\tdata.userQuery = $sp.getParameter('query');\n\n\t// Get pagination parameters\n\tdata.page_id = $sp.getParameter('id');\n\tdata.page = parseInt($sp.getParameter('page'), 10) || 1; // Current page number, default to 1\n\tdata.display = parseInt(data.recordsPerPage, 10) > 0 ? parseInt(data.recordsPerPage, 10) : 10; // Amount of records to display per page\n\n\t// Count total records\n\tvar countGa = new GlideAggregate(data.recordsTable);\n\tcountGa.addEncodedQuery(data.recordsQuery);\n\tcountGa.addEncodedQuery(data.userQuery);\n\tcountGa.addAggregate('COUNT');\n\tcountGa.query();\n\tif (countGa.next()) {\n\t\tdata.count = parseInt(countGa.getAggregate('COUNT'), 10);\n\t}\n\n\tdata.pages = calculatePaginationPages(data.count, data.display, data.page);\n\n\t// Adjust current page if it exceeds the total number of pages\n\tif (data.pages[data.pages.length - 1] < data.page) {\n\t\tdata.page = Math.ceil(data.count / data.display);\n\t}\n\n\t// Calculate the starting row for the current page\n\tdata.rowStart = (data.page - 1) * data.display;\n\tvar rowEnd = data.rowStart + data.display;\n\n\t// Fetch records for the current page\n\tdata.records = [];\n\tvar recordsGr = new GlideRecord(data.recordsTable);\n\trecordsGr.addEncodedQuery(data.recordsQuery);\n\trecordsGr.addEncodedQuery(data.userQuery);\n\trecordsGr.chooseWindow(data.rowStart, rowEnd);\n\trecordsGr.query();\n\twhile (recordsGr.next()) {\n\t\tvar record = {};\n\t\tfor (var i = 0; i < data.recordsFields.length; i++) {\n\t\t\tvar fieldName = data.recordsFields[i];\n\t\t\trecord[fieldName] = (recordsGr.getElement(fieldName) + \"\").trim();\n\t\t}\n\t\tdata.records.push(record);\n\t}\n\n\t/**\n\t * Calculates the page numbers to display in the pagination control\n\t * @param {number} totalRecords - Total number of records\n\t * @param {number} recordsPerPage - Number of records per page\n\t * @param {number} currentPage - Current page number\n\t * @returns {Array} An array of page numbers and ellipses to display\n\t */\n\tfunction calculatePaginationPages(totalRecords, recordsPerPage, currentPage) {\n\t\tif (recordsPerPage <= 0) {\n\t\t\treturn [];\n\t\t}\n\n\t\tvar totalPages = Math.ceil(totalRecords / recordsPerPage);\n\t\tvar pages = [];\n\t\tvar startPage, endPage;\n\n\t\t// Determine the range of pages to display\n\t\tif (totalPages <= 7) {\n\t\t\t// If 7 or fewer pages, show all\n\t\t\tstartPage = 1;\n\t\t\tendPage = totalPages;\n\t\t} else {\n\t\t\t// For more than 7 pages, use a sliding window\n\t\t\tif (currentPage < 4) {\n\t\t\t\tstartPage = 1;\n\t\t\t\tendPage = 5;\n\t\t\t} else if (currentPage === 5) {\n\t\t\t\tstartPage = 3;\n\t\t\t\tendPage = 7;\n\t\t\t} else if (currentPage + 2 >= totalPages) {\n\t\t\t\tstartPage = totalPages - 4;\n\t\t\t\tendPage = totalPages;\n\t\t\t} else {\n\t\t\t\tstartPage = currentPage - 2;\n\t\t\t\tendPage = currentPage + 2;\n\t\t\t}\n\t\t}\n\n\t\t// Add first page and ellipsis if necessary\n\t\tif (startPage > 1) {\n\t\t\tpages.push(1);\n\t\t\tif (startPage > 2) {\n\t\t\t\tpages.push('...');\n\t\t\t}\n\t\t}\n\n\t\t// Add page numbers\n\t\tfor (var i = startPage; i <= endPage; i++) {\n\t\t\tpages.push(i);\n\t\t}\n\n\t\t// Add last page and ellipsis if necessary\n\t\tif (endPage < totalPages) {\n\t\t\tif (endPage < totalPages - 1) {\n\t\t\t\tpages.push('...');\n\t\t\t}\n\t\t\tpages.push(totalPages);\n\t\t}\n\n\t\treturn pages;\n\t}\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Paginated Data/paginated-data.html",
    "content": "<div>\n  <div class=\"record-list\">\n    <div ng-repeat=\"record in data.records\">\n      <span ng-repeat=\"field in data.recordsFields\">{{record[field]}} </span>\n    </div>\n  </div>\n  <nav aria-label=\"Page navigation\" ng-if=\"data.count > 0\">\n    <ul class=\"pagination\">\n      <li class=\"page-item\">\n        <a class=\"page-link\" href=\"?id={{data.page_id}}&page={{data.page-1}}&display={{data.display}}&query={{data.userQuery}}\" aria-label=\"Previous\" ng-if=\"data.page > 1\">\n          <span aria-hidden=\"true\">&laquo;</span>\n          <span class=\"sr-only\">Previous</span>\n        </a>\n        <a class=\"page-link disabled\" aria-label=\"Previous\" ng-if=\"data.page <= 1\">\n          <span aria-hidden=\"true\">&laquo;</span>\n          <span class=\"sr-only\">Previous</span>\n        </a>\n      </li>\n      <li class=\"page-item\" ng-repeat=\"page in data.pages track by $index\" ng-class=\"{active: page == data.page}\"><a class=\"page-link\" href=\"?id={{data.page_id}}&page={{page}}&display={{data.display}}&query={{data.userQuery}}\" ng-if=\"page != '...'\">{{page}}</a><span ng-if=\"page == '...'\">...</span></li>\n      <li class=\"page-item\">\n        <a class=\"page-link\" href=\"?id={{data.page_id}}&page={{data.page+1}}&display={{data.display}}&query={{data.userQuery}}\" aria-label=\"Next\" ng-if=\"data.page < data.pages[data.pages.length - 1]\">\n          <span aria-hidden=\"true\">&raquo;</span>\n          <span class=\"sr-only\">Next</span>\n        </a>\n        <a class=\"page-link disabled\" aria-label=\"Next\" ng-if=\"data.page >= data.pages[data.pages.length - 1]\">\n          <span aria-hidden=\"true\">&raquo;</span>\n          <span class=\"sr-only\">Next</span>\n        </a>\n      </li>\n    </ul>\n  </nav>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Pagination widget with next and previous button/CSS.css",
    "content": ".pageNum {\n  font-size: 16px;\n}\n.btngroupStyle {\n  margin-top: -0px;\n}\ntable {\n  width: 100%;\n  word-wrap: break-word;\n  table-layout: fixed;\n  border-collapse: separate;\n  border-spacing: 0;\n}\n\n.th {\n  font-size: 16px;\n  font-weight: 550;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Pagination widget with next and previous button/HTML_Script.html",
    "content": "<div>\n  <!-- your widget template -->\n  <!--TABLE TO DISPLAY INCIDENT TABLE RECORDS-->\n  <table class=\"table table-striped item-table\">\n\n    <thead class=\"fixed-listheader\">\n      <tr>\n        <th scope=\"col\" colspan=\"2\">${Number}</th>\n        <th scope=\"col\" colspan=\"2\">\n          ${State}</th>\n        <th scope=\"col\" colspan=\"3\">${Short Description}</th>\n        <th scope=\"col\" colspan=\"2\">\n          ${Priority}</th>\n        <th scope=\"col\" colspan='3'>\n          ${Assignment Group}</th>\n        <th scope=\"col\" colspan='3'>\n          ${Assigned to}</th>\n      </tr>\n    </thead>\n    <tbody>\n      <tr ng-repeat=\"item in displayData | orderBy:number\" ng-if=\"!loading\">\n\n        <td headers=\"id-header-item\" scope=\"row\" colspan=\"2\">\n\n          <a ng-href=\"{{::item.link}}\">\n            <div>\n              <span>{{::item.number}}</span>\n            </div>\n          </a>\n        </td>\n        <td headers=\"id-header-description id-item-{{item.sys_id}}\" class=\"catalog-text-wrap\"\n            colspan=\"2\">\n          {{::item.state}}</td>\n        <td headers=\"id-header-description id-item-{{item.sys_id}}\" class=\"catalog-text-wrap\"\n            colspan=\"3\">\n          {{::item.short_description}}</td>\n        <td headers=\"id-header-description id-item-{{item.sys_id}}\" class=\"catalog-text-wrap\"\n            colspan=\"2\" >\n          {{::item.priority}}</td>\n        <td headers=\"id-header-description id-item-{{item.sys_id}}\" class=\"catalog-text-wrap\"\n            colspan=\"3\" ng-if=\"data.task_table == 'cmdb_ci_outage'\">\n          {{::item.duration}}</td>\n        <td headers=\"id-header-description id-item-{{item.sys_id}}\" class=\"catalog-text-wrap\"\n            colspan=\"3\" >\n          {{::item.assignment_group}}</td>\n        <td headers=\"id-header-description id-item-{{item.sys_id}}\" class=\"catalog-text-wrap\"\n            colspan=\"2\">\n          {{::item.assigned_to}}</td>\n      </tr>\n      <tr ng-if=\"displayData.length==0\">\n        <td colspan='15' class=\"text-center\">No Records</td>\n      </tr>\n    </tbody>\n  </table>\n\n  <!--PAGINATION-->\n  <div class=\"text-center\">\n    <button class=\"btn btn1 btn-primary\" ng-disabled=\"currentPage == 0\"\n            ng-click=\"previousPage(currentPage-1)\">\n      Previous\n    </button>\n    <span class=\"pageNum\">{{currentPage+1}} of {{numberOfPages}}</span>\n    <button class=\"btn btn1 btn-primary\" ng-disabled=\"currentPage >= numberOfPages - 1\"\n            ng-click=\"pageChange(currentPage)\">\n      Next\n    </button>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Pagination widget with next and previous button/README.md",
    "content": "This widget displays a paginated table of incident records using Next and Previous buttons to navigate between pages. The widget is built with AngularJS and integrates with ServiceNow GlideRecordSecure to fetch active and In progress incident records.\n\nFeatures:\n\nDisplays Incident Records: The widget retrieves and displays active and In progress incident records from the ServiceNow incident table, including fields such as:\no Number\no State\no Short Description\no Priority\no Assignment Group\no Assigned To\nPagination:\no The table is paginated to show 5 records per page.\no Users can navigate through pages using Next and Previous buttons.\nFiles/Code Overview:\n\nHTML Template (Table and Pagination UI):\n• The HTML template uses an AngularJS directive ng-repeat to loop through displayData, which contains a subset of the incident records for the current page.\n• A table is built with the following columns:\no Number, State, Short Description, Priority, Assignment Group, Assigned To.\n• Pagination Controls:\no Previous Button: Decrements the currentPage and updates the table content.\no Next Button: Increments the currentPage and updates the table content.\n• No Records: Displays a message when there are no records in displayData.\n\nCSS Styles:\n• .pageNum: Defines the styling for the page number display.\n• .btngroupStyle: Minor adjustments to button margin.\n• table: Ensures the table has a fixed layout and word wrapping to handle long text.\n• .th: Defines the styling for the table header.\n\nAngularJS Controller (api.controller):\n• $scope.currentPage: Tracks the current page number.\n• $scope.pageSize: Defines the number of records per page (set to 5).\n• $scope.numberOfPages: Calculates the total number of pages based on the length of the tableRecord array.\n• $scope.displayData: Contains the records to be displayed on the current page. It is updated whenever the user changes pages.\n• Pagination Functions:\no pageChange(): This function updates the data shown when the user clicks the Next button. It calculates the range of records to display based on the current page index.\no previousPage(): This function updates the data shown when the user clicks the Previous button. It recalculates the range of records for the previous page.\n\nServer-Side Script:\n• Uses a GlideRecord query to retrieve incident records from the incident table where the state is \"active\" (state=2).\n• The data fetched includes:\no Number, Short Description, Priority, State, Assignment Group, and Assigned To.\n• Each record's sys_id is used to generate a clickable link for each incident in the table (sp?id=form&table=incident&sys_id=).\n• The fetched data is stored in the data.tableRecord array.\n\nHow Pagination Works:\n• Initial Page Load: When the widget is first loaded, it fetches all active incidents and displays the first 5 records.\n• Next Button: When the user clicks the Next button, the page index is incremented by 1. The records from the next page are sliced from the tableRecord array and displayed in the table.\n• Previous Button: When the user clicks the Previous button, the page index is decremented by 1. The records from the previous page are sliced and displayed.\n\nError Handling:\n• If an error occurs during the GlideRecord query, an error message is captured and displayed using gs.addErrorMessage().\n\nCustomization:\n• Change Page Size: Modify $scope.pageSize in the controller to change how many records are displayed per page.\n• Alter Displayed Fields: Adjust the fields fetched from GlideRecord (like adding more fields or removing existing ones).\n• Styling: Modify the CSS styles to customize the table layout, fonts, or button appearance.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Pagination widget with next and previous button/client_controller.js",
    "content": "api.controller = function ($scope) {\n  /* widget controller */\n  var c = this;\n  $scope.currentPage = 0;\n  $scope.pageSize = 5;\n  $scope.numberOfPages = Math.ceil(c.data.tableRecord.length / $scope.pageSize);\n  $scope.displayData = c.data.tableRecord.slice(0, 5);\n  /*Pagination Next Button Function*/\n  $scope.pageChange = function (index) {\n    var begin = (index + 1) * $scope.pageSize;\n    var end = begin + $scope.pageSize;\n    $scope.displayData = c.data.tableRecord.slice(begin, end);\n    $scope.currentPage += 1;\n  };\n\n  /*Pagination Previous Button Function*/\n  $scope.previousPage = function (index) {\n    var begin = index * $scope.pageSize;\n    var end = begin + $scope.pageSize;\n    $scope.displayData = c.data.tableRecord.slice(begin, end);\n    $scope.currentPage -= 1;\n  };\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Pagination widget with next and previous button/server_script.js",
    "content": "(function () {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n  /*PAGINATION*/\n  try {\n    data.tableRecord = [];\n    var getIncRec = new GlideRecordSecure(\"incident\");\n    getIncRec.addEncodedQuery(\"active=true^state=2\");\n    getIncRec.query();\n    while (getIncRec.next()) {\n      var obj = {};\n      obj.number = getIncRec.getDisplayValue(\"number\");\n      obj.short_description = getIncRec.getDisplayValue(\"short_description\");\n      obj.priority = getIncRec.getDisplayValue(\"priority\");\n      obj.state = getIncRec.getDisplayValue(\"state\");\n      obj.assignment_group = getIncRec.getDisplayValue(\"assignment_group\");\n      obj.assigned_to = getIncRec.getDisplayValue(\"assigned_to\");\n      obj.priority = getIncRec.getDisplayValue(\"priority\");\n      obj.link =\n        \"sp?id=form&table=incident&sys_id=\" + getIncRec.getUniqueValue();\n      data.tableRecord.push(obj);\n    }\n  } catch (e) {\n    gs.addErrorMessage(\"Error Catched\" + e);\n  }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Portal widgets Performance Test/README.md",
    "content": "This code is helpful to check the Service Portal widget Performance. This code will show the time taken by the widgets to load.\n\nSteps to Use the Code:\n\nStep 1: Go to the Portal Page\n\nStep 2: Right Click on page and open Inspect Element\n\nStep 3: Open Console Tab\n\nStep 4: Copy and Past the code\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Portal widgets Performance Test/code.js",
    "content": "(async () => {\n  let threshold = 1000; // only show load times for widgets higher than this value (in milliseconds)\n  var wa = $(\"div [widget='widget']\").css(\"border\", \"1px solid red\").css(\"padding-top\", \"20px\").css(\"position\", \"relative\")\n  let widgetTable = [];\n  let wmk = [];\n  for (var i = 0; i < wa.length; i++) {\n    let widgetEntry = {};\n    $0 = wa[i]\n    let scope = $($0).scope();\n    try{\n      var widget = scope.widget;\n    } catch(e){\n      console.error(e);\n      continue;\n    }\n    var timing = 0;\n    let elem = $(\"<div style='position: absolute; top: 1px; left: 1px'><a target='_blank' href='/$sp.do?id=widget_editor&sys_id=\" + widget.sys_id + \"'> \" + widget.name + \"</a>&nbsp;&nbsp;</div>\");\n    var printScope = $(\"<a href='javascript:void(0);'>Print scope </a>\").on('click', function() {\n      console.info(scope);\n    });\n    elem.append(printScope);\n    widgetEntry.name = widget.name;\n    widgetEntry.rectangle = widget.rectangle_id || 'undefined';\n    widgetEntry.sys_id = widget.sys_id;\n    let id = scope.widget.rectangle_id + \"_\" + scope.$id\n\n    // if this is not a nested widget, go ahead and refresh it.\n    if (!scope.$parent || !scope.$parent.widget) {\n      var widget_name = widget.name;\n      var t0 = performance.now();\n      await scope.server.refresh();\n      var t1 = performance.now();\n      timing = \"<div style='float:right;color:red;' id='\" + id + \"'> Load Time: \" + parseInt(t1 - t0) + \" ms.</div>\";\n      widgetEntry.load_time_ms = parseInt(t1 - t0) || 0;\n      elem.append(timing);\n    }\n    // add a button to refresh manually.\n    var loadTime = $(\"<button style='border-radius: 50%;'> ⟳ </button>\").on('click', function() {\n      var widget_name = scope.widget.name;\n      let id = scope.widget.rectangle_id + \"_\" + scope.$id\n      var t0 = performance.now();\n      scope.server.refresh().then(() => {\n        var t1 = performance.now();\n        timing = \"<div style='float:right;color:red;' id='\" + id + \"'> Load Time: \" + parseInt(t1 - t0) + \" ms.</div>\";\n        widgetEntry.load_time_ms = parseInt(t1 - t0) || 0;\n        if ($('#' + id)) {\n          $('#' + id).remove();\n        }\n\n        elem.append(timing);\n        console.log(\"Call to \" + widget_name + \" took \" + (t1 - t0) + \" ms.\");\n      });\n    });\n\n    elem.append(loadTime);\n    $($0).append(elem);\n    widgetTable.push(widgetEntry);\n  }\n  widgetTable.sort((a, b) => (a.load_time_ms > b.load_time_ms) ? 1 : -1);\n  var slow = widgetTable.filter((e, i, w) => {\n    return e.load_time_ms >= threshold;\n  });\n\n  let mkdn = `|Name|sys_id|rectangle_id|Load Time Ms|\n|:---:|:---:|:---:|:---:|\n${slow.map((widgetEntry, i, widgetTable) => {\nreturn `|${widgetEntry.name}|${widgetEntry.sys_id}|${widgetEntry.rectangle}|${widgetEntry.load_time_ms}|`;\n}).join(\"\\n\")}`;\n  var doCopy = function() {\n    var el = document.createElement('textarea');\n    el.value = mkdn;\n    el.setAttribute('readonly', '');\n    el.style = {\n      position: 'absolute',\n      left: '-9999px'\n    };\n    document.body.appendChild(el);\n    el.select();\n    document.execCommand('copy');\n    document.body.removeChild(el);\n  }\n  var elem = `<div style=\"float: right;\nz-index: 1;\nposition: relative;\nleft: -50%; /* or right 50% */\ntext-align: left;\npadding:10px\">\n<h2>${(slow.length > 0)?\"Slow Widgets:\" : \"No Slow Widgets Found!\"}</h2>\n<table style=\"padding:3px; display:${(slow.length > 0)?\"table\":\"none\"}\">\n<tr><td style=\"padding:3px;\" >Name</td><td style=\"padding:3px;\">sys_id</td><td style=\"padding:3px;\">rectangle id</td><td style=\"padding:3px;\">load time ms</td></tr>\n${slow.map((widgetEntry, i, widgetTable) => {\nreturn `<tr><td style=\"padding:3px;\">${widgetEntry.name}</td><td style=\"padding:3px;\">${widgetEntry.sys_id}</td><td style=\"padding:3px;\">${widgetEntry.rectangle}</td><td style=\"padding:3px;\">${widgetEntry.load_time_ms}</td></tr>`;\n}).join(\" \")}\n</table>\n<button onClick=${doCopy()} style=\"display:${(slow.length > 0)?\"table\":\"none\"}\">Copy Markdown</button>\n</div>`;\n  $('body').append(elem);\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Recently Viewed Items/README.md",
    "content": "\n# Recently Viewed Items\n\nThis is a modification to the out-of-the-box Homepage Search widget that displays the users 3 most recently viewed items under the search bar.\n\n![image](https://github.com/captainbraddles/code-snippets/blob/554df81b4d4ca9e73efd1e4368842b1d87acb425/Service%20Portal%20Widgets/Recently%20Viewed%20Items/Recently%20viewed%20widget.png)\n\n## Description\n\nThe modification to the out-of-the-box Homepage Search widget provides some additional text under the search bar the displays the logged in users 3 most recently viewed catalog items / record producers. It does not display\nduplicate items.\n\nThe list of items is obtained from the sp_log table and can be modified to suit your needs for the number of records it checks to find the 3 most recent unique items. The default is 100.\n\n## Getting Started\n\n### Dependencies\n\n* You must be in the scope of the widget you are modifying.\n\n### Execution\n\n1. Clone the out-of-the-box Homepage Search widget.\n2. Add the HTML from the recently-viewed-items-widget.js file to your cloned widget under the <sp-widget widget=\"data.typeAheadSearch\"></sp-widget> line.\n3. Update the <PORTAL> components of the href tags with your portal suffix.\n4. Add the CSS from the recently-viewed-items-widget.js file to your cloned widget CSS section.\n5. Add the Server Script from the recently-viewed-items-widget.js file to your cloned widget Server Script section.\n6. Save the widget and add it to your portal page.\n\n### Custom Configurations\nYou can modify the CSS values to change the style of the text that is displayed under the search bar.\n\n## Authors\n\nBrad Warman\n\nhttps://www.servicenow.com/community/user/viewprofilepage/user-id/80167\n\n## Version History\n\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Recently Viewed Items/recently-viewed-items-widget.js",
    "content": "<!-- ADD THE BELOW TO THE HTML, SERVER SCRIPT AND CSS FIELDS OF THE WIDGET YOU ARE USING TO DISPLAY THE SEARCH BAR IN THE SERVICE PORTAL / EMPLOYEE CENTER (DEFAULT IS HOMEPAGE SEARCH). YOU MAY NEED TO CLONE THE OUT OF THE BOX WIDGET TO MAKE CHANGES -->\n\n<!--HTML - ADD THIS UNDER THE '<sp-widget widget=\"data.typeAheadSearch\"></sp-widget>' LINE IN THE WIDGET. DONT FORGET TO REPLACE THE <PORTAL> PARTS OF THE HREF WITH YOUR PORTAL SUFFIX-->\n\n<div class=\"recently-viewed\" ng-if=\"data.items.length == 1\">\n    <strong>Recently viewed: &nbsp</strong><a\n        href=\"../<PORTAL>?id=sc_cat_item&sys_id={{data.items[0].sys_id}}\">{{data.items[0].name}}</a> <!-- Replace <PORTAL> with your portal suffix-->\n</div>\n<div class=\"recently-viewed\" ng-if=\"data.items.length == 2\">\n    <strong>Recently viewed: &nbsp</strong><a\n        href=\"../<PORTAL>?id=sc_cat_item&sys_id={{data.items[0].sys_id}}\">{{data.items[0].name}}</a> &nbsp &nbsp|&nbsp &nbsp <!-- Replace <PORTAL> with your portal suffix-->\n    <a href=\"../<PORTAL>?id=sc_cat_item&sys_id={{data.items[1].sys_id}}\">{{data.items[1].name}}</a> <!-- Replace <PORTAL> with your portal suffix-->\n</div>\n<div class=\"recently-viewed\" ng-if=\"data.items.length > 2\">\n    <strong>Recently viewed: &nbsp</strong><a\n        href=\"../<PORTAL>?id=sc_cat_item&sys_id={{data.items[0].sys_id}}\">{{data.items[0].name}}</a> &nbsp &nbsp|&nbsp &nbsp <!-- Replace <PORTAL> with your portal suffix-->\n    <a href=\"../<PORTAL>?id=sc_cat_item&sys_id={{data.items[1].sys_id}}\">{{data.items[1].name}}</a> &nbsp &nbsp|&nbsp &nbsp <!-- Replace <PORTAL> with your portal suffix-->\n    <a href=\"../<PORTAL>?id=sc_cat_item&sys_id={{data.items[2].sys_id}}\">{{data.items[2].name}}</a> <!-- Replace <PORTAL> with your portal suffix-->\n</div>\n\n\n<!--CSS-->\n\n.recently-viewed {\n    color: white;\n    margin:10px 0px 10px 10px;\n    width: max-content;\n  }\n  \n  .recently-viewed a {\n      color: white;\n      text-decoration: none;\n  }\n\n\n<!--SERVER SCRIPT-->\n\n// Get the logged in users last 100 recent items.\nvar itemsArray = [];\nvar viewed = new GlideRecord('sp_log');\nviewed.addEncodedQuery('userDYNAMIC90d1921e5f510100a9ad2572f2b477fe^type=Cat Item View'); // Encoded query uses the 'user is me' dynamic filter\nviewed.orderByDesc('sys_created_on');\nviewed.setLimit(100);\nviewed.query();\nwhile (viewed.next()) {\n\titemsArray.push(viewed.id.toString());\n}\n\n// Remove duplicate items from array\nvar arr = [];\nvar arrayUtil = new global.ArrayUtil();\nitemsArray = arrayUtil.unique(itemsArray);\nfor (var i = 0; i < itemsArray.length; i++) {\n     arr.push(itemsArray[i]);\n}\n\n// Look up the details of the first 3 items in array\nvar returnedArray = [];\nfor (var j = 0; j < 3; j++) {\n\tvar obj = {};\n\tvar itemLookup = new GlideRecord('sc_cat_item');\n\titemLookup.addEncodedQuery('sys_id=' + arr[j]);\n\titemLookup.query();\n\tif (itemLookup.next()) {\n\t\tobj.name = itemLookup.name.toString();\n\t\tobj.sys_id = itemLookup.sys_id.toString();\n\t\treturnedArray.push(obj);\n\t}\n}\n\ndata.items = returnedArray;\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Record process flow/PortalUtils.js",
    "content": "var PortalUtils = Class.create();\nPortalUtils.prototype = Object.extendsObject(PortalUtilsBase, {\n  initialize: function() {\n  },\n  type: 'PortalUtils'\n});"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Record process flow/PortalUtilsBase.js",
    "content": "var PortalUtilsBase = Class.create();\nPortalUtilsBase.prototype = {\n\tinitialize: function() {\n  },\n\ngetProcessFlows: function(data,table,newRecord, grRecord){\n\t\tdata.processFlow = {show:false, items:[]};\n\t\t\n\t\tvar grProcessStates = new GlideRecord('sys_process_flow');\n\t\tgrProcessStates.addQuery(\"table\", table);\n\t\tgrProcessStates.addQuery(\"active\",true);\n\t\tgrProcessStates.orderByDesc('order');\n\t\tgrProcessStates.query();\n\t\t\n\t\tvar matchingFound = false;\n\t\t\n\t\twhile(grProcessStates.next()) {\n\t\t\tdata.processFlow.show = true;\n\t\t\t\n\t\t\tvar item = {};\n\t\t\t\n\t\t\titem.label = grProcessStates.getValue(\"label\");\n\t\t\t\n\t\t\tif(newRecord){\n\t\t\t\titem.class_name = \"disabled\";\n\t\t\t}else if(GlideFilter.checkRecord(grRecord,grProcessStates.getValue(\"condition\"))){\n\t\t\t\titem.class_name = \"completed active\";\n\t\t\t\tmatchingFound = true;\n\t\t\t}else{\n\t\t\t\tif(matchingFound)\n\t\t\t\t\titem.class_name = \"completed active\";\n\t\t\t\telse\n\t\t\t\t\titem.class_name = \"disabled\";\n\t\t\t}\n\t\t\t\n\t\t\tdata.processFlow.items.unshift(item);\n\t\t}\n\t\t\n\t\tif(newRecord && data.processFlow.show && data.processFlow.items.length > 0){\n\t\t\tdata.processFlow.items[0].class_name = \"active\";\n\t\t}\n \t},\n\n  \ttype: 'PortalUtilsBase'\n};"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Record process flow/README.md",
    "content": "# Creating Process flow in service portal\n## Introduction\nIn this snippet you will create a custom process flow with a custom page and populated with standard widgets.\n\n## Step 1: Create a new Widget\n***Go to Service Portal > Widget > Click New***\n- Name: Process Flow\n- Id: process-flow\n- Click on `submit`\n\n***Body HTML template***\n- Copy and paste below `HTML Code` in Widget's HTML Template section\n```HTML\n<div class=\"container\">\n  <div class=\"process\">\n    <div class=\"process-row\">\n      <div class=\"process-step\" ng-repeat=\"stateItem in data.processFlow.items\">\n        <button type=\"button\" disabled=\"disabled\" class=\"btn btn-default btn-circle\" ng-if=\"c.data.currentValue!=stateItem\"><i class=\"fa fa-check fa-3x\" aria-hidden=\"true\"></i></button>\n        <button type=\"button\" disabled=\"disabled\" class=\"btn btn-success btn-circle\" ng-if=\"c.data.currentValue==stateItem\"><i class=\"fa fa-check fa-3x\" aria-hidden=\"true\"></i></button>  \n        <p>{{stateItem.label}}</p>       \t\n      </div>\n    </div>\n  </div>\n </div>\n```\n\n***CSS/SCSS***\n- Copy and paste below `CSS` in Widget's CSS/SCSS Section\n```CSS\n.btn-circle {\n  width: 40px;\n  height: 40px;\n  text-align: center;\n  padding: 6% 0;\n  font-size: 6px;\n  line-height: 0.6;\n  border-radius: 100%;\n}\n\n.process-row {\n    display: table-row;\n}\n\n.process {\n    display: table;     \n    width: 100%;\n    position: relative;\n}\n\n.process-step button[disabled] {\n    opacity: 1 !important;\n    filter: alpha(opacity=100) !important;\n}\n\n.process-row:before {\n    top: 20px;\n    bottom: 0;\n    position: absolute;\n    content: \" \";\n    width: 100%;\n    height: 1px;\n    background-color: #ccc;\n    z-order: 0;\n    \n}\n\n.process-step {    \n    display: table-cell;\n    text-align: center;\n    position: relative;\n    padding-left: 0%;\n    padding-right: 5%;\n}\n\n.process-step p {\n    margin-top:10px;\n    \n}\n\n.btn-circle.active {\n    border: 2px solid #cc0;\n}\n\n```\n\n***Server Side Scripts***\n- Copy and Paste below `Server-Side Script` in Widget's Server Side Section\n```javascript\n(function() {\n  /* populate the 'data' object */\n  /* e.g., data.table = $sp.getValue('table'); */\n\n\tdata.table  = $sp.getParameter(\"table\");\n\tdata.sys_id = $sp.getParameter(\"sys_id\");\n\n\tvar gr = new GlideRecord(data.table);\n  gr.get(data.sys_id);\n  \n\tvar spUtils = new PortalUtils();\n  spUtils.getProcessFlows(data,data.table,(data.sys_id == -1),gr);\n  \n```\n\n## Step 2: Create a Script Include\n***Go to Script Includes (System Definition) > Click New***\n- Name: PortalUtils\n- Accessible from: This application scope only\n- Copy and Paste below Server-Side Script in Script Section:\n```javascript\n\n  var PortalUtils = Class.create();\n  PortalUtils.prototype = Object.extendsObject(PortalUtilsBase, {\n\tinitialize: function() {\n\t},\n\ttype: 'PortalUtils'\n});\n```\n- Click on `Submit` button.\n\n***Go to Script Includes (System Definition) > Click New***\n- Name: PortalUtilsBase\n- Accessible from: This application scope only\n- Copy and Paste below Server-Side Script in Script Section:\n```javascript\nvar PortalUtilsBase = Class.create();\nPortalUtilsBase.prototype = {\n\tinitialize: function() {\n  },\n\ngetProcessFlows: function(data,table,newRecord, grRecord){\n\t\tdata.processFlow = {show:false, items:[]};\n\t\t\n\t\tvar grProcessStates = new GlideRecord('sys_process_flow');\n\t\tgrProcessStates.addQuery(\"table\", table);\n\t\tgrProcessStates.addQuery(\"active\",true);\n\t\tgrProcessStates.orderByDesc('order');\n\t\tgrProcessStates.query();\n\t\t\n\t\tvar matchingFound = false;\n\t\t\n\t\twhile(grProcessStates.next()) {\n\t\t\tdata.processFlow.show = true;\n\t\t\t\n\t\t\tvar item = {};\n\t\t\t\n\t\t\titem.label = grProcessStates.getValue(\"label\");\n\t\t\t\n\t\t\tif(newRecord){\n\t\t\t\titem.class_name = \"disabled\";\n\t\t\t}else if(GlideFilter.checkRecord(grRecord,grProcessStates.getValue(\"condition\"))){\n\t\t\t\titem.class_name = \"completed active\";\n\t\t\t\tmatchingFound = true;\n\t\t\t}else{\n\t\t\t\tif(matchingFound)\n\t\t\t\t\titem.class_name = \"completed active\";\n\t\t\t\telse\n\t\t\t\t\titem.class_name = \"disabled\";\n\t\t\t}\n\t\t\t\n\t\t\tdata.processFlow.items.unshift(item);\n\t\t}\n\t\t\n\t\tif(newRecord && data.processFlow.show && data.processFlow.items.length > 0){\n\t\t\tdata.processFlow.items[0].class_name = \"active\";\n\t\t}\n \t},\n\n  \ttype: 'PortalUtilsBase'\n};\n```\n- Click on `Submit` button.\n\n## Step 3: Create a new Page\n***Go to Service Portal > Page > Click New***\n- Name: process_flow - Test Page\n- ID: process_flow\n- Click on `Submit` button.\n- Once submitted, Click on `Open in Page Designer` related link\n- In Page designer, Place `Process Flow` widget inside a container > row > Column at top location.\n- View paget from following link `http://instance-name.service-now.com/sp?id=process_flow`. "
  },
  {
    "path": "Modern Development/Service Portal Widgets/Record process flow/body.html",
    "content": "<div class=\"container\">\n    <div class=\"process\">\n      <div class=\"process-row\">\n        <div class=\"process-step\" ng-repeat=\"stateItem in data.processFlow.items\">\n          <button type=\"button\" disabled=\"disabled\" class=\"btn btn-default btn-circle\" ng-if=\"c.data.currentValue!=stateItem\"><i class=\"fa fa-check fa-3x\" aria-hidden=\"true\"></i></button>\n          <button type=\"button\" disabled=\"disabled\" class=\"btn btn-success btn-circle\" ng-if=\"c.data.currentValue==stateItem\"><i class=\"fa fa-check fa-3x\" aria-hidden=\"true\"></i></button>  \n          <p>{{stateItem.label}}</p>       \t\n        </div>\n      </div>\n    </div>\n   </div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Record process flow/server.js",
    "content": "(function() {\n    /* populate the 'data' object */\n    /* e.g., data.table = $sp.getValue('table'); */\n  \n      data.table  = $sp.getParameter(\"table\");\n      data.sys_id = $sp.getParameter(\"sys_id\");\n  \n      var gr = new GlideRecord(data.table);\n    gr.get(data.sys_id);\n    \n      var spUtils = new PortalUtils();\n    spUtils.getProcessFlows(data,data.table,(data.sys_id == -1),gr);"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Record process flow/style.css",
    "content": ".btn-circle {\n    width: 40px;\n    height: 40px;\n    text-align: center;\n    padding: 6% 0;\n    font-size: 6px;\n    line-height: 0.6;\n    border-radius: 100%;\n  }\n  \n  .process-row {\n      display: table-row;\n  }\n  \n  .process {\n      display: table;     \n      width: 100%;\n      position: relative;\n  }\n  \n  .process-step button[disabled] {\n      opacity: 1 !important;\n      filter: alpha(opacity=100) !important;\n  }\n  \n  .process-row:before {\n      top: 20px;\n      bottom: 0;\n      position: absolute;\n      content: \" \";\n      width: 100%;\n      height: 1px;\n      background-color: #ccc;\n      z-order: 0;\n      \n  }\n  \n  .process-step {    \n      display: table-cell;\n      text-align: center;\n      position: relative;\n      padding-left: 0%;\n      padding-right: 5%;\n  }\n  \n  .process-step p {\n      margin-top:10px;\n      \n  }\n  \n  .btn-circle.active {\n      border: 2px solid #cc0;\n  }"
  },
  {
    "path": "Modern Development/Service Portal Widgets/RecordPickerForListReference/README.md",
    "content": "The widget is an example record picker that can be used as reference or list field. The field is referring to user table and upon change, the new values are passed to the server side for processing any business logic.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/RecordPickerForListReference/recordpicker.html",
    "content": "<!-- A record picker that can select multiple records from user table\n\nfield - mapped to the $scope to pass the value\ntable - Table to be associated with list/reference field\ndisplay-field - Display column name for the field\nvalue-field - Default value column for the field\nsearch-fields - List of search columns\ndefault-query - Default reference qualifier \nmultiple - false=Reference field, true=List field\npaze-size - Number of records displayed in a page\n\n-->\n\n<div>\n   <sn-record-picker field=\"user\" table=\"'sys_user'\" display-field=\"'name'\" value-field=\"'sys_id'\" search-fields=\"'name'\"\n      default-query=\"'active=true'\" multiple = \"true\" page-size=\"100\" ></sn-record-picker>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/RecordPickerForListReference/recordpickerclient.js",
    "content": "api.controller = function($scope, spUtil) {\n    /* widget controller */\n    c = this;\n\t\n    //Map the record values\n    $scope.user = {\n        displayValue: c.data.userName,\n        value: c.data.userId,\n        name: 'user'\n    };\n\n    //Event when field value changes\n    $scope.$on(\"field.change\", function(evt, parms) {\n        if (parms.field.name == 'user') {\n            //Pass new value to server side\n            c.server.get({\n                action: 'updateuser',\n                userId: parms.newValue,\n                userName: parms.displayValue\n            }).then(function(response) {\n                //Handle any response\n            });\n        }\n    });\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/RecordPickerForListReference/recordpickerserver.js",
    "content": "(function() {\n    if (input && input.action == \"updateuser\") {\n        //ACCESS CHANGED VALUE\t\n        var changedUserName = input.userId;\n        var changedUserId = input.userName;\n        /**Add any logic here to update and return the response**/\n\t//data.response = \"\";\n        //console.log(\"userName: \" + changedUserName);\n        //console.log(\"userId: \" + changedUserId);\n    } else {\n        //DEFAULT VALUE\n        data.userName = gs.getUserDisplayName();\n        data.userId = gs.getUserID();\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Redirect to different portals based on browser/README.md",
    "content": "## Widget to Redirect to different portals based on browser\n\nThis widget can be used to redirect users to different portals based on the browsers being used. Simply drop this widget anywhere into the homepage of the portal where you want a redirection to occur(for example */sp*) and you're done. Currently it's having conditions for Internet explorer and Chrome but we can more as and for required. \n\n\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Redirect to different portals based on browser/redirect_to_different_portals_based_on_browser.js",
    "content": "[HTML] \n\n<p></p>\n\n[Server Script]\n\n(function() {\ndata.browserVersion = gs.getSession().getProperty('user_agent_browser'); //Checks with the browser that user is using if applicable does the redirect to a different portal.\n})();\n\n[Client Controller]\n\nfunction() {\n  var c = this;\n  if(c.data.browserVersion == 'ie')   //If the browsers is IE redirect to sp_classic.\n\t  {\n\t\tdocument.location = \"/sp_classic\";\n\t  }\n   if(c.data.browserVersion == 'chrome') //If the browsers is IE redirect to sp_chrome.\n\t  {\n\t\tdocument.location = \"/sp_chrome\";\n\t  }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Set Variables By Url/README.md",
    "content": "## Use case\nSometimes you want to share a link to a catalog item on the portal and you want to pre-set some variables.  Like when you make documentation and you want to pre-categorize a ticket with information but you dont show all that.\n\nSo you build your link `https://{{instance}}/sp?id=sc_cat_item&sys_id={{itemSysID}}&description=I'cant%20access%20ServiceNow`\n\nWhich would set the description variable to \"I can't access ServiceNow\"\n\n## Set up\n\n1.  Create the widget\n2.  Goto the https://{{instance}}/nav_to.do?uri=sp_page.do?sysparm_query=id=sc_cat_item\n3.  Click the \"Open in Designer\" link at the bottom\n4.  Add your widget anywhere as it has no html it will not render any elements."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Set Variables By Url/client.js",
    "content": "function($scope, $window) {\n    // This is the controller, we've included\n    // $scope in the function above because\n    // it's easy to work with\n    var c = this;\n    // We are going to simplify accessing \n    // g_form within the client script by\n    // setting it as a variable named g_form\n    var g_form = $scope.page.g_form;\n    //We are going to simplify accessing\n    // g_form within the HTML by setting\n    // it as a $scope attribute\n    $scope.g_form = $scope.page.g_form;\n    // from here you can just iterate over\n    // the url params;\n    var params = $window.location.href.split('?')[1];\n    console.log(params);\n    var paramsToString = params.toString();\n    var paramsArr = paramsToString.split('&');\n    paramsArr.map(function (keyValue) {\n        var key = keyValue.split('=')[0];\n        var value = keyValue.split(key + '=').join('');\n        value = decodeURIComponent(value);\n        try {\n            var message = 'Setting ' + key + ' to ';\n            message += value + ' from url parameter.';\n            //console.log(message);\n            $scope.g_form.setValue(key, value);\n        } catch (error) {\n            console.log('Error setting field', error);\n        }\n    });\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Signature Pad Widget/Client Controller.js",
    "content": "api.controller = function($scope) {\n    var c = this;\n    var canvas, ctx;\n    var drawing = false;\n    var lastPos = { x: 0, y: 0 };\n\n    // Initialize after DOM is ready\n    c.$onInit = function() {\n        setTimeout(function() {\n            canvas = document.getElementById('signature-pad');\n            if (!canvas) return;\n\n            // Get 2D drawing context\n            ctx = canvas.getContext('2d');\n            ctx.lineWidth = 2;\n            ctx.strokeStyle = '#000';\n\n            // Mouse event listeners\n            canvas.addEventListener('mousedown', startDraw);\n            canvas.addEventListener('mousemove', draw);\n            canvas.addEventListener('mouseup', endDraw);\n\n            // Touch event listeners (for mobile/tablet)\n            canvas.addEventListener('touchstart', startDraw);\n            canvas.addEventListener('touchmove', draw);\n            canvas.addEventListener('touchend', endDraw);\n        }, 200);\n    };\n\n    // Get drawing position based on mouse or touch input\n    function getPosition(event) {\n        var rect = canvas.getBoundingClientRect();\n        if (event.touches && event.touches[0]) {\n            return {\n                x: event.touches[0].clientX - rect.left,\n                y: event.touches[0].clientY - rect.top\n            };\n        }\n        return {\n            x: event.clientX - rect.left,\n            y: event.clientY - rect.top\n        };\n    }\n\n    // Start drawing when mouse/touch pressed\n    function startDraw(e) {\n        drawing = true;\n        lastPos = getPosition(e);\n    }\n\n    // Draw line on canvas while dragging\n    function draw(e) {\n        if (!drawing) return;\n        e.preventDefault(); // Prevent page scrolling on touch\n        var pos = getPosition(e);\n        ctx.beginPath();\n        ctx.moveTo(lastPos.x, lastPos.y);\n        ctx.lineTo(pos.x, pos.y);\n        ctx.stroke();\n        lastPos = pos;\n    }\n\n    // Stop drawing when mouse/touch released\n    function endDraw() {\n        drawing = false;\n    }\n\n    // Clear the canvas\n    c.clearSignature = function() {\n        if (ctx) ctx.clearRect(0, 0, canvas.width, canvas.height);\n        drawing = false;\n    };\n\n    // Convert signature to base64 image and attach\n    c.attachSignature = function() {\n        if (!ctx) return alert(\"Canvas not initialized.\");\n        var data = canvas.toDataURL('image/png'); // Get base64 encoded image\n        alert(\"Signature captured successfully. It will be attached after submission.\\n\\n\" + data);\n    };\n};\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Signature Pad Widget/HTML File.html",
    "content": "<div class=\"text-center\">\n  <!-- Canvas area for drawing signature -->\n  <canvas id=\"signature-pad\" width=\"400\" height=\"200\"\n          style=\"border:1px solid #ccc; border-radius:8px; cursor:crosshair; touch-action:none;\">\n  </canvas>\n\n  <!-- Action buttons -->\n  <div class=\"mt-3\">\n    <button class=\"btn btn-primary\" ng-click=\"c.clearSignature()\">Clear</button>\n    <button class=\"btn btn-success\" ng-click=\"c.attachSignature()\">Attach Signature</button>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Signature Pad Widget/README.md",
    "content": "A lightweight ServiceNow Service Portal widget that allows users to draw, clear, and attach signatures using a <canvas> element, compatible with both desktop and mobile devices.\n\nFeatures\n\nDraw signature using mouse or touch input.\n\nClear the signature pad with one click.\n\nConvert the signature to a Base64 PNG string for storage or submission.\n\nNo external dependencies (pure JavaScript & HTML5 Canvas).\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Spiderman Animation/README.md",
    "content": "To use this widget, follow the below steps:\n\n1. Create a new widget and copy the html, style and client script in the widget.\n2. Upload the 2 images- Green goblin image and Spiderman Image, in the image table. Also, upload the audio in audio table.Use the same name given in the tables in the widget\n3. Create a page and add the widget to it(12 GRID LAYOUT).\n4. Change the width of `.card-overlay` class accordingly.\n5. (Optional) On the portal page of this widget , open - Edit container background and change the width to fluid. \n\nHere is Page Content structure\n\n### Page Content\n##### Page: Marvel\n\n    Marvel - Container1\n\t    Row1\n\t\t    Column1\n\t\t\t    Instance: (Spiderman)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Spiderman Animation/client_script.js",
    "content": "api.controller=function() {\n\t/* widget controller */\n\tvar c = this;\n\t  var sound=new Audio('spider.mp3');\n\t  sound.play();\n\t  setTimeout( function() {\n\t\t  var ele = document.querySelector('.spidey')\n\t\t  ele.style.animationPlayState = 'paused'\n\t  }, 8500);\n  };"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Spiderman Animation/html_template.html",
    "content": "<div class='row'>\n  <div class='goblin' class='card-overlay'> \n  <img src=\"greengoblin.png\" width=\"740px\" height=\"360\" class='goblin flashit'/>\n  <img src=\"spid.png\" height=\"260\" class='spidey web'/>\n </div>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Spiderman Animation/style.css",
    "content": ".row {\n  display: flex;\n}\n\n/*animation for spiderman*/\n.web {\n  position: absolute;\n  top: -260px;\n  animation: webout 9s;\n  right: 0;\n}\n\n@-webkit-keyframes webout {\n  0% {\n    top: -260px;\n    animation-play-state: paused\n  }\n\n  100% {\n    top: -10px\n  }\n}\n\n.card-overlay {\n  background: black;\n  margin-left: -1px;\n  width: 100%;\n  /* Adjust width accordingly */\n  margin-top: -10px;\n  position: relative;\n}\n\n.goblin {\n\n  margin-left: 100px;\n  -webkit-filter: brightness(10);\n  filter: brightness(10);\n\n}\n\n/* animation for green goblin*/\n.flashit {\n  -webkit-animation: flash ease 8s infinite;\n  animation: flash ease 8s infinite;\n}\n\n@-webkit-keyframes flash {\n  from {\n    opacity: 0;\n  }\n\n  92% {\n    opacity: 0;\n  }\n\n  93% {\n    opacity: 0.6;\n  }\n\n  94% {\n    opacity: 0.2;\n  }\n\n  96% {\n    opacity: 0.9;\n  }\n\n  to {\n    opacity: 0;\n  }\n}\n\n@keyframes flash {\n  from {\n    opacity: 0;\n  }\n\n  92% {\n    opacity: 0;\n  }\n\n  93% {\n    opacity: 0.6;\n  }\n\n  94% {\n    opacity: 0.2;\n  }\n\n  96% {\n    opacity: 1;\n  }\n\n  to {\n    opacity: 0;\n  }\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed Incident-Request-Knowledge/README.md",
    "content": "Squid Game Themed Incident - Request - Knowledge Widget \n<br />\n![alt text](https://github.com/avssrikanth/code-snippets/blob/0b965e0d6679d86f663b4b5ffd123c9869d840ee/Service%20Portal%20Widgets/Squid%20Game%20Themed%20Incident-Request-Knowledge/Squid%20Game%20Themed%20Incident-Request-Knowledge%20Widget%20Preview.png)\n<br />\nSteps to use this code :\n<br />\n1)Create a new widget in servicenow instance <br />\n2)Add the code from \"squid_game_irk.html\" file to html template<br />\n3)Add the code from \"squid_game_irk.css\" file to css-scss <br />\n4)Upload all the image files in the repo with same name to ui images.<br />\n5)Save and drag and drop on to your required page using page designer and you are done.<br />\n<br />\nFuture Enhancements:-\n<br />\nMake this widget reusable using instance options.\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed Incident-Request-Knowledge/squid_game_irk.css",
    "content": "\n .staff {\n \n    align-items: center;\n    display: inline-flex;\n    flex-direction: column;\n}\n.squid_container {\n  border-radius: 50%;\n  height: 312px;\n  -webkit-tap-highlight-color: transparent;\n  transform: scale(0.48);\n  transition: transform 250ms cubic-bezier(0.4, 0, 0.2, 1);\n  width: 400px;\n}\n.squid_container:after {\n  background-color: #f2f2f2;\n  content: \"\";\n  height: 10px;\n  position: absolute;\n  top: 390px;\n  width: 100%;\n}\n.squid_container:hover {\n  transform: scale(0.54);\n}\n.squid_container-inner {\n  clip-path: path(\n    \"M 390,400 C 390,504.9341 304.9341,590 200,590 95.065898,590 10,504.9341 10,400 V 10 H 200 390 Z\"\n  );\n  position: relative;\n  transform-origin: 50%;\n  top: -200px;\n}\n.circle {\n  background-color: #fee7d3;\n  border-radius: 50%;\n  cursor: pointer;\n  height: 380px;\n  left: 10px;\n  pointer-events: none;\n  position: absolute;\n  top: 210px;\n  width: 380px;\n}\n.img {\n  pointer-events: none;\n  position: relative;\n  transform: translateY(20px) scale(1.15);\n  transform-origin: 50% bottom;\n  transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);\n}\n.squid_container:hover .img {\n  transform: translateY(0) scale(1.2);\n}\n.img1 {\nleft: 2.2rem;\n    top: 16.4rem;\n    width: 34rem;\n    -webkit-transform-origin-x: center;}\n\n.divider {\n  background-color: #F44788;\n  height: 1px;\n  width: 160px;\n}\n.name {\n  color: white;\n  font-size: 36px;\n  font-weight: 600;\n  margin-top: 16px;\n  text-align: center;\n}\n.title {\n  color: #F44788;\n  font-family: arial;\n  font-size: 14px;\n  font-style: italic;\n  margin-top: 4px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed Incident-Request-Knowledge/squid_game_irk.html",
    "content": "\n  <!--  This widget is configured using the theme of Netflix's most watched and very popular show Squid Game  -->\n<!-- This widget can be made reusable by only having the div with class 'staff' and replace the hard coded values with instance options to fetch the data dynamically -->\n<div class=\"row \" style=\"\n    margin-top: 50px;\n    margin-bottom: 50px;\n\">\n  <div class=\"col-sm-4\">\n   <!-- Block one for incident -->\n  <div class=\"staff\">\n    <a href=\"/sp?id=sc_cat_item&sys_id=3f1dd0320a0a0b99000a53f7604a2ef9&sysparm_category=e15706fc0a0a0aa7007fc21e1ab70c2f\">\n    <div class=\"squid_container\">\n      <div class=\"squid_container-inner\">\n        <img\n          class=\"circle\"\n src=\"squid_honeycomb_candy.png\" width=\"543\" height=\"543\"/>\n        <img\n          class=\"img img1\"\n           src=\"squid_circle_staff.png\" />\n      </div>\n    </div>\n    </a>\n    <div class=\"divider\"></div>\n    <div class=\"name\">Raise an Issue</div>\n    <div class=\"title\">Please raise an incident if you are facing any issue</div>\n  </div> \n  </div>\n  \n   <!-- Block one for request -->\n  <div class=\"col-sm-4\">\n   \n  <div class=\"staff\">\n    <a href=\"/sp?id=sc_category\">\n    <div class=\"squid_container\">\n      <div class=\"squid_container-inner\">\n        <img\n          class=\"circle\"\n           src=\"squid_honeycomb_candy.png\" width=\"543\" height=\"543\"/>\n        <img\n          class=\"img img1\"\n            src=\"squid_triangle_staff.png\"/>\n      </div>\n    </div>\n    </a>\n    <div class=\"divider\"></div>\n    <div class=\"name\">Raise a Request</div>\n    <div class=\"title\">Please raise a request if you need anything</div>\n  </div> \n  </div>\n  \n   <!-- Block one for knowledge -->\n  <div class=\"col-sm-4\">\n   \n  <div class=\"staff\">\n    <a href=\"/sp?id=kb_view2\">\n    <div class=\"squid_container\">\n      <div class=\"squid_container-inner\">\n        <img\n          class=\"circle\"\n          src=\"squid_honeycomb_candy.png\" width=\"543\" height=\"543\"/>\n        <img\n          class=\"img img1\"\n           src=\"squid_square_staff.png\" />\n      </div>\n    </div>\n    </a>\n    <div class=\"divider\"></div>\n    <div class=\"name\">Know Information</div>\n    <div class=\"title\">Please browse knowledge articles to know information</div>\n  </div> \n  </div>\n\n   \n  \n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed Simple List/README.md",
    "content": "\nSquid Game Themed Simple List Widget \n<br />\n![alt text](https://github.com/avssrikanth/code-snippets/blob/45d2362d65f21f4344a762f6417ad04a9d03b906/Service%20Portal%20Widgets/Squid%20Game%20Themed%20Simple%20List/Squid%20Game%20Themed%20Simple%20List%20-%20Preview.png)\n\n<br />\nSteps to use this code :\n<br />\n1)Clone the out of box Simple List widget <br />\n2)Add the code from \"squid_game_simple_list.html\" file to html template<br />\n3)Add the code from \"squid_game_simple_list.css\" file to css-scss <br />\n4)Not required to modify the existing code in client script and server script of the cloned widget.Keep it as it is.<br />\n5)Upload all the image files in the repo with same name to ui images.<br />\n6)Save and drag and drop on to your required page using page designer .<br />\n7)Click 'ctrl' + 'right click' and open instance in page editor , add name and configure required table,filter condition query,display fields in instance options and you are done.<br />\n<br />\nFuture Enhancements:-\n<br />\nMake background image and color dynamic using instance options to make the theme of widget user configurable\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed Simple List/squid_game_simple_list.css",
    "content": ".b{\n  border: 0.5rem solid #F44788;\n}\n\n.panel {\nposition: relative;\n}\n\n.list-group-item {\n  background-color: black;\n  border: 4rem solid #F44788;\n}\n\n.text-muted {\n  color:white;\n}\n.panel-heading i {\n  cursor: pointer;\n  position: absolute;\n  padding: 10px;\n  top: 0px;\n  right: 0px;\n  cursor: pointer;\n}\n\n.disabled-filter {\n\tcolor: #A0A0A0;\n}\n\n.list-group-item.ng-enter {\n  transition: all 1s;\n  -webkit-transition: all 1s;\n\n  background-color: #c0dcfa;\n}\n\n.list-group-item.ng-enter-active {\n  background-color: #ffffff;\n}\n\n.hide-x-overflow {\n  overflow-x: hidden;\n}\n\n.translated-html > p {\n  margin: 0px;\n  padding: 0px;\n}\n\nIMG {\n  max-width: 320px;\n  max-height: 240px;\n}\n\nIMG.img-sm {\n  max-height: 40px;\n  max-width: 40px;\n}\n\n.filter-box {\n  margin-top: 10px;\n}\n\n.panel-footer {\n  background-color: black;\n  .number-shown-label {\n    margin-top: 0;\n    margin-bottom: 0;\n    font-size: 16px;\n    display: inline-block;\n  }\n\n  a {\n    color: inherit;\n  }\n}\n\n.list-group-item > a {\n\tdisplay: inline-block;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed Simple List/squid_game_simple_list.html",
    "content": "<div class=\"panel panel-{{::c.options.color}} b\" ng-if=\"c.data.isValid && (c.options.always_show == 'true' || c.options.always_show == true || c.data.filterText || c.data.list.length)\">\n  <div class=\"panel-heading\" ng-if=\"::!c.options.hide_header\" style=\"\n    background-image: url('squid_game_coffin.png');\n    background-size: auto;\n    background-position-x: center;\n    background-position-y: center;\n    height: 130px;\n    border-color: #F44788;\n\">\n    <h3 class=\"h4 panel-title\" style=\"\n    text-align: -webkit-center;\n    color: white;\n    font-weight: bold;\n    font-size: xx-large;\n    margin-left: 70px;\n   \n\">\n      <span ng-if=\"c.options.glyph\">\n        <fa name=\"{{::c.options.glyph}}\" />\n      </span>{{::c.options.title}}</h3>\n    <!-- <i class=\"fa fa-filter\" ng-click=\"c.toggleFilter()\" ng-class=\"{'disabled-filter': !c.showFilter}\"></i> -->\n    <div ng-show=\"c.showFilter\">\n      <input aria-label=\"${Filter}\" ng-model=\"c.data.filterText\" ng-model-options=\"{debounce: 300}\" sn-focus=\"c.showFilter\" placeholder=\"{{::data.filterMsg}}\" ng-change=\"c.update()\" class=\"form-control input-sm filter-box\">\n    </div>\n  </div>\n  <ul class=\"list-group hide-x-overflow\" ng-style=\"::{maxHeight: c.getMaxHeight()}\" style=\"overflow-y: auto;\" ng-if=\"c.data.list.length > 0\">\n    <li ng-repeat=\"item in c.data.list track by item.sys_id\" class=\"list-group-item\">\n      <a ng-if=\"action.glyph || c.options.image_field || (item.display_field.type == 'translated_html' ? item.display_field.value : item.display_field.display_value)\" ng-click=\"c.onClick($event, item, item.url, {})\" href=\"javascript:void(0)\" oncontextmenu=\"return false;\">\n        <span ng-repeat=\"action in c.data.actions\" href=\"\" ng-click=\"c.onClick($event, item, action.url, action)\" ng-if=\"action.glyph\"\n              class=\"list-action l-h-40 pull-right\">\n          <fa name=\"{{action.glyph}}\" ng-class=\"c.getActionColor(action)\" />\n        </span>\n        <span ng-if=\"c.options.image_field\" class=\"pull-left m-r\"\n              ng-class=\"{'avatar': c.options.rounded_images, 'thumb-sm': c.options.rounded_images}\">\n          <img ng-src=\"{{item.image_field}}\" alt=\"...\" class=\"img-sm\" ng-class=\"{'img-circle': c.options.rounded_images}\">\n        </span>\n          <div ng-switch on=\"item.display_field.type\" ng-class=\"{'l-h-40': !item.secondary_fields.length}\">\n            <span class=\"translated-html\" ng-switch-when=\"translated_html\" ng-bind-html=\"item.display_field.value\"></span>\n            <div ng-switch-default>{{item.display_field.display_value}}</div>\n          </div>\n\t  </a>\n\t    <div>\n          <small class=\"text-muted\" ng-repeat=\"f in item.secondary_fields\">\n            <span ng-if=\"!$first\"> • </span>\n            <span ng-switch=\"f.type\" title=\"{{::f.label}}\">\n              <span ng-switch-when=\"glide_date\">\n                <span ng-if=\"!f.isFuture\"> <sn-day-ago date=\"::f.value\" /> </span>\n                <span ng-if=\"f.isFuture\"> {{f.display_value}}</span>\n              </span>\n              <span ng-switch-when=\"glide_date_time\">\n                <span ng-if=\"!f.isFuture\"> <sn-time-ago timestamp=\"::f.value\" /></span>\n                <span ng-if=\"f.isFuture\"> {{f.display_value}}</span>\n              </span>\n              <span ng-switch-default=\"\">{{f.display_value}}</span>\n            </span>\n          </small>\n        </div>\n    </li>\n  </ul>\n  <div ng-if=\"!c.data.list.length\" class=\"list-group-item\">\n     ${No records found}\n  </div>\n  <div class=\"panel-footer\" ng-if=\"!c.options.hide_footer && c.options.maximum_entries && c.data.count > c.options.maximum_entries\">\n    <div class=\"h4 number-shown-label\">{{c.getMaxShownLabel(c.options.maximum_entries, c.data.count)}}</div>\n    <a class=\"pull-right\" ng-href=\"?id={{c.seeAllPage}}&table={{c.options.table}}&filter={{c.options.filter}}{{c.targetPageID}}\" aria-label=\"{{::data.viewAllMsg}} - {{::c.options.title}}\">${View all}</a>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed User Profile Card/README.md",
    "content": "\nSquid Game Themed User Profile Card Widget \n<br />\n![alt text](https://github.com/avssrikanth/code-snippets/blob/781e8f811212a6e2cb1d5a5d584fd23473352280/Service%20Portal%20Widgets/Squid%20Game%20Themed%20User%20Profile%20Card/Squid%20Game%20Themed%20User%20Profile%20Card%20-%20Front%20-%20Preview.png)\n<br />\n![alt text](https://github.com/avssrikanth/code-snippets/blob/781e8f811212a6e2cb1d5a5d584fd23473352280/Service%20Portal%20Widgets/Squid%20Game%20Themed%20User%20Profile%20Card/Squid%20Game%20Themed%20User%20Profile%20Card%20-%20Back%20-%20Preview.png)\n<br />\nSteps to use this code :\n<br />\n1)Clone the out of box User Profile widget <br />\n2)Add the code from \"squid_game_user_profile_card.html\" file to html template<br />\n3)Add the code from \"squid_game_user_profile_card.css\" file to css-scss <br />\n4)Not required to modify the existing code in client script and server script of the cloned widget.Keep it as it is.\n5)Upload all the image files in the repo with same name to ui images.<br />\n6)Save and drag and drop on to your required page using page designer and you are done.<br />\n<br />\nFuture Enhancements:-\n<br />\nConfigure full user profile page in squid game theme"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed User Profile Card/squid_game_user_profile_card.css",
    "content": "\n .card1 {\n  background-color: transparent;\n  width: 300px;\n  height: 300px;\n  perspective: 1000px;\n}\n\n.card {\n  position: relative;\n  width: 100%;\n  height: 100%;\n  text-align: center;\n  transition: transform 0.6s;\n  transform-style: preserve-3d;\n  box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);\n}\n\n.card1:hover .card {\n  transform: rotateY(180deg);\n}\n\n.front, .back {\n  position: absolute;\n  width: 100%;\n  height: 100%;\n  -webkit-backface-visibility: hidden;\n  backface-visibility: hidden;\n}\n\n.front {\n \n}\n\n.back {\n \n  transform: rotateY(180deg);\n}\n \t\n\n* {\n\t margin: 0;\n\t padding: 0;\n\t box-sizing: border-box;\n}\n body {\n\t display: flex;\n\t justify-content: center;\n\t align-items: center;\n\t font-family: \"montserrat\", san-serif;\n\t height: 100vh;\n}\n .card {\n\t display: flex;\n\t justify-content: center;\n\t align-items: center;\n\t width: 400px;\n\t height: 250px;\n\t background-image: url(squid_game_card.png);\n\t background-color: #b29967;\n\t cursor: pointer;\n\t transform-style: preserve-3d;\n\t transition: all 0.5s ease;\n\t position: relative;\n}\n .card .front {\n\t display: flex;\n\t justify-content: center;\n\t align-items: center;\n\t position: absolute;\n\t backface-visibility: hidden;\n}\n .card .front .circle {\n\t width: 70px;\n\t height: 70px;\n\t border: 7px solid #000;\n\t border-radius: 50%;\n\t margin-right: 0.5em;\n}\n .card .front .triangle {\n\t position: relative;\n\t border-left: 45px solid transparent;\n\t border-right: 45px solid transparent;\n\t border-bottom: 70px solid #000;\n}\n .card .front .triangle:before {\n\t width: 68px;\n\t height: 55px;\n\t content: \" \";\n\t background: url(squid_game_card.png) center center;\n\t clip-path: polygon(50% 5%, 0% 100%, 100% 100%);\n\t position: absolute;\n\t top: 10px;\n\t left: -34px;\n}\n .card .front .square {\n\t width: 70px;\n\t height: 70px;\n\t border: 7px solid #000;\n\t margin-left: 0.5em;\n}\n .back {\n\t display: flex;\n\t justify-content: center;\n\t align-items: center;\n\t transform: rotateY(180deg);\n}\n .back .icon {\n\t width: 50px;\n\t height: 50px;\n\t background-color: #485461;\n\t background-image: linear-gradient(315deg, #485461 0%, #28313b 74%);\n\t border-radius: 50%;\n\t margin-right: 1em;\n}\n .back p {\n\t font-weight: 400;\n\t letter-spacing: 1px;\n}\n .flip-card {\n\t transform: rotateY(180deg);\n}\n \n\n\n\n\n\n\n\n.list-group-item {\n  border: none;\n  padding: 5px 15px;\n\n  .btn-link {\n  \tpadding-left: 0;\n    padding-right: 0;\n  }\n}\n\n.user-name {\n  word-break: break-word;\n}\n\n.select2-container.select2-allowclear .select2-choice .select2-chosen {\n  margin-right: 60px;\n}\n\n.mia i {\n  font-size: 6rem;\n}\n\n.mia {\n  color: #808080;\n}\n\n.popover {\n  z-index: 1049;\n}\n\n.user-profile-container {\n  width: 100%;\n  max-width: 755px;\n  margin: 0 auto;\n  padding: 0 8px;\n}\n.input-switch input[type=checkbox]:checked ~ .switch {\n\tbackground-color: $brand-primary;\n}\n\n// accessible accessibility toggle\n@media screen and (-ms-high-contrast: active) {\n  .input-switch input.ng-not-empty+label.switch[for=\"accessibility-enabled\"] {\n    background-color: highlight;\n    border: none;\n\n    &:before {\n      background-color: highlightText;\n    }\n  }\n\n  .input-switch input.ng-empty+label[for=\"accessibility-enabled\"] {\n    background-color: window;\n    border: 1px solid windowText;\n\n    &:before {\n      background-color: windowText;\n    }\n  }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Squid Game Themed User Profile Card/squid_game_user_profile_card.html",
    "content": " <!--  This widget is configured using the theme of Netflix's most watched and very popular show Squid Game  -->\n<!-- Squid game themed user profile created by cloning oob user profile widget and making required changes to the css -->\n\n<div class=\"card1\">\n<div class=\"card\">\n    <div class=\"front\">\n        <div class=\"circle\"></div>\n        <div class=\"triangle\"></div>\n        <div class=\"square\"></div>\n    </div>\n    <div class=\"back\">\n      <div ng-if=\"data.userExists\">\n    <div class=\"panel panel-default\" style=\"\n    background-color: transparent;\n    border-color: transparent;\n\">\n      <div class=\"panel-body\">\n        <div class=\"row\">\n          <div class=\"col-xs-4 col-sm-4 text-center\" style=\"\n    padding-left: 5px;\n    padding-right: 5px;\n\">\n            <div class=\"row\">\n              <div class=\"avatar-extra-large avatar-container\" style=\"cursor:default;\">\n                <div class=\"avatar soloAvatar bottom\">\n                  <div class=\"sub-avatar mia\" ng-style=\"avatarPicture\"><i class=\"fa fa-user\"></i></div>\n                </div>\n              </div>\n            </div>\n            <div class=\"row\">\n              <button ng-if=\"::connectEnabled()\" ng-click=\"openConnectConversation()\" type=\"button\"\n                      class=\"btn btn-primary send-message\" style=\"\n    margin-left: 35px;\n    margin-right: 35px;\n    margin-top: 10px;\n    background-color: transparent;\n    color: black;\n    padding-left: 8px;\n    padding-right: 8px;\n    font-weight: bold;\n    padding-top: 3px;\n    padding-bottom: 3px;\n    border-color: black;\n    border-width: initial;\n\" ><span class=\"glyphicon glyphicon-comment pad-right\"  ></span>${Message}</button>\n              <!-- file upload -->\n              <span ng-if=\"::data.isLoggedInUsersProfile\">\n                <input ng-show=\"false\" type=\"file\" accept=\"image/jpeg,image/png,image/bmp,image/x-windows-bmp,image/gif,image/x-icon,image/svg+xml\" ng-file-select=\"attachFiles({files: $files})\" />\n                <button ng-click=\"uploadNewProfilePicture($event)\"\n                        ng-keypress=\"uploadNewProfilePicture($event)\" type=\"button\"\n                        class=\"btn btn-default send-message\" style=\"\n    margin-left: 35px;\n    margin-right: 35px;\n    margin-top: 10px;\n    background-color: transparent;\n    color: black;\n    padding-left: 8px;\n    padding-right: 8px;\n    font-weight: bold;\n    padding-top: 3px;\n    padding-bottom: 3px;\n    border-color: black;\n    border-width: initial;\n\">${Upload Picture}</button>\n              </span>\n              <a href=\"/sp?id=user_profile\" ><button  type=\"button\"\n                        class=\"btn btn-default send-message\" style=\"\n    margin-left: 35px;\n    margin-right: 35px;\n    margin-top: 10px;\n    background-color: transparent;\n    color: black;\n    padding-left: 5px;\n    padding-right: 5px;\n    font-weight: bold;\n    padding-top: 3px;\n    padding-bottom: 3px;\n    border-color: black;\n    border-width: initial;\n\">${View Full Profile}</button></a>\n              \n            </div>\n          </div>\n          <div class=\"col-xs-8 col-sm-8\" style=\"\n    margin-left: 0px;\n    padding-left: 40px;\n    padding-right: 40px;\n    padding-top: 12px;\n    padding-bottom: 12px;\n\" >\n            <h2 class=\"user-name\" style=\"\n    color: black;\n    font-weight: bold;\n    padding-bottom: 10px;\n    padding-top: 10px;\n\" >{{data.name}}</h2>\n            <p ng-if=\"::displayField('sys_user', 'company', true)\" style=\"color: black;\" ><strong class=\"pad-right\">${Company}</strong><sp-editable-field editable-by-user=\"data.isLoggedInUsersProfile\" table=\"sys_user\" table-id=\"data.sysUserID\" field-model=\"data.sysUserModel.company\" style=\"color: white;\"></sp-editable-field></p>\n            <p ng-if=\"::displayField('sys_user', 'title', true)\" style=\"color: black;\"><strong class=\"pad-right\">${Title}</strong><sp-editable-field editable-by-user=\"data.isLoggedInUsersProfile\" table=\"sys_user\" table-id=\"data.sysUserID\" field-model=\"data.sysUserModel.title\" style=\"color: white;\" ></sp-editable-field></p>\n            <p ng-if=\"::displayField('sys_user', 'department', true)\" style=\"color: black;\" ><strong class=\"pad-right\">${Department}</strong><sp-editable-field editable-by-user=\"data.isLoggedInUsersProfile\" table=\"sys_user\" table-id=\"data.sysUserID\" field-model=\"data.sysUserModel.department\" style=\"color: white;\" ></sp-editable-field></p>\n            <p ng-if=\"::displayField('sys_user', 'location', true)\" style=\"color: black;\" ><strong class=\"pad-right\">${Location}</strong><sp-editable-field editable-by-user=\"data.isLoggedInUsersProfile\" table=\"sys_user\" table-id=\"data.sysUserID\" field-model=\"data.sysUserModel.location\" style=\"color: white;\" ></sp-editable-field></p>\n            <p ng-if=\"::(data.liveProfileID && displayField('live_profile', 'short_description', true))\" style=\"color: black;\" ><strong class=\"pad-right\">${Bio}</strong><sp-editable-field editable-by-user=\"data.isLoggedInUsersProfile\" table=\"live_profile\" table-id=\"data.liveProfileID\" field-model=\"data.liveProfileModel.short_description\" style=\"color: white;\" ></sp-editable-field></p>\n          </div>\n        </div>\n      </div>\n    </div>\n \n    \n    </div>\n</div>\n  \n  </div>\n  \n  \n  \n  \n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Standard Ticket Header With On Hold Reason/README.md",
    "content": "# Ticket header with on hold reason\nA simple wrapper widget that enhances the OOTB ticket header widget and displays additional On Hold Reason field if there is any.\n\n![ticket header example](ticket_header.png)\n\n## HTML\n```html\n<sp-widget widget=\"c.data.headerTicketWidget\"></sp-widget>\n```\n## Client Controller\n```javascript\napi.controller=function($scope, spUtil, $location) {\n\t/* widget controller */\n\tvar c = this;\n\tvar urlParams = $location.search();\n\tspUtil.recordWatch($scope, urlParams.table, 'sys_id=' + urlParams.sys_id, function(){\n        c.server.refresh();\n    });\t\n};\n```\n\n## Server Script\n```javascript\n(function() {\n\tdata.headerTicketWidget = $sp.getWidget('standard_ticket_header');\n\t\n\tvar tableName = $sp.getParameter('table');\n\tvar sysId = $sp.getParameter('sys_id');\n\n\tif (tableName != 'incident'){\n\t\treturn;\n\t}\n\tvar recordGR = new GlideRecord(tableName);\n\tif (recordGR.get(sysId)){\n\t\tvar holdReasonField = $sp.getField(recordGR, 'hold_reason');\n\t\tdata.headerTicketWidget.data.headerFields.push(holdReasonField);\n\t}\n})();\n```\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Standard Ticket Page Enhanced Action Widget/README.md",
    "content": "\n# Enhanced Action Widget for Standard Ticket Page\n\nAdds additional action if current record has an approval pending or if current record is a Request Item, you can navigate to the Request level.\n# Action Widget dropdown \n![Image of Action Widget on Service Portl Page](./enhanced_action_sp_widget.png)\n\n## How to use\nImport the widget XML to your Service Portal Widget Table.\nGo to Standard Ticket Configuration and select the table (record type) you'd like to add this widget too.\nThen click on the Action Region tab, in the Action Widget field, add your new widget - EC Standard Ticket Approval Actions.\n\n\n![Image of Standard Ticket Configuration](./standard_ticket_config.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Standard Ticket Page Enhanced Action Widget/sp_widget_std_action.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<unload unload_date=\"2022-10-11 20:04:19\">\n<sp_widget action=\"INSERT_OR_UPDATE\">\n<category>custom</category>\n<client_script><![CDATA[function ($scope, spUtil, spModal) {\n\tvar c = this;\n\n\tc.action = function(state) {\n\t\tif(state == 'rejected'){\n\t\t\tspModal.prompt(\"Please provide a rejection reason\").then(function(reason) {\n\t\t\t\tc.data.reject_reason = reason;\n\t\t\t\tc.data.op = state;\n\t\t\t\tc.data.state = state;\n\t\t\t\tc.server.update();\n\t\t\t});\n\t\t} else{\n\t\t\tc.data.op = state;\n\t\t\tc.data.state = state;\n\t\t\tc.server.update();\n\t\t}\t\n\t}\n}]]></client_script>\n<controller_as>c</controller_as>\n<css>.arrow {\n  position: absolute;\n  margin-left: 50px;\n  z-index: 150;\n  border-left: 12px solid transparent;\n  border-right: 12px solid transparent;\n  border-bottom: 12px solid #FFFFFF;\n}\n\n.question-tab{\n\tfont-size:1.4rem;\n}\n\n.dropdown-menu li {\n  cursor: pointer;\n}\n\n.action-btn {\n  width: 100%;\n}</css>\n<data_table>sp_instance</data_table>\n<demo_data/>\n<description/>\n<docs display_value=\"\"/>\n<field_list/>\n<has_preview>false</has_preview>\n<id>ec_stk_approval_actions</id>\n<internal>false</internal>\n<link><![CDATA[function link(scope, element, attrs, controller) {\n  scope.setFocusOnActionButtons = function() {\n\t\tif (scope.widget.isFocusRequired) {\n\t\t\tvar elm = element.find('#actions-button')[0];\n\t\t\telm.focus();\n\t\t}\n\t}\n}]]></link>\n<name>EC Standard Ticket Approval Actions</name>\n<option_schema/>\n<public>true</public>\n<roles/>\n<script><![CDATA[(function() {\n\n\tvar taskGR = $sp.getRecord();\n\tif (taskGR == null || !taskGR.isValid()) {\n\t\tdata.isValid = false;\n\t\treturn;\n\t}\n\n\tvar tableName = taskGR.getTableName();\n\tvar sysId = taskGR.getUniqueValue();\n\tdata.canRead = taskGR.canRead();\n\tdata.canWrite = taskGR.canWrite();\n\tdata.table = tableName;\n\tdata.sys_id = sysId;\n\tvar user = gs.getUser();\n\n\t// If record is RITM, get REQ\n\tdata.req = {};\n\tif(data.table == 'sc_req_item'){\n\t\tvar ritm = new GlideRecord('sc_req_item');\n\t\tif(ritm.get(data.sys_id)){\n\t\t\tvar req = ritm.request.getRefRecord();\n\t\t\tdata.req.number = req.getValue('number');\n\t\t\tdata.req.id = req.sys_id.toString();\n\t\t\t//data.req.table = req.sys_class_name.toString();\n\t\t}\n\t}\n\n\n\tvar approvalGR = new GlideRecord('sysapproval_approver');\n\tapprovalGR.addQuery('document_id', sysId);\n\tapprovalGR.addQuery('sysapproval', sysId);\n\tapprovalGR.addQuery(\"state\", \"requested\");\n\tapprovalGR.addQuery(\"approver\", user.getID());\n\tapprovalGR.query();\n\twhile(approvalGR.next()){\n\t\tdata.isValid = true;\n\t\tif(input && input.op){\n\t\t\tif(input.reject_reason){\n\t\t\t\tapprovalGR.comments = input.reject_reason;\n\t\t\t\tapprovalGR.state = input.op;\n\t\t\t\tapprovalGR.update();\n\t\t\t} else{\n\t\t\t\tapprovalGR.state = input.op;\n\t\t\t\tapprovalGR.update();\n\t\t\t}\n\t\t}\n\n\n\t}\n\n\n\n})();\n]]></script>\n<servicenow>false</servicenow>\n<sys_class_name>sp_widget</sys_class_name>\n<sys_created_by>lcabrera</sys_created_by>\n<sys_created_on>2022-04-25 16:04:33</sys_created_on>\n<sys_id>0406a24a1bcf45902f782069bc4bcbab</sys_id>\n<sys_mod_count>125</sys_mod_count>\n<sys_name>EC Standard Ticket Approval Actions</sys_name>\n<sys_package display_value=\"Service Portal - Standard Ticket\" source=\"sn_std_tkt\">c9f1df5787f50010e0ef0cf888cb0b2c</sys_package>\n<sys_policy/>\n<sys_scope display_value=\"Service Portal - Standard Ticket\">c9f1df5787f50010e0ef0cf888cb0b2c</sys_scope>\n<sys_update_name>sp_widget_0406a24a1bcf45902f782069bc4bcbab</sys_update_name>\n<sys_updated_by>lcabrera</sys_updated_by>\n<sys_updated_on>2022-05-17 03:24:18</sys_updated_on>\n<template><![CDATA[<div ng-if=\"data.isValid || data.table == 'sc_req_item' \">\n  <div class=\"dropdown\" id=\"child-case-tabs\">\n    <button type=\"button\" id=\"actions-button\" class=\"btn btn-default dropdown-toggle action-btn\" data-toggle=\"dropdown\" style=\"width : 100%\" aria-haspopup=\"true\" ng-init=\"setFocusOnActionButtons()\">\n      ${Actions}\n      <span class=\"caret\"></span>\n    </button>\n    <ul class=\"dropdown-menu pull-right\" id=\"actionList\">\n      <li ng-if=\"data.table == 'sc_req_item'\">\n        <a ng-href=\"?sys_id={{::data.req.id}}&view=sp&id=order_status&table=sc_request\">${View } {{::data.req.number}}</a>\n      </li>  \n      <li ng-if=\"data.isValid\">\n        <a href=\"javascript:void(0)\" ng-click=\"c.action('approved')\">${Approve}</a>\n      </li>\n      <li ng-if=\"data.isValid\">\n        <a href=\"javascript:void(0)\" ng-click=\"c.action('rejected')\">${Reject}</a>\n      </li>\n    </ul>\n  </div>\n</div>]]></template>\n</sp_widget>\n</unload>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Stepper/CSS.css",
    "content": "/* Main container styling with padding and rounded corners */\n.stepper-container {\n  padding: 16px;\n  border-radius: 8px;\n}\n\n/* Flex container for horizontal stepper layout */\n.stepper {\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n\n/* Individual step container with flex properties */\n.stepper-step {\n  display: flex;\n  align-items: center;\n  flex: 1;\n  position: relative;\n}\n\n/* Circular step indicator base styling */\n.stepper-circle {\n  width: 40px;\n  height: 40px;\n  background: #e0e0e0;\n  border-radius: 50%;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  font-weight: bold;\n  font-size: 20px;\n  color: #fff;\n}\n\n/* Green background for completed steps */\n.stepper-circle.completed {\n  background: #43A047;\n}\n\n/* Blue background with border for active step */\n.stepper-circle.active {\n  background: #4285f4;\n  border: 3px solid #1976d2;\n}\n\n/* Step label text styling */\n.stepper-label {\n  margin-left: 12px;\n  font-size: 20px;\n  color: #333;\n  opacity: 0.7;\n  font-weight: normal;\n}\n\n/* Enhanced styling for completed and active step labels */\n.stepper-label.completed,\n.stepper-label.active {\n  color: #222;\n  font-weight: bold;\n  opacity: 1;\n}\n\n/* Connecting line between steps */\n.stepper-line {\n  height: 2px;\n  background: #e0e0e0;\n  flex: 1;\n  margin: 0 16px;\n}\n\n/* Hide line after the last step */\n.stepper-step:last-child .stepper-line {\n  display: none;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Stepper/HTML.html",
    "content": "<!-- Main container for the stepper component -->\n<div class=\"stepper-container\">\n  <!-- Stepper wrapper with flex layout -->\n  <div class=\"stepper\">\n    <!-- Loop through each step in the data.steps array -->\n    <div ng-repeat=\"step in data.steps track by $index\" class=\"stepper-step\">\n      <!-- Circle element showing step number or checkmark -->\n      <div class=\"stepper-circle\" ng-class=\"{\n        'completed': $index < data.currentStep,\n        'active': $index === data.currentStep\n      }\">\n        <!-- Show checkmark icon for completed steps -->\n        <i ng-if=\"$index < data.currentStep\" class=\"fa fa-check\"></i>\n        <!-- Show step number for current and future steps -->\n        <span ng-if=\"$index >= data.currentStep\">{{$index + 1}}</span>\n      </div>\n      <!-- Label text for each step -->\n      <span class=\"stepper-label\" ng-class=\"{\n        'completed': $index < data.currentStep,\n        'active': $index === data.currentStep\n      }\">\n        {{step}}\n      </span>\n      <!-- Connecting line between steps (hidden for last step) -->\n      <div class=\"stepper-line\" ng-if=\"!$last\"></div>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Stepper/README.md",
    "content": "# Stepper Widget\n\nThis custom widget provides a visually appealing **stepper** (multi-step progress indicator) for ServiceNow Service Portal, allowing you to display progress through steps such as campaign creation or onboarding.\n\n## Features\n\n- Shows steps with dynamic titles and highlights the current and completed steps.\n- Steps and current step are passed in as widget options.\n- Completed steps show a green icon.\n- Handles widget options for showing steps and the current step.\n\n<img width=\"1314\" height=\"257\" alt=\"image\" src=\"https://github.com/user-attachments/assets/abc005ea-3dc2-49c7-9108-5008dcf620f4\" />\n\n\n## Widget Options\n\n| Option        | Type   | Description                                   | Example                                  |\n|---------------|--------|-----------------------------------------------|------------------------------------------|\n| steps         | String | Stringified array of step names (JSON array)  | `[\"Step 1\", \"Step 2\", \"Step 3\"]`         |\n| currentStep   | Number | The current active step (0-based index)       | `1`                                      |\n\n## Usage\n\n1. Add the widget to your Service Portal page.\n2. In the widget options, set:\n   - **steps** as a JSON string array (e.g., `[\"Step 1\", \"Step 2\", \"Step 3\"]`)\n   - **currentStep** as the index of the current step (e.g., `1`)\n<img width=\"1119\" height=\"358\" alt=\"image\" src=\"https://github.com/user-attachments/assets/a51d48e1-1881-4b8c-9b67-06e0a0165c4c\" />\n\n\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Stepper/Server Script.js",
    "content": "(function() {\n  // Parse and set the steps array from widget options\n  // If options.steps exists, parse the JSON string; otherwise, use empty array\n  data.steps = options.steps ? JSON.parse(options.steps) : [];\n  \n  // Parse and set the current step index from widget options\n  // If options.current_step exists, convert to integer; otherwise, default to 0\n  data.currentStep = options.current_step ? parseInt(options.current_step) : 0;\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Sticky Notes/CSS-SCSS.css",
    "content": ".sticky-notes-widget {\n  padding: 10px;\n}\n\n.sticky-note {\n  display: inline-block;\n  width: 220px;\n  min-height: 100px;\n  margin: 8px;\n  padding: 10px;\n  border-radius: 6px;\n  box-shadow: 0 2px 5px rgba(0,0,0,0.2);\n  vertical-align: top;\n  font-size: 13px;\n  white-space: pre-wrap;\n}\n\n.note-toolbar {\n  text-align: right;\n  margin-bottom: 3px;\n}\n\n.note-content {\n  font-weight: 500;\n  color: #333;\n}\n\n.note-meta {\n  margin-top: 6px;\n  font-size: 11px;\n  color: #666;\n}\n\n.add-note-box {\n  margin-top: 20px;\n}\n\n.note-controls {\n  display: flex;\n  gap: 5px;\n  margin-top: 5px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Sticky Notes/Client Script.js",
    "content": "api.controller=function($scope) {\n/* widget controller */\nvar c = this;\n c.add =function(){\n  c.data.action = \"add\";\n  c.server.update().then(function(){\n    c.data.action = undefined;\n\t  c.data.newColor =\"\";\n    c.data.text = \"\";\n  })\n }\n c.remove =function(i){\n  c.data.i =i;\n  c.data.action = \"remove\";\n  c.server.update().then(function(){\n    c.data.action = undefined;\n  })\n }\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Sticky Notes/HTML.html",
    "content": " <div class=\"container sticky-notes-widget\">\n      <h4><i class=\"fa fa-sticky-note\"></i> My Sticky Notes</h4> \n  <div class=\"sticky-note\" ng-repeat=\"n in c.data.notes track by n.sys_id\"\n       ng-style=\"{'background-color': n.color}\">\n    <div class=\"note-toolbar\">\n      <button class=\"btn btn-xs btn-link text-danger\" ng-click=\"c.remove(n.sys_id)\">\n        <i class=\"fa fa-trash\"></i>\n      </button>\n    </div>\n    <div class=\"note-content\">{{n.text}}</div>\n    <div class=\"note-meta\">{{n.created_on | date:'short'}}</div>\n  </div>\n     <div class=\"add-note-box\">\n    <textarea ng-model=\"c.data.text\" placeholder=\"Enter value...\" class=\"form-control\" />\n    <br>\n    <div class=\"note-controls\">\n      <select ng-model=\"c.data.newColor\" class=\"form-control input-sm\">\n        <option value=\"#fff59d\">Yellow</option>\n        <option value=\"#b3e5fc\">Blue</option>\n        <option value=\"#c8e6c9\">Green</option>\n        <option value=\"#f8bbd0\">Pink</option>\n      </select>\n    <button class=\"btn btn-primary\" ng-click=\"c.add()\"><i class=\"fa fa-floppy-o\"></i> Add </button>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Sticky Notes/README.md",
    "content": "## Sticky Notes Widget\n\nA Sticky Notes Widget allows users to create, view, delete and organize personal notes - just like physical sticky notes directly within Service Portal.\n\n## Pre-requisite \n- Create new custom table Sticky Notes (u_sticky_notes)\n- Short Description - String (type)\n- Color - String (type)\n\n## Demo table\n![A test image](table.png)\n\n## Widget Output\n![A test image](demo.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Sticky Notes/Server Side.js",
    "content": "(function() {\nif(input){\n  if (input.action == \"add\") {\n  \tif(!input.text){\n\t\tgs.addInfoMessage(\"Please enter value in text area\");\n\t\treturn;\n\t}\n\t     var rec = new GlideRecord('u_sticky_notes'); \n       rec.initialize();\n\t\t   rec.u_short_description = input.text;\n\t\t\t rec.u_color = input.newColor;\n        rec.insert();\n\t}\n  if (input.action == \"remove\") {\n\t   var del = new GlideRecord('u_sticky_notes');\n        if (del.get(input.i)) {\n          del.deleteRecord();\n        }\n  }\n}\n\t\n\tvar gr = new GlideRecord('u_sticky_notes');\n\tgr.orderByDesc('sys_created_on');\n\tgr.query();\n\tdata.notes = [];\n\twhile(gr.next()){\n\t\t  data.notes.push({\n      sys_id: gr.getUniqueValue(),\n      text: gr.getValue(\"u_short_description\"),\n      color: gr.getValue(\"u_color\"),\n      created_on: gr.getDisplayValue(\"sys_created_on\")\n    });\n\t}\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Tab Panel Widget/CSS-SCSS.scss",
    "content": ".tab-content{\n    background-color: #f0f0f0;\n    padding: 10px;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Tab Panel Widget/HTML Template.html",
    "content": "<div>\n  <ul class=\"nav nav-tabs\">\n    <li class=\"active\">\n      <a  href=\"#1\" data-toggle=\"tab\">{{::c.options.tab_heading_1}}</a>\n    </li>\n    <li><a href=\"#2\" data-toggle=\"tab\">{{::c.options.tab_heading_2}}</a>\n    </li>\n    <li><a href=\"#3\" data-toggle=\"tab\">{{::c.options.tab_heading_3}}</a>\n    </li>\n  </ul>\n\n  <div class=\"tab-content\">\n    <div class=\"tab-pane active\" id=\"1\">\n      <h3>{{::c.options.tab_content_1}}</h3>\n    </div>\n    <div class=\"tab-pane\" id=\"2\">\n      <h3>{{::c.options.tab_content_2}}</h3>\n    </div>\n    <div class=\"tab-pane\" id=\"3\">\n      <h3>{{::c.options.tab_content_3}}</h3>\n    </div>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Tab Panel Widget/Options.json",
    "content": "[{\"name\":\"tab_heading_1\",\"section\":\"Data\",\"default_value\":\"Tab 1\",\"label\":\"Tab Heading 1\",\"type\":\"string\"},{\"name\":\"tab_content_1\",\"section\":\"Data\",\"default_value\":\"This is the Tab Content for Tab 1\",\"label\":\"Tab Content 1\",\"type\":\"string\"},{\"name\":\"tab_heading_2\",\"section\":\"Data\",\"default_value\":\"Tab 2\",\"label\":\"Tab Heading 2\",\"type\":\"string\"},{\"name\":\"tab_content_2\",\"section\":\"Data\",\"default_value\":\"This is the Tab Content for Tab 2\",\"label\":\"Tab Content 2\",\"type\":\"string\"},{\"name\":\"tab_heading_3\",\"section\":\"Data\",\"default_value\":\"Tab 3\",\"label\":\"Tab Heading 3\",\"type\":\"string\"},{\"name\":\"tab_content_3\",\"section\":\"Data\",\"default_value\":\"This is the Tab Content for Tab 3\",\"label\":\"Tab Content 3\",\"type\":\"string\"}]\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Tab Panel Widget/README.md",
    "content": "# Tab Panel Widget\nThis widget can be used to show tab wise content.\n\nTab 1\n![alt text](https://raw.githubusercontent.com/debendu-das/code-snippets/service-portal-widget-tab-panel/Service%20Portal%20Widgets/Tab%20Panel%20Widget/Image%201.png)\n\nTab 2\n![alt text](https://raw.githubusercontent.com/debendu-das/code-snippets/service-portal-widget-tab-panel/Service%20Portal%20Widgets/Tab%20Panel%20Widget/image%202.png)\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Upload Files/README.md",
    "content": "# Service Portal Widget - \"Add attachments\" form\n\nThis widget is using the 'Attachment API - POST /now/attachment/file' to upload multiple files on form submit.\n\nThe result will appear in the console or check directly the record."
  },
  {
    "path": "Modern Development/Service Portal Widgets/Upload Files/body.html",
    "content": "<div>\n    <br /> Table Name: <sn-record-picker table=\"'sys_db_object'\" display-field=\"'label'\" value-field=\"'name'\"\n        field=\"tableName\" search-fields=\"'label'\" default-query=\"''\" required></sn-record-picker>\n    <br /> Record Number (eg: INC0000055): <input type='text' ng-model='record' ng-change='getID(record)' required />\n    <br /><br /> File: <input type=\"file\" id=\"fileToUpload\" multiple\n        onchange=\"angular.element(this).scope().setFiles(this)\" />\n    <br />\n    <div ng-show=\"files.length\">\n        <div ng-repeat=\"file in files.slice(0)\">\n            <span>{{file.webkitRelativePath || file.name}}</span>\n            (<span ng-switch=\"file.size > 1024*1024\">\n                <span ng-switch-when=\"true\">{{file.size / 1024 / 1024 | number:2}} MB</span>\n                <span ng-switch-default>{{file.size / 1024 | number:2}} kB</span>\n            </span>)<span class=\"glyphicon glyphicon-remove-circle\" id=\"removeicon\" ng-click=\"removeFiles(file)\"></span>\n        </div>\n        <input type=\"button\" ng-click=\"uploadFiles()\" value=\"Upload\" />\n    </div>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Upload Files/client.js",
    "content": "function($scope, $http) {\n\tvar c = this;\n\t// CODE FOR sn-record-picker\n\t$scope.tableName = {\n\t\tname: 'tableName'\n\t};\t\t\n\t$scope.$on(\"field.change\", function(evt, parms) {\n\t\tif (parms.field.name == 'tableName'){\n\t\t\tc.data.table = parms.newValue.toString();\n\t\t\tc.server.update();\n\t\t}\n\t});\n\n\t// CODE FOR input\trecord\n\t$scope.getID = function(rec) {\n\t\tc.data.record = rec.toString();\n\t\tc.server.update();\n\t}\t\n\n\t// CODE FOR fileupload\t\n\t$scope.files = [];\t\n\t$scope.setFiles = function(element) {\n\t\t$scope.$apply(function() {\n\t\t\tconsole.log('files:', element.files);\n\t\t\t// Turn the FileList object into an Array\n\t\t\tfor (var i = 0; i < element.files.length; i++) {\n\t\t\t\t$scope.files.push(element.files[i]);\n\t\t\t}\n\t\t});\n\t};\n\n\t$scope.removeFiles = function(fname) {\n\t\tvar index = $scope.files.indexOf(fname);\n\t\tif(index>-1)\n\t\t\t$scope.files.splice(index,1);\n\t};\t\n\n\t$scope.uploadFiles = function() {\t\n\t\t$scope.fd = new FormData();\n\t\t$scope.files.forEach(function(file){\n\t\t\t$scope.fd.set('files', file);\n\t\t\tvar request = {\n\t\t\t\tmethod: 'POST',\n\t\t\t\turl: '/api/now/attachment/file?table_name='+c.data.table+'&table_sys_id='+c.data.rec_sysid+'&file_name='+file.name,\n\t\t\t\tdata: $scope.fd.get('files'),\n\t\t\t\theaders: {\n\t\t\t\t\t'Content-Type': file.type,\n\t\t\t\t\t'Accept':'application/json'\t\t\t\t\t\n\t\t\t\t}\n\t\t\t};\n\t\t\t//console.log('HTTP request:',request);\n\n\t\t\t// SEND THE FILES.\n\t\t\t$http(request).then(function successCallback(response) {\n\t\t\t\t// this callback will be called asynchronously\n\t\t\t\t// when the response is available\n\t\t\t\tconsole.log(\"File was uploaded successfully!\")\n\t\t\t}, function errorCallback(response) {\n\t\t\t\t// called asynchronously if an error occurs\n\t\t\t\t// or server returns response with an error status.\n\t\t\t\tconsole.log(\"Uploaded failed!\")\n\t\t\t});\n\n\t\t});\n\t}\n\n}"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Upload Files/server.js",
    "content": "(function() {\n\tdata.response = '';\n\tif(input.record){\n\t\tvar gr = new GlideRecord(input.table);\n\t\tgr.addQuery('number',input.record);\n\t\tgr.query();\n\t\tif(gr.next()){\n\t\t\tdata.rec_sysid = gr.sys_id.toString();\n\t\t\tdata.response = 'success';\n\t\t}\n\t}\n\n})();"
  },
  {
    "path": "Modern Development/Service Portal Widgets/Upload Files/style.css",
    "content": "#removeicon:hover{\n    cursor:pointer;\n    cursor:hand;\n  }"
  },
  {
    "path": "Modern Development/Service Portal Widgets/custom404/HTML.js",
    "content": "<div>\n  <h1 class=\"heading-message\">${This page could not be found. Based on your search we have found below valid pages}</h1>\n  <div class=\"page-container\"> <!-- Container to show valid pages.-->\n    <ul>\n      <li ng-repeat=\"item in data.pageArr\">  <!-- List to show valid pages.-->\n        <a href = {{item.url}} target=\"_blank\">{{item.name}}</a>\n      </li>\n    </ul>\n  </div>\n</div>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/custom404/README.md",
    "content": "**How to use**\n1. Add this widget to new portal page.\n2. Add the page in \"sp_portal\" record in 404 field.\n\n**Use Case**\n1. Some organizations do not want to show OOB 404 page having game but want to show the suggestions with correct pages.\n\n**Result**\n1. This code will search the 3 letters of page_id from URL and will suggest correct pages.\n2. The correct links will open the pages in next tab.\n\n   <img width=\"959\" height=\"413\" alt=\"404\" src=\"https://github.com/user-attachments/assets/2480ba26-4ea3-4e12-baf8-bbf89fec548a\" />\n\n\n   \n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/custom404/css.js",
    "content": ".heading-message{\n  color:red;\n}\n.page-container{\n  display: flex;\n  justify-content: space-evenly;\n  flex-direction:cloumn;\n}\nul{ \n  padding: 2rem;\n  list-style-type: disclosure-closed;\n}\nli{\n  padding:1rem;\n}\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/custom404/server.js",
    "content": "(function() {\n    /*\n\tThis script will get the 3 characters of page_id from url and suggest valid pages.\n     */\n    data.pageArr = []; // array to store related pages\n    var pageId = $sp.getParameter('id').toString(); // get page id from url\n    // get 3 letters of page id\n    if (pageId && pageId.length() > 3)\n        pageId = pageId.substring(0, 3);\n\n    var relatedPages = new GlideRecord('sp_page');\n    relatedPages.addEncodedQuery('idLIKE' + pageId);\n    relatedPages.query();\n    while (relatedPages.next()) {\n        var tempList = {}; // temporary object.\n        tempList.name = relatedPages.getValue('title');\n        tempList.url = '/' + $sp.getValue('url_suffix') + '?id=' + relatedPages.getValue('id');\n        data.pageArr.push(tempList); // add related suggested pages to array\n    }\n})();\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/g_form on SP/README.md",
    "content": "# g_form on Service Portal\nBorrow code from the OOTB form widget to do interesting things with g_form on Service Portal.\nThis example widget is a quick demonstration of what can be done. I recommend looking into the OOTB form widget for more inspiration.\n\nThis widget demos to following:\n* Being able to have UI actions anywhere on the page, not just the bottom of the form widget\n* Add confirmation modals to UI Actions\n* Disable/Enable buttons based on whether form is dirty or clean\n\n***The code is intended to be on the same page as an instance of a form widget.***"
  },
  {
    "path": "Modern Development/Service Portal Widgets/g_form on SP/gform_on_sp.html",
    "content": "<div class=\"panel panel-default\">\n    <div class=\"panel-heading\">\n      <h4>{{data.f.title}}</h4>\n    </div>\n    <div class=\"panel-body\">\n      <button class=\"btn btn-block\" \n              ng-class=\"::getButtonClass(action)\" \n              ng-repeat=\"action in data.f._ui_actions\" \n              ng-click=\"triggerUIAction(action)\" \n              gfst_id=\"{{::action.sys_id}}\" \n              ng-disabled=\"submitting\">\n        {{action.name}}\n      </button>\n    </div>\n</div>\n  \n<div class=\"panel panel-default\">\n    <div class=\"panel-heading\">\n      <h4>Behavior based on form state</h4>\n    </div>\n    <div class=\"panel-body\">\n      <button class=\"btn btn-block btn-primary\" ng-click=\"showInfoMessage()\" ng-disabled=\"dirtyForm || !pageReady\">\n        Show when Pristine\n      </button>\n      <button class=\"btn btn-block btn-secondary\" ng-click=\"showErrorMessage()\" ng-disabled=\"!dirtyForm || !pageReady\">\n        Show when Dirty\n      </button>\n    </div>\n</div>"
  },
  {
    "path": "Modern Development/Service Portal Widgets/g_form on SP/gform_on_sp_client.js",
    "content": "api.controller = function($scope, $rootScope, spModal) {\n    var c = this;\n\n    $scope.pageReady = false;\n\n    // From ootb form widget\n    var g_form;\n    $rootScope.$on(\"spModel.gForm.rendered\", function(e, gFormInstance) {\n        if (gFormInstance.getTableName() == $scope.data.table) {\n            g_form = gFormInstance;\n            $scope.pageReady = true;\n        }\n    });\n\n    $rootScope.$on(\"field.change\", function(e, parms) {\n        $scope.dirtyForm = g_form.isUserModified();\n    });\n\t\n\t$rootScope.$on(\"sp.form.record.updated\", function() {\n\t\t$scope.submitting = false;\n\t});\n\n    // From ootb form widget\n    $scope.getButtonClass = function(action) {\n        if (action.form_style == \"destructive\")\n            return \"btn-danger\";\n\n        if (action.form_style == \"primary\")\n            return \"btn-primary\";\n\n        return \"btn-default\";\n    };\n\n    // From ootb form widget\n    $scope.triggerUIAction = function(action) {\n        if ($scope.data.disableUIActions && !action.primary) {\n            return;\n        }\n\n        var activeElement = document.activeElement;\n        if (activeElement) {\n            activeElement.blur();\n\t\t}\n\n        spModal.confirm(\"Are you sure?\").then(function(confirmed) {\n            if (confirmed) {\n                $scope.$evalAsync(function() {\n                    if (g_form) {\n                        $scope.submitting = true;\n                        if (!g_form.submit(action.action_name || action.sys_id))\n                            $scope.submitting = false;\n                    }\n                });\n            }\n        });\n    };\n\n    $scope.showInfoMessage = function() {\n        g_form.addInfoMessage(\"Info message\");\n    };\n\n    $scope.showErrorMessage = function() {\n        g_form.addErrorMessage(\"Error message\");\n    };\n};"
  },
  {
    "path": "Modern Development/Service Portal Widgets/g_form on SP/gform_on_sp_server.js",
    "content": "(function() {\n\tdata.table = $sp.getParameter(\"table\");\n\tdata.sys_id = $sp.getParameter(\"sys_id\");\n\tdata.view = $sp.getParameter(\"view\");\n\n    data.f = $sp.getForm(data.table, data.sys_id, data.query, data.view, false);\n})();"
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/README.md",
    "content": "# iFrame Widget\nA flexible and reusable widget to display content in an iFrame.\n\n## Configurable Instance Options\n\nThe following instance options allow this widget to show external content in a variety of predefined sizes and can update the Service Portal navigation breadcrumb widget and Portal page title.\n\n1. URL\n\nThe URL of the external page to embed\n\n2. Size\n\nA selectable list of available size and aspect ratios. These map to css classes defined in the widget.\n\n3. Label\n\nDescription text used to support assistive readers and update the ServiceNow breadcrumb widget if it is present on the same page.\n\n4. Set Page Title\n\nA checkbox to override the page title with the Label field.\n\n\n## Setup the widget\n\n> The entire widget and a demo page has been provided in an update set, `Service Portal - iFrame Widget.xml`, but the follow the steps below to set up the widget from scratch\n\n1. Create a new widget and set **Name** to **iFrame**\n2. Copy the contents of `template.html` to the **Body HTML template** window\n3. Copy the contents of `style.scss` to the **CSS** window\n4. Copy the contents of `server.js` to the **Server script** window\n5. Copy the contents of `client.js` to the **Client controller** window\n6. Copy the contents of `optionSchema.json` to the **Option schema** window\n7. Drag the newly created widget onto any Service Portal page\n8. Add the URL of the external content to the Widget instance options\n\n## External content requirements\n\nDisplaying an external site in an iFrame is subject to Cross Origin Resource Sharing (CORS) policies. These are enforced by the web browser and configured by the external site. You must ensure that the site you are trying to embed allows it.\n\nYou may test the iFrame widget using [Wikipedia](https://en.wikipedia.org/) which does allow embedding."
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/Service Portal - iFrame Widget.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?><unload unload_date=\"2021-10-20 05:03:12\">\n<sys_remote_update_set action=\"INSERT_OR_UPDATE\">\n<application display_value=\"Global\">global</application>\n<application_name>Global</application_name>\n<application_scope>global</application_scope>\n<application_version/>\n<collisions/>\n<commit_date/>\n<deleted/>\n<description/>\n<inserted/>\n<name>Service Portal - iFrame Widget</name>\n<origin_sys_id/>\n<parent display_value=\"\"/>\n<release_date/>\n<remote_base_update_set display_value=\"\"/>\n<remote_parent_id/>\n<remote_sys_id>a3fc8db11b53301097be5283604bcb12</remote_sys_id>\n<state>loaded</state>\n<summary/>\n<sys_class_name>sys_remote_update_set</sys_class_name>\n<sys_created_by>admin</sys_created_by>\n<sys_created_on>2021-10-20 05:03:12</sys_created_on>\n<sys_id>055dc9bd1b13301097be5283604bcb90</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_updated_by>admin</sys_updated_by>\n<sys_updated_on>2021-10-20 05:03:12</sys_updated_on>\n<update_set display_value=\"\"/>\n<update_source display_value=\"\"/>\n<updated/>\n</sys_remote_update_set>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_widget_bdf526831b5cbc907393da09dc4bcbb4</name>\n<payload>&lt;?xml version=\"1.0\" encoding=\"UTF-8\"?&gt;&lt;record_update table=\"sp_widget\"&gt;&lt;sp_widget action=\"INSERT_OR_UPDATE\"&gt;&lt;category&gt;custom&lt;/category&gt;&lt;client_script&gt;&lt;![CDATA[api.controller = function ($scope, $rootScope, $location, $sce, spUtil) {\n    var c = this;\n    var params = $location.search();\n    var url = params.url || c.options.url;\n    var size = params.size || c.options.size;\n      var label = params.label || c.options.label;\n      var setPageTitle = params.set_page_title || c.options.set_page_title;\n  \n    if (!url) $scope.showError = true;\n    else {\n      $scope.frameSource = $sce.trustAsResourceUrl(url);\n      $scope.contentClass = \"external-content-\" + size;\n          $scope.label = label;\n    }\n  \n    spUtil.setBreadCrumb($scope, [{label:label}])\n    if(setPageTitle)\n       $('head title').text(label);\n};\n  ]]&gt;&lt;/client_script&gt;&lt;controller_as&gt;c&lt;/controller_as&gt;&lt;css&gt;.external-content {\n  overflow: hidden;\n  position: relative;\n}\n\n.external-content-large {\n  height: 3000px;\n}\n\n.external-content-medium {\n  height: 0;\n  padding-bottom: 75%;\n}\n\n.external-content-small {\n  height: 150px;\n  width: 200px;\n}\n\n.external-content-video {\n  height: 0;\n  padding-bottom: 56.25%; // 16:9 aspect ratio\n  padding-top: 35px;\n}\n\n.external-content iframe {\n  position: absolute;\n  top: 0;\n  left: 0;\n  width: 100%;\n  height: 100%;\n  border: none;\n}\n&lt;/css&gt;&lt;data_table&gt;sp_instance&lt;/data_table&gt;&lt;demo_data/&gt;&lt;description&gt;A flexible and reusable widget to display content in an iFrame&lt;/description&gt;&lt;docs/&gt;&lt;field_list/&gt;&lt;has_preview&gt;false&lt;/has_preview&gt;&lt;id/&gt;&lt;internal&gt;false&lt;/internal&gt;&lt;link&gt;&lt;![CDATA[function link(scope, element, attrs, controller) {\n\n}]]&gt;&lt;/link&gt;&lt;name&gt;iFrame&lt;/name&gt;&lt;option_schema&gt;[{\"name\":\"url\",\"section\":\"Presentation\",\"label\":\"URL\",\"type\":\"string\"},{\"name\":\"size\",\"section\":\"Presentation\",\"label\":\"Size\",\"type\":\"choice\",\"choices\":[{\"label\":\"large\",\"value\":\"large\"},{\"label\":\"medium\",\"value\":\"medium\"},{\"label\":\"small\",\"value\":\"small\"},{\"label\":\"video\",\"value\":\"video\"}]},{\"name\":\"label\",\"section\":\"Presentation\",\"label\":\"Label\",\"type\":\"string\"},{\"name\":\"set_page_title\",\"section\":\"Presentation\",\"label\":\"Set Page Title\",\"type\":\"boolean\"}]&lt;/option_schema&gt;&lt;public&gt;false&lt;/public&gt;&lt;roles/&gt;&lt;script&gt;&lt;![CDATA[(function() {\n\toptions.size = options.size || \"large\";\n})();]]&gt;&lt;/script&gt;&lt;servicenow&gt;false&lt;/servicenow&gt;&lt;sys_class_name&gt;sp_widget&lt;/sys_class_name&gt;&lt;sys_created_by&gt;admin&lt;/sys_created_by&gt;&lt;sys_created_on&gt;2021-05-28 05:35:19&lt;/sys_created_on&gt;&lt;sys_id&gt;bdf526831b5cbc907393da09dc4bcbb4&lt;/sys_id&gt;&lt;sys_mod_count&gt;30&lt;/sys_mod_count&gt;&lt;sys_name&gt;iFrame&lt;/sys_name&gt;&lt;sys_package display_value=\"Global\" source=\"global\"&gt;global&lt;/sys_package&gt;&lt;sys_policy/&gt;&lt;sys_scope display_value=\"Global\"&gt;global&lt;/sys_scope&gt;&lt;sys_update_name&gt;sp_widget_bdf526831b5cbc907393da09dc4bcbb4&lt;/sys_update_name&gt;&lt;sys_updated_by&gt;admin&lt;/sys_updated_by&gt;&lt;sys_updated_on&gt;2021-10-20 05:02:14&lt;/sys_updated_on&gt;&lt;template&gt;&lt;![CDATA[&lt;div style=\"padding: 5px\"&gt;\n  &lt;div ng-if=\"showError\" class=\"padding\"&gt;\n    &lt;div class=\"alert alert-warning\" role=\"alert\"&gt;\n      Provide a URL as a widget option or as a parameter, 'url'\n    &lt;/div&gt;\n  &lt;/div&gt;\n  &lt;div class=\"external-content\" ng-class=\"contentClass\"&gt;\n    &lt;iframe title=\"{{label}}\" aria-label=\"{{label}}\" ng-src=\"{{frameSource}}\" frameborder=\"0\" scrolling=\"yes\" sandbox=\"allow-same-origin allow-forms allow-modals allow-popups allow-scripts\" allow=\"fullscreen\"\n      &gt;${Browser does not allow iFrames}\n    &lt;/iframe&gt;\n  &lt;/div&gt;\n&lt;/div&gt;\n]]&gt;&lt;/template&gt;&lt;/sp_widget&gt;&lt;/record_update&gt;</payload>\n<payload_hash>-1028301866</payload_hash>\n<remote_update_set display_value=\"Service Portal - iFrame Widget\">055dc9bd1b13301097be5283604bcb90</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>admin</sys_created_by>\n<sys_created_on>2021-10-20 05:03:12</sys_created_on>\n<sys_id>015dc9bd1b13301097be5283604bcb91</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c9c1414b30000001</sys_recorded_at>\n<sys_updated_by>admin</sys_updated_by>\n<sys_updated_on>2021-10-20 05:03:12</sys_updated_on>\n<table/>\n<target_name>iFrame</target_name>\n<type>Widget</type>\n<update_domain>global</update_domain>\n<update_guid>321dc97d9e1330106edff12f2e3b3464</update_guid>\n<update_guid_history>321dc97d9e1330106edff12f2e3b3464:-1028301866,2cf6057db3133010f09e7bca67a73372:-321479317,54960d7d011330108247cc29da1fb9c3:-1900201992,8f660d7d9a133010c9764e6eb5889ebb:377898878,4456c97db8133010834a171a040df908:299766654,5226817d7c133010efe57f08e85cf87d:-1439421155,8705c53d43133010084e44508881d6fc:-1881917512,1babc2915fcd38d039c15cdd26e3f345:-1003000564,40bf7551618d38d0a68e5d964b01f198:1034999308,e19fb151698d38d0b6f064bb1a238e95:2013051852,de581709e4c9f4d00f96e46f3e265c97:1034999308,aaf7530929c9f4d0566a36c827af934f:1221946261,d7bf0f092689f4d0bdb78022b6baa1fb:1360854074,180fc7c55389f4d0f2a0775703cf99ae:-1844628313,209dc7457b89f4d0f46b5c7fb08fa538:2147205276,cbacc7416989f4d022f17f8f33415589:-1104214402,e2c047cbb1dcbc907065a3395880128c:-1677609529,750cbec354dcbc90423c1d2087429816:-690359750,1bfbbec3bbdcbc907a00d0ca04910479:220023520,62cbfac399dcbc902339bd520eba0074:-1816872900,a50b3e4335dcbc901e1c36fcf690f1c3:-1746763043,0b6a7acfe09cbc90c93930c0402e2d0f:1221494628,b047f20fa69cbc90ebc3768a2ff7dc96:1955724214,7f33b287a09cbc904bf4e8e0f90c3341:-636549773,9713be47949cbc905d4e955ea72320df:-981648365,e1d2f247dc9cbc904c8bd1ca4270ea51:1751911966,cf12f207009cbc90d89fdb986d208eac:-684393446,63686607915cbc90485a4f443bbbec11:-1307710588,0448e647da5cbc9083206ba9eb3419e6:-1315848630,4b18aac3745cbc90144569e7842ad039:-1524178090,e9262a831f5cbc90bbaa56d972572113:-930804740</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n<sys_update_xml action=\"INSERT_OR_UPDATE\">\n<action>INSERT_OR_UPDATE</action>\n<application display_value=\"Global\">global</application>\n<category>customer</category>\n<comments/>\n<name>sp_page_c43a2a0b1b5cbc907393da09dc4bcb9f</name>\n<payload><![CDATA[<?xml version=\"1.0\" encoding=\"UTF-8\"?><record_update table=\"sp_page\"><sp_page action=\"INSERT_OR_UPDATE\"><category>custom</category><css/><draft>false</draft><dynamic_title_structure/><id>external_content</id><internal>false</internal><omit_watcher>false</omit_watcher><public>false</public><roles/><seo_script/><short_description>Example of embedding an external site into Service Portal.</short_description><sys_class_name>sp_page</sys_class_name><sys_created_by>admin</sys_created_by><sys_created_on>2021-05-28 05:53:31</sys_created_on><sys_id>c43a2a0b1b5cbc907393da09dc4bcb9f</sys_id><sys_mod_count>1</sys_mod_count><sys_name>external_content</sys_name><sys_package display_value=\"Global\" source=\"global\">global</sys_package><sys_policy/><sys_scope display_value=\"Global\">global</sys_scope><sys_update_name>sp_page_c43a2a0b1b5cbc907393da09dc4bcb9f</sys_update_name><sys_updated_by>admin</sys_updated_by><sys_updated_on>2021-10-20 05:02:46</sys_updated_on><title>External Content</title><use_seo_script>false</use_seo_script></sp_page><sys_translated_text action=\"delete_multiple\" query=\"documentkey=c43a2a0b1b5cbc907393da09dc4bcb9f\"/></record_update>]]></payload>\n<payload_hash>-832817701</payload_hash>\n<remote_update_set display_value=\"Service Portal - iFrame Widget\">055dc9bd1b13301097be5283604bcb90</remote_update_set>\n<replace_on_upgrade>false</replace_on_upgrade>\n<sys_created_by>admin</sys_created_by>\n<sys_created_on>2021-10-20 05:03:12</sys_created_on>\n<sys_id>095dc9bd1b13301097be5283604bcb90</sys_id>\n<sys_mod_count>0</sys_mod_count>\n<sys_recorded_at>17c9c1492860000001</sys_recorded_at>\n<sys_updated_by>admin</sys_updated_by>\n<sys_updated_on>2021-10-20 05:03:12</sys_updated_on>\n<table/>\n<target_name>external_content</target_name>\n<type>Page</type>\n<update_domain>global</update_domain>\n<update_guid>323d41f1425330106c4efaf32879ea44</update_guid>\n<update_guid_history>323d41f1425330106c4efaf32879ea44:-832817701,c16505f96c1330102af5ae4088d57b94:-219654804</update_guid_history>\n<update_set display_value=\"\"/>\n<view/>\n</sys_update_xml>\n</unload>\n"
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/client.js",
    "content": "api.controller = function ($scope, $rootScope, $location, $sce) {\n    var c = this;\n    var params = $location.search();\n    var url = params.url || c.options.url;\n    var size = params.size || c.options.size;\n      var label = params.label || c.options.label;\n      var setPageTitle = params.set_page_title || c.options.set_page_title;\n  \n    if (!url) $scope.showError = true;\n    else {\n      $scope.frameSource = $sce.trustAsResourceUrl(url);\n      $scope.contentClass = \"external-content-\" + size;\n          $scope.label = label;\n    }\n  \n      $rootScope.$emit('sp.update.breadcrumbs', [{label:label}]);\n      if(setPageTitle)\n          $('head title').text(label);\n  };\n  "
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/optionsSchema.json",
    "content": "[{\"name\":\"url\",\"section\":\"Presentation\",\"label\":\"URL\",\"type\":\"string\"},{\"name\":\"size\",\"section\":\"Presentation\",\"label\":\"Size\",\"type\":\"choice\",\"choices\":[{\"label\":\"large\",\"value\":\"large\"},{\"label\":\"medium\",\"value\":\"medium\"},{\"label\":\"small\",\"value\":\"small\"},{\"label\":\"video\",\"value\":\"video\"}]},{\"name\":\"label\",\"section\":\"Presentation\",\"label\":\"Label\",\"type\":\"string\"},{\"name\":\"set_page_title\",\"section\":\"Presentation\",\"label\":\"Set Page Title\",\"type\":\"boolean\"}]"
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/server.js",
    "content": "(function() {\n\toptions.size = options.size || \"large\";\n})();"
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/style.scss",
    "content": ".external-content {\n    overflow: hidden;\n    position: relative;\n  }\n  \n  .external-content-large {\n    height: 3000px;\n  }\n  \n  .external-content-medium {\n    height: 0;\n    padding-bottom: 75%;\n  }\n  \n  .external-content-small {\n    height: 150px;\n    width: 200px;\n  }\n  \n  .external-content-video {\n    height: 0;\n    padding-bottom: 56.25%; // 16:9 aspect ratio\n    padding-top: 35px;\n  }\n  \n  .external-content iframe {\n    position: absolute;\n    top: 0;\n    left: 0;\n    width: 100%;\n    height: 100%;\n    border: none;\n  }\n  "
  },
  {
    "path": "Modern Development/Service Portal Widgets/iFrame/template.html",
    "content": "<div style=\"padding: 5px\">\n    <div ng-if=\"showError\" class=\"padding\">\n      <div class=\"alert alert-warning\" role=\"alert\">\n        Provide a URL as a widget option or as a parameter, 'url'\n      </div>\n    </div>\n    <div class=\"external-content\" ng-class=\"contentClass\">\n      <iframe title=\"{{label}}\" aria-label=\"{{label}}\" ng-src=\"{{frameSource}}\" frameborder=\"0\" scrolling=\"yes\" sandbox=\"allow-same-origin allow-forms allow-modals allow-popups allow-scripts\" allow=\"fullscreen\"\n        >${Browser does not allow iFrames}\n      </iframe>\n    </div>\n  </div>\n  "
  },
  {
    "path": "PAGES.md",
    "content": "# GitHub Pages Site Documentation\n\nThis document describes the GitHub Pages implementation for the ServiceNow Code Snippets repository.\n\n## Overview\n\nThe GitHub Pages site provides a beautiful, user-friendly interface for browsing the ServiceNow code snippets collection. It features:\n\n- **Responsive Design**: Works perfectly on desktop, tablet, and mobile devices\n- **Modern UI**: Clean, professional interface with ServiceNow branding\n- **Category Navigation**: Organized browsing by the 6 main categories\n- **Search Functionality**: Find specific snippets quickly\n- **Syntax Highlighting**: Beautiful code presentation with Prism.js\n- **Dark Mode Support**: Automatic dark/light mode based on user preferences\n- **Accessibility**: WCAG 2.1 compliant with keyboard navigation and screen reader support\n\n## Site Structure\n\n```\n/\n├── index.html              # Main landing page\n├── core-apis.html          # Core ServiceNow APIs category page\n├── sitemap.xml            # SEO sitemap\n├── _config.yml            # Jekyll configuration\n├── assets/\n│   ├── css/\n│   │   └── custom.css     # Additional styling and utilities\n│   ├── js/\n│   │   └── site.js        # Site functionality and interactions\n│   └── images/            # Site images and assets\n└── .github/\n    └── workflows/\n        └── pages.yml      # GitHub Pages deployment workflow\n```\n\n## Features\n\n### Design System\n\nThe site uses a comprehensive design system with:\n\n- **Color Palette**: ServiceNow-inspired blues and greens\n- **Typography**: Inter font for readability, JetBrains Mono for code\n- **Spacing**: Consistent 8px grid system\n- **Components**: Reusable cards, buttons, and navigation elements\n- **Responsive Grid**: CSS Grid and Flexbox for layouts\n\n### Category Pages\n\nEach of the 6 main categories has:\n\n- **Overview Page**: Description and subcategory listing\n- **GitHub Integration**: Direct links to repository folders\n- **Featured Examples**: Highlighted code snippets\n- **Statistics**: Snippet counts and contribution metrics\n\n### Search Implementation\n\nThe search functionality includes:\n\n- **Real-time Search**: As-you-type filtering\n- **Category Search**: Search within specific categories\n- **Fuzzy Matching**: Flexible search terms\n- **Result Highlighting**: Matched terms highlighted in results\n\n### Performance Optimizations\n\n- **CDN Assets**: External libraries loaded from CDN\n- **Minified CSS/JS**: Optimized asset delivery\n- **Image Optimization**: Responsive images with lazy loading\n- **Caching**: Appropriate cache headers for static assets\n\n## Development\n\n### Local Development\n\nTo run the site locally:\n\n```bash\n# Install Jekyll and dependencies\ngem install jekyll bundler\nbundle install\n\n# Serve the site locally\nbundle exec jekyll serve\n\n# Site will be available at http://localhost:4000\n```\n\n### File Organization\n\n- **HTML Files**: Main pages in the root directory\n- **Assets**: CSS, JS, and images in the `/assets` directory\n- **Configuration**: Jekyll config in `_config.yml`\n- **Workflows**: GitHub Actions in `.github/workflows/`\n\n### Making Changes\n\n1. **Content Updates**: Edit HTML files directly\n2. **Styling Changes**: Modify CSS files in `/assets/css/`\n3. **Functionality**: Update JavaScript in `/assets/js/`\n4. **Configuration**: Update `_config.yml` for Jekyll settings\n\n### Deployment\n\nThe site automatically deploys via GitHub Actions when:\n\n- Changes are pushed to the `main` branch\n- Pull requests are merged\n- Manual workflow dispatch is triggered\n\nThe deployment process:\n\n1. **Build**: Jekyll builds the static site\n2. **Deploy**: GitHub Pages hosts the built site\n3. **URL**: Available at `https://servicenowdevprogram.github.io/code-snippets/`\n\n## Customization\n\n### Adding New Categories\n\nTo add a new category:\n\n1. **Create Category Page**: New HTML file (e.g., `new-category.html`)\n2. **Update Navigation**: Add links in `index.html`\n3. **Update Site.js**: Add category to the categories object\n4. **Update Sitemap**: Add new URLs to `sitemap.xml`\n\n### Modifying Design\n\nThe design system is built with CSS custom properties (variables):\n\n```css\n:root {\n    --primary-color: #1a73e8;     /* ServiceNow blue */\n    --secondary-color: #34a853;   /* ServiceNow green */\n    --bg-primary: #ffffff;        /* Background color */\n    --text-primary: #202124;      /* Text color */\n    /* ... more variables */\n}\n```\n\nChange these variables to customize the entire site's appearance.\n\n### Adding Features\n\nCommon feature additions:\n\n1. **New Components**: Create reusable CSS classes\n2. **JavaScript Functionality**: Add to `site.js` or create new modules\n3. **External Integrations**: GitHub API calls, analytics, etc.\n4. **Search Enhancements**: Integrate with external search services\n\n## SEO and Analytics\n\n### SEO Features\n\n- **Meta Tags**: Comprehensive meta descriptions and titles\n- **Structured Data**: JSON-LD for search engines\n- **Sitemap**: XML sitemap for search indexing\n- **Open Graph**: Social media sharing optimization\n- **Performance**: Fast loading times and mobile optimization\n\n### Analytics Integration\n\nThe site is prepared for analytics integration:\n\n- **Google Analytics**: Add tracking code to templates\n- **GitHub Insights**: Track repository interactions\n- **Search Analytics**: Monitor search query patterns\n\n## Accessibility\n\nThe site follows WCAG 2.1 guidelines:\n\n- **Keyboard Navigation**: Full keyboard accessibility\n- **Screen Readers**: Proper ARIA labels and semantic HTML\n- **Color Contrast**: Minimum 4.5:1 contrast ratios\n- **Focus Management**: Visible focus indicators\n- **Reduced Motion**: Respects user motion preferences\n\n## Browser Support\n\n- **Modern Browsers**: Chrome, Firefox, Safari, Edge (latest versions)\n- **Legacy Support**: IE11+ (with graceful degradation)\n- **Mobile Browsers**: iOS Safari, Chrome Mobile, Samsung Internet\n\n## Contributing to the Site\n\nTo contribute improvements to the GitHub Pages site:\n\n1. **Fork the Repository**: Create your own fork\n2. **Create Feature Branch**: Work on a dedicated branch\n3. **Test Changes**: Verify locally before submitting\n4. **Submit PR**: Include description of changes\n5. **Review Process**: Site changes require maintainer review\n\n## Troubleshooting\n\n### Common Issues\n\n1. **Build Failures**: Check Jekyll syntax and dependencies\n2. **Missing Assets**: Verify file paths and asset organization\n3. **Broken Links**: Update internal links when moving files\n4. **Mobile Issues**: Test responsive design on various devices\n\n### Getting Help\n\n- **Repository Issues**: Report bugs on GitHub\n- **Documentation**: Check Jekyll and GitHub Pages docs\n- **Community**: Ask questions on ServiceNow Developer Community\n\n## Future Enhancements\n\nPlanned improvements:\n\n- **Advanced Search**: Full-text search across code content\n- **User Contributions**: Submit snippets directly through the site\n- **Interactive Examples**: Live code execution and testing\n- **API Integration**: Real-time data from GitHub API\n- **Multilingual Support**: Internationalization for global users\n\n---\n\nThis GitHub Pages site transforms the ServiceNow Code Snippets repository into a beautiful, functional web application that makes it easy for developers to discover and use ServiceNow code examples."
  },
  {
    "path": "README.md",
    "content": "![Code Snippets Banner](https://github.com/ServiceNowDevProgram/code-snippets/assets/31702109/f9fa072a-4c0c-426b-8eed-200c6616ff60)\n\n<div align=\"center\">\n\n[![Contributions Welcome](https://img.shields.io/badge/contributions-welcome-brightgreen.svg?style=flat-square)](CONTRIBUTING.md)\n[![Hacktoberfest](https://img.shields.io/badge/Hacktoberfest-Participating-orange?style=flat-square)](https://github.com/ServiceNowDevProgram/Hacktoberfest)\n\n</div>\n\n# ServiceNow Code Snippets Repository\n\nWelcome to ServiceNow's Code Snippets community repository, managed by the Developer Program and the sndevs Slack community.\n\nInside this repository, you will find community submitted code-snippets and their variants for different use-cases.\n\n**[📝 Contribution Guidelines](CONTRIBUTING.md)** | **[📚 Browse Categories](#repository-organization)** | **[🔍 How to Use](#how-to-use-this-repository)**\n\n> Interested in our other ServiceNow Hacktoberfest projects? See the main repository [here](https://github.com/ServiceNowDevProgram/Hacktoberfest) or see our official blog post [here](https://devlink.sn/hacktoberfest).\n\n**Note:** ServiceNowDevProgram has many repositories that can be imported directly into ServiceNow, this is not one of them; This repository is meant to be edited directly in GitHub or any other Git-enabled IDE like VS Code.\n\n## Disclaimer\n\nPlease note the following:\n\n1. **Community-Sourced Code**: The code found in this repository is contributed by members of the community and has not been vetted or officially endorsed by the repository owners.\n\n2. **Use at Your Own Risk**: Users are advised to exercise caution and thoroughly review the code before implementing it in their ServiceNow instances. We strongly recommend a comprehensive review to ensure the code aligns with your specific requirements and security standards.\n\n3. **Reporting Mistakes and Issues**: If you come across any mistakes, issues, or improvements in the code, we encourage you to report them and consider contributing to the repository by submitting corrections or enhancements.\n\n4. **No Warranty or Support**: This repository is provided as-is, without any warranties or guarantees. It does not come with official support from the ServiceNow team or the repository owners.\n\nBy using the code from this repository, you acknowledge that you have read and understood this disclaimer. Your use of the code is at your own discretion and risk.\n\nWe appreciate your participation and contributions to this community-driven project. Let's collaborate to make it a valuable resource for ServiceNow developers and enthusiasts.\n\n🔔🔔🔔<br>\n**_CONTRIBUTORS must follow all guidelines in [CONTRIBUTING.md](CONTRIBUTING.md)_** or run the risk of having your Pull Requests labeled as spam.<br>\n🔔🔔🔔\n\n## Table of Contents\n\n- [How to Use This Repository](#how-to-use-this-repository)\n- [Repository Organization](#repository-organization)\n- [Finding Snippets](#finding-snippets)\n- [Folder Structure](#folder-structure)\n- [Contributing](#we-invite-you-to-contribute)\n- [Disclaimer](#disclaimer)\n\n## How to Use This Repository\n\nThis repository contains code snippets that you can use in your ServiceNow instance. Here's how to make the most of it:\n\n1. **Browse by Category**: Navigate through the [six major categories](#repository-organization) to find snippets relevant to your needs.\n2. **Copy and Adapt**: Once you find a useful snippet, copy the code and adapt it to your specific use case in your ServiceNow instance.\n3. **Read the Documentation**: Each snippet folder contains a README.md file that explains how the snippet works and how to implement it.\n4. **Search by Topic**: Use GitHub's search functionality to find snippets by keywords (e.g., \"GlideRecord\", \"REST\", \"UI Action\").\n5. **Contribute Your Own**: If you have a useful snippet, consider [contributing](#we-invite-you-to-contribute) to help others.\n\n## Repository Organization\n\nThe repository is organized into **6 major categories** that cover all aspects of ServiceNow development:\n\n### 📚 [Core ServiceNow APIs](Core%20ServiceNow%20APIs/)\nEssential ServiceNow JavaScript APIs and classes including GlideRecord, GlideAjax, GlideSystem, GlideDate, GlideDateTime, and other foundational APIs.\n\n### ⚙️ [Server-Side Components](Server-Side%20Components/)\nServer-side code including Background Scripts, Business Rules, Script Includes, Scheduled Jobs, Transform Map Scripts, and other server-executed components.\n\n### 🖥️ [Client-Side Components](Client-Side%20Components/)\nClient-side code including Client Scripts, Catalog Client Scripts, UI Actions, UI Scripts, UI Pages, and UX framework components.\n\n### 🚀 [Modern Development](Modern%20Development/)\nModern ServiceNow development approaches including Service Portal, NOW Experience Framework, GraphQL implementations, and ECMAScript 2021 features.\n\n### 🔗 [Integration](Integration/)\nExternal system integrations, data import/export utilities, RESTMessageV2 examples, Mail Scripts, MIDServer utilities, and attachment handling.\n\n### 🎯 [Specialized Areas](Specialized%20Areas/)\nDomain-specific functionality including CMDB utilities, ITOM scripts, Performance Analytics, ATF Steps, Agile Development tools, and other specialized use cases.\n\n## Finding Snippets\n\nThere are several ways to find the snippets you need:\n\n1. **By Category**: Browse the six major categories listed above.\n2. **By Search**: Use GitHub's search functionality with keywords like:\n   - API names: `GlideRecord`, `GlideSystem`, `GlideAjax`\n   - Component types: `Business Rule`, `Client Script`, `UI Action`\n   - Functionality: `REST`, `SOAP`, `Import`, `Export`\n   - Use cases: `Authentication`, `Notification`, `Workflow`\n\n3. **By Tags**: Many snippets include common keywords in their README files that can be searched.\n\n## Folder Structure\n\nThe repository follows a consistent structure to make navigation easier:\n\n```\nTop-Level Category/\n  ├── Sub-Category/\n  │   ├── Specific Snippet/\n  │   │   ├── README.md         # Description and usage instructions\n  │   │   ├── snippet_file.js   # The actual code snippet\n  │   │   └── variant.js        # Variations of the snippet (if applicable)\n```\n\nFor example:\n```\nCore ServiceNow APIs/\n  ├── GlideRecord/\n  │   ├── Query Performance Optimization/\n  │   │   ├── README.md\n  │   │   ├── basic_query.js\n  │   │   └── optimized_query.js\n```\n\n## We invite you to contribute!\n\nWe welcome contributions from the ServiceNow developer community! Your knowledge and experience can help others solve common challenges.\n\n## 🌐 Contribute from the Web (No Setup Needed!)\n\nThis repository has been approved by Hacktoberfest in spirit of learning source control and getting started in your open-source journey. You can contribute directly from your browser without needing a ServiceNow instance:\n\n1. Click **Fork** at the top-right corner of this page\n2. Open the folder where you want to add or edit a file\n3. Click **Add file → Create new file** (or edit an existing one)\n4. Scroll down, add a **commit message**, and select  \n   > \"**Create a new branch for this commit and start a pull request**\"\n5. Click **Propose changes → Create pull request**\n\nThat's it! **For detailed contribution instructions, please read our [CONTRIBUTING.md](CONTRIBUTING.md) guide before submitting.**\n\n### What makes a good contribution?\n\n- **Useful snippets** that solve common ServiceNow development challenges\n- **Well-documented code** with clear comments explaining how it works\n- **Proper organization** following the repository structure\n- **Variations** of snippets for different use cases when applicable\n\n## Leaderboard\n\nLooking for the old leaderboard? We've moved the leaderboard to the overarching [Hacktoberfest](https://github.com/ServiceNowDevProgram/Hacktoberfest#leaders) repository and have expanded its scope to all participating projects.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/ACL Audit Utility/README.md",
    "content": "# ACL Audit Utility for ServiceNow\n\n## Overview\n\nThis script audits Access Control Lists (ACLs) in your ServiceNow instance to identify potential security misconfigurations. It helps ensure that ACLs are properly configured and do not unintentionally expose sensitive data.\n\n## Features\n\n- Detects **inactive ACLs**\n- Flags ACLs with **no condition or script**\n- Warns about **public read access** (ACLs with no roles assigned)\n- Logs findings using `gs.info()` and `gs.warning()` for visibility\n\n## Usage\n\n1. Navigate to **System Definition >Scripts - Background** in your ServiceNow instance.\n2. Create a new Script Include named `ACL_Audit_Utility`.\n3. Paste the contents of `code.js` into the script field.\n\n\n## Notes\n\n- This script does not make any changes to ACLs; it only audits and logs findings.\n- You can extend the script to send email notifications or create audit records in a custom table.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/ACL Audit Utility/code.js",
    "content": "\n    // Description: Audits ACLs for potential misconfigurations and logs findings.\n\n    var grACL = new GlideRecord('sys_security_acl');\n    grACL.query();\n\n    while (grACL.next()) {\n        var aclName = grACL.name.toString();\n        var type = grACL.type.toString();\n        var operation = grACL.operation.toString();\n        var active = grACL.active;\n\n        // Check for ACLs that are inactive\n        if (!active) {\n            gs.info('[ACL Audit] Inactive ACL found: ' + aclName + ' | Operation: ' + operation);\n            continue;\n        }\n\n        // Check for ACLs with no condition or script\n        var hasCondition = grACL.condition && grACL.condition.toString().trim() !== '';\n        var hasScript = grACL.script && grACL.script.toString().trim() !== '';\n\n        if (!hasCondition && !hasScript) {\n            gs.warning('[ACL Audit] ACL with no condition or script: ' + aclName + ' | Operation: ' + operation);\n        }\n\n        // Check for ACLs granting 'read' access to 'public'\n        if (operation === 'read' && grACL.roles.toString() === '') {\n            gs.warning('[ACL Audit] Public read access detected: ' + aclName);\n        }\n    }\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Access Analysis Utility/Access alaysis script.js",
    "content": "var userId = ['abel.tuter', 'abraham.lincoln']; //example\nvar workItems = ['INC0009009', 'INC0009005'];\n/* Beginning of function*/\n\nvar gi = GlideImpersonate();\nvar currUser = gs.getUserID();\n\n// If the logged in user doesn't have impersonating roles.\nif (!gi.canImpersonate(currUser)) {\n    gs.info(\"You don't have access to impersonate\");\n}\nfor (var id in userId) {\n    var userGr = new GlideRecord('sys_user');\n    userGr.addQuery('user_name', userId[id]);\n    userGr.query();\n    if (!userGr.hasNext()) {\n      // If the user id mentioned is incorrect\n        gs.print(\"Cannot find user from user id \" + user[id] + \". Please Validate the user id\");\n    } else if (userGr.active == 'false') {\n      //If the persona is inactive\n        gs.print(id + \" is inactve.\");\n    } else {\n\t\tgi.impersonate(userGr.sys_id);\n      // Analysis report\n        gs.print(\"Access result for \" + gs.getUserDisplayName + \":\");\n\t\tfor (var item in workItems){\n\t\t\tvar taskGr = new GlideRecord('task');\n\t\t\ttaskGr.addQuery('number', workItems[item]);\n\t\t\ttaskGr.query();\n\t\t\tgs.print(workItems[item] + \" Read: \" + taskGr.canRead() + \", Write: \" + taskGr.canWrite());\n\t\t}\n\n    }\n\n}\n// End impersonation. Impersonate back to logged in user\ngi.impersonate(currUser); \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Access Analysis Utility/README.md",
    "content": "In scenarios where it's necessary to verify Read/Write access for multiple users across various work items (such as Incidents, Tasks, etc.), traditional methods like impersonating individual users or using the Access Analyzer plugin can be time-consuming. This utility streamlines the process by enabling simultaneous access analysis for multiple users by impersonating them and can be executed efficiently via a background script.\n\nuserId- Array containing the user id of personas whose access to be analyzed.\n\nworkItems- Work items extending the 'task' table.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add Bookmarks - ITIL Users/README.md",
    "content": "Script to be used to add bookmark for ITIL users. This will help add favorites for SLAs for\n- My Group Tasks\n- SLAs for My Tasks\n- Tasks Assigned to Me\n- My approvals\nto all ITIL users.\nReplace the addQuery value to get the favorites applied from the backend to required audience\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add Bookmarks - ITIL Users/script.js",
    "content": "var jsonFavList = {\n  \"SLA for My Group Tasks\": \"task_list.do?sysparm_query=assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744&sysparm_first_row=1&sysparm_view=\",\n  \"SLA for My Tasks\": \"task_list.do?sysparm_query=assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe&sysparm_first_row=1&sysparm_view=\",\n  \"Tasks Assigned to Me\": \"task_list.do?sysparm_query=stateNOT INclosed_complete,closed_abandoned^assigned_toDYNAMIC90d1921e5f510100a9ad2572f2b477fe\",\n  \"My approvals\": \"sysapproval_approver_list.do?sysparm_query=approverDYNAMIC90d1921e5f510100a9ad2572f2b477fe&sysparm_first_row=1&sysparm_view=\"\n};\n\nvar g = new GlideRecord(\"sys_user_has_role\");\ng.addEncodedQuery(\"role=282bf1fac6112285017366cb5f867469\");//considering sys_id for ITIL role is 282bf1fac6112285017366cb5f867469\ng.query();\nwhile (g.next()) {\n\tfor (var fav in jsonFavList) {\n\t\tvar grBookMark = new GlideRecord(\"sys_ui_bookmark\");\n\t\tgrBookMark.addEncodedQuery(\"user=\" + g.user + \"^title=\" + fav + \"^url=\" + jsonFavList[fav]);\n\t\tgrBookMark.query();\n\t\tif (!grBookMark.next()) {\n\t\t\tgrBookMark.initialize();\n\t\t\tgrBookMark.pinned = true;\n\t\t\tgrBookMark.title = fav;\n\t\t\tgrBookMark.url = jsonFavList[fav];\n\t\t\tgrBookMark.user = g.user;\n\t\t\tgrBookMark.insert();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add Comments/README.md",
    "content": "There have been scenarios where you are working on an integration and one of the usecase is to add comments on a record. In this scenario once you add comments directly\nto the record by assigning the value in the comments or work_notes field, it captures it as a integration user in the activity formatter. But with setJournalEntry() \nmethod you can pass the user_name and it will add the comment on behalf of the user whose user_name is passed. In this way it easy to track who is actually modifying \nthe record or adding the comments. Hope this will be a helpful snippet.\n\nIn this function the paramters are defined as:\ntableName: Name of the table on which you are adding the comments.\nrecSysId: sys_id of the record for which the comment is getting added.\nuserName: user_name of the user who is adding the comments\nfieldName: It can be either comments or work_notes or any custom journal entry field.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add Comments/addComment.js",
    "content": "function addComments(tableName,recSysId, userName, fieldName){\nvar rec = new GlideRecord(tableName);\nif(rec.get(recSysId)){\n  rec[fieldName].setJournalEntry('This is my comment',userName);\n  rec.update();\n}\n}\n\naddComments(tableName,recSysId,userName,fieldName);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add No Audit Attribute To Multiple Dictionary Entries/AddNoAuditAttributeToMultipleDictionaryEntries.js",
    "content": "/*\nThis script is used to add the [no_audit=true] attribute to multiple dictionary entries in bulk.\nCan be used for other attributes as well. \nNB: The attribut sys_id must be verified before the script execution!\n*/\n\nvar encodedQuery = '<insert encoded query here>'\n\n//Verify this is in your instance before script execution\nvar noAuitAttributeSysID = '96ea04dfeb321100d4360c505206fe7d'; \n\nvar grSD = new GlideRecord('sys_dictionary');\ngrSD.addEncodedQuery(encodedQuery);\ngrSD.query();\nwhile (grSD.next()) {\n\n\n    var grExistingAttribute = new GlideRecord('sys_schema_attribute_m2m');\n    grExistingAttribute.addQuery('schema', grSD.getUniqueValue());\n    grExistingAttribute.addQuery('attribute', noAuitAttributeSysID); //\n    grExistingAttribute.query();\n\n    if(grExistingAttribute.hasNext()){\n        grExistingAttribute.next();\n\n        if(grExistingAttribute.getValue('value')=='false'){\n            grExistingAttribute.setValue('value', 'true');\n            grExistingAttribute.update();\n        }\n    }else{\n\n        var grDicitionaryAttributeM2M = new GlideRecord('sys_schema_attribute_m2m');\n        grDicitionaryAttributeM2M.initialize();\n        grDicitionaryAttributeM2M.setValue('schema', grSD.getUniqueValue());\n        grDicitionaryAttributeM2M.setValue('attribute', noAuitAttributeSysID)\n        grDicitionaryAttributeM2M.setValue('value', 'true');\n        grDicitionaryAttributeM2M.insert();\n    }\n\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add No Audit Attribute To Multiple Dictionary Entries/README.md",
    "content": "A background script add the no-audit attribute to multiple dictionary entries. \nThe no-audit attribute will exclued the specified attributes from the system audit process and will not produc audit track at all.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Add Standard Change Model/README.md",
    "content": "Add the OOTB \"Standard Change\" model to an existing change record. OOTB, the Standard Change model is applied through an onDisplay business rule, and is not an available choice from the Model field. If you have migrated to use Change Models and generate some change requests with scripts, you may need to add the model with a background script if there is already an existing workflow context or if the model is not set by the generation script. The setter portion can be added to an existing generation script to prevent future need for this background script."
  },
  {
    "path": "Server-Side Components/Background Scripts/Add Standard Change Model/addStandardChgModel.js",
    "content": "var sid = 'ADD YOUR CHANGE RECORD SYS_ID HERE';\n\nvar chg = new GlideRecord('change_request');\nif(chg.get(sid)) {\n    chg.setValue('chg_model', 'e55d0bfec343101035ae3f52c1d3ae49'); //standard change model\nchg.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Adding bookmark to Favorites tab/Adding bookmark into Favorites tab.js",
    "content": "var filter = \"active=true^assignment_groupDYNAMICd6435e965f510100a9ad2572f2b47744\";// using dynamic filter we are filtering the assignments groups of the logged-in user\n\nvar listURL = \"/incident_list.do?sysparm_query=\" + encodeURIComponent(filter); //creating the url with the filter to showcase the list of tickets assigned to the groups which the user is a part of.\n\nvar bookmark = new GlideRecord(\"sys_ui_bookmark\"); //gliding bookmark table to verify the logged-in user's has already a book mark or not \nbookmark.addQuery(\"user\", gs.getUserID());\nbookmark.addQuery(\"url\", listURL);\nbookmark.query();\nif (!bookmark.next()) { //if not available then we are creating a new bookmark under favorites tab of the logged-in users\n    var newBookmark = new GlideRecord(\"sys_ui_bookmark\");\n\tnewBookmark.initialize();\n\tnewBookmark.order=9;\n\tnewBookmark.icon=\"list\";\n\tnewBookmark.user = gs.getUserID();\n\tnewBookmark.url = listURL;\n\tnewBookmark.title = \"Incidents assigned to my groups\";\n    \tnewBookmark.pinned = true;\n    \tnewBookmark.insert();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Adding bookmark to Favorites tab/README.md",
    "content": "The script bookmarks the list of incidents assigned to the user's groups in ServiceNow's favorite tab. It works by :\n  \n--> Constructing a filter for incidents assigned to logged-in user's groups using the OOTB dynamic filter functionality.  \n--> Checking if the list is already bookmarked.  \n--> If not, it creates a new bookmark with the title : \"Incidents assigned to my groups\", adds the list url.  \n\nThis allows the user to quickly access the list of relevant incidents from the favorites tab.\t \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Analyze user access UI page/readme.md",
    "content": "Background script to check if a specific user has access to UI page\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Analyze user access UI page/script.js",
    "content": "/*\nBelow background script can be used to analyze if user has access to UI page \n*/\n\nvar user = '5381f6fbdb5f4d14567a8e7a4896192e'; // Gets current user ID\nvar pageId = \"manage_access\"; // Replace with your actual page ID\n\nvar spScriptable = new GlideSPScriptable();\nvar canAccess = spScriptable.canSeePage(pageId);\n\ngs.print(\"User \" + user + \" can access page '\" + pageId + \"': \" + canAccess);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Approval Reminders/README.md",
    "content": "Use Case: Approval are pending due approver unavailability\n\nSolution : Assign delgates to the approver and send reminder email notification using the Approval reminder code.It will send email to delgates and they will get notified with the approval request to approve/reject per limited period.\n\nSteps: Register an event and create a notification to send  email to manager when the event fired/triggered.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Approval Reminders/approvalReminderToDelegates.js",
    "content": "Background script to send approval reminder to Approver Delegates:\n========================================\nvar appr = new GlideRecord('sysapproval_approver');\nappr.addQuery('state', 'requested');\nappr.addQuery('sysapproval.sys_class_name', 'sc_req_item');\nappr.addEncodedQuery(\"sysapproval.numberLIKERITM0010468\");\nappr.query();\nwhile(appr.next())\n\t{\n    //\" approval.reminded\" is a registered event to trigger notificationn email to  their delegates to approve or reject approval requests when approvar not available\n\t\tgs.eventQueue(\"approval.reminded\" , appr , appr.approver , appr.approver.getUserName());\n\t}\n\n\ngs.print(appr.getRowCount());\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Attach Workflow to Existing Record/README.md",
    "content": "# Attach Workflow to existing records\n\nThis background script can be useful if you run into a situation where the workflow condition did not trigger on an intended record, or an older version of a workflow had a flaw but the record already began fulfillment.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Attach Workflow to Existing Record/script.js",
    "content": "//add table name, encrypted query, and workflow sys_id you want to attach to the record(s)\n\n//example data\nvar table = \"sc_req_item\";\nvar encQuery =\n  \"active=true^cat_item=a01e54b3dbb46340cd5af9041d961958^numberINRITM0028376,RITM0028370,RITM0028310,RITM0028234,RITM0028385)\";\nvar workflow_sys_id = \"[enter workflow sys_id here]\";\n\nvar task = new GlideRecord(table);\ntask.addEncodedQuery(encQuery);\ntask.query();\n\nwhile (task.next()) {\n  var wf = new Workflow();\n  var context = wf.startFlow(workflow_sys_id, task, task.update());\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Attachment Size Audit/README.md",
    "content": "# Attachment Size Audit\n\nA ServiceNow background script that analyzes your instance's attachment storage and identifies which tables consume the most space and locate oversized files.\n\n## Overview\n\nThis read-only script scans all attachments in your instance and provides:\n- Total storage used across all attachments\n- Storage breakdown by source table (kb_knowledge, sc_task, incident, etc.)\n- List of the 25 largest individual attachments\n\nThe script does not modify or delete any data—it only reads and reports findings.\n\n## How It Works\n\nThe script runs through three main steps:\n\n1. **Calculate Total Storage** - Sums all attachment sizes to give your total storage footprint\n2. **Break Down by Table** - Groups attachments by source table to identify which applications consume the most storage\n3. **Find Largest Files** - Identifies individual attachments larger than your threshold and lists them in descending order\n\n## Configuration\n\nBefore running, adjust these variables at the top of the script:\n\n- `TOP_N_ATTACHMENTS` (default: 25) - Number of largest files to display\n- `MIN_SIZE_MB` (default: 5) - Only show attachments larger than this size in MB\n- `USE_COMPRESSED_SIZE` (default: true) - Use actual disk size (true) or logical file size (false)\n\n## Prerequisites\n\n- Admin role to execute background scripts\n- Global scope (or scoped app with sys_attachment visibility)\n- Read access to sys_attachment table\n- Access to System Logs to view results\n\n## How to Run\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Click **New** and paste the script\n3. Optionally adjust configuration variables\n4. Click **Execute**\n5. View results in **System Diagnostics → System Log** (search for \"Attachment Size Audit\")\n\n## Sample Output\n\n```\n=== Attachment Size Audit Started ===\nUsing size field: SIZE_COMPRESSED\n\nTotal Attachment Storage: 415.28 MB\n\n=== Storage Usage by Table (Descending) ===\ninvisible.sys_store_app           : 175.29 MB\nsys_upgrade_manifest              : 49.39 MB\nZZ_YYsn_wn_media                  : 33.50 MB\nZZ_YYdb_image                     : 28.99 MB\nsys_sg_mobile_builder_config      : 25.36 MB\nZZ_YYhelp_content                 : 21.00 MB\nincident                          : 1.18 MB\nkb_knowledge                      : 75.54 KB\n\n=== Top 25 Largest Attachments (>5 MB) ===\n[ 68.47 MB ] sncencZZYY.fd254d9443a161100967247e6bb8f200_3_1_3.zip | Table: invisible.sys_store_app | Record: fd254d9443a161100967247e6bb8f200\n[ 24.72 MB ] upgrade_manifest.csv | Table: sys_upgrade_manifest | Record: d5ee366b47203210ca05a464116d4328\n[ 24.66 MB ] upgrade_manifest.csv | Table: sys_upgrade_manifest | Record: 9ee610ffe17a22108bb2327e625886d0\n[ 17.77 MB ] image | Table: ZZ_YYhelp_content | Record: 19d642e347203210ca05a464116d4369\n[ 11.63 MB ] sncencZZYY.a77e3ede4df92010f87774ecf02d44f3_28_1_1.zip | Table: invisible.sys_store_app | Record: a77e3ede4df92010f87774ecf02d44f3\n\n=== Attachment Size Audit Complete ===\n```\n\n## Use Cases\n\n- **Storage Planning** - Understand current attachment storage and plan for capacity growth\n- **Cost Optimization** - Identify storage hotspots for cloud instances to reduce costs\n- **Performance Issues** - Find large attachment tables that may be slowing queries\n- **Cleanup Strategy** - Identify which tables and files should be targeted for archival or deletion\n- **Pre-Upgrade Preparation** - Run before major upgrades to identify optimization opportunities\n\n## Future Enhancement Opportunities\n\n- **Generate Cleanup Script** - Create a companion script to delete old or orphaned attachments with DRY_RUN mode\n- **Orphaned Attachment Detection** - Identify attachments where parent records have been deleted\n- **File Age Analysis** - Track attachments older than 30/90/365 days to support retention policies\n- **File Type Breakdown** - Categorize attachments by extension (PDF, DOCX, MP4, etc.)\n- **Email Notifications** - Send automated summaries to storage admins with key metrics\n- **Dashboard Widget** - Create PA widget to visualize storage trends over time\n- **Scheduled Reporting** - Set up as scheduled job to track growth monthly\n- **Duplicate Detection** - Find duplicate attachments on same records for cleanup opportunities\n\n## Safety & Performance\n\n- **Read-only operation** - No data is modified or deleted\n- **No infinite loops** - Explicit limits on record processing\n- **Timeout prevention** - Efficient aggregate queries for large datasets\n- **Sub Pro Testing** - Test in sub-production environments first\n\n\n## Authors\nMasthan Sharif Shaik ( <a href=\"https://www.linkedin.com/in/nowsharif/\" target=\"_blank\">LinkedIn</a> ,  <a href=\"https://www.servicenow.com/community/user/viewprofilepage/user-id/668622\" target=\"_blank\">SN Community</a> )\n\n## Version History:\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Attachment Size Audit/attachmentSizeAudit.js",
    "content": "// Background Script: Attachment Size Audit\n// Purpose: Analyzes your ServiceNow instance's attachment storage to identify which tables and individual files consume the most space. \n//Helps you understand storage usage patterns, locate oversized files, and plan cleanup or archival strategies. \n//Provides a read-only audit report without modifying any data.\n//\n// Author: Masthan Sharif Shaik\n// Date: October 2025\n// Tested on: Zurich, Yokohama\n//\n// IMPORTANT: Test in non-production environment first\n\n(function attachmentSizeAudit() {\n\n    // ================= CONFIGURATION =================\n    var TOP_N_ATTACHMENTS = 25;        // Number of largest attachments to list\n    var MIN_SIZE_MB = 5;              // Only list attachments larger than this (MB)\n    var USE_COMPRESSED_SIZE = true;   // true = use size_compressed, false = use size_bytes\n    // =================================================\n\n    var sizeField = USE_COMPRESSED_SIZE ? \"size_compressed\" : \"size_bytes\";\n    var totalSizeBytes = 0;\n\n    gs.info(\"=== Attachment Size Audit Started ===\");\n    gs.info(\"Using size field: \" + sizeField.toUpperCase());\n\n    // ----------------- TOTAL SIZE --------------------\n    var totalAgg = new GlideAggregate(\"sys_attachment\");\n    totalAgg.addAggregate(\"SUM\", sizeField);\n    totalAgg.query();\n    if (totalAgg.next()) {\n        totalSizeBytes = totalAgg.getAggregate(\"SUM\", sizeField);\n    }\n\n    gs.info(\"Total Attachment Storage: \" +\n        formatBytes(totalSizeBytes));\n\n    // ------------- SIZE PER TABLE (AGGREGATED) --------------\n    gs.info(\"\\n=== Storage Usage by Table (Descending) ===\");\n\n    var tableAgg = new GlideAggregate(\"sys_attachment\");\n    tableAgg.groupBy(\"table_name\");\n    tableAgg.addAggregate(\"SUM\", sizeField);\n    tableAgg.orderByAggregate(\"SUM\", sizeField);\n    tableAgg.query();\n\n    while (tableAgg.next()) {\n        var table = tableAgg.getValue(\"table_name\") || \"<no table>\";\n        var tableSize = tableAgg.getAggregate(\"SUM\", sizeField);\n        gs.info(table.padEnd(40) + \" : \" + formatBytes(tableSize));\n    }\n\n    // --------------- TOP N BIGGEST ATTACHMENTS ---------------\n    gs.info(\"\\n=== Top \" + TOP_N_ATTACHMENTS + \" Largest Attachments (>\" + MIN_SIZE_MB + \" MB) ===\");\n\n    var attGR = new GlideRecord(\"sys_attachment\");\n    attGR.addQuery(sizeField, \">\", MIN_SIZE_MB * 1024 * 1024); \n    attGR.orderByDesc(sizeField);\n    attGR.setLimit(TOP_N_ATTACHMENTS);\n    attGR.query();\n\n    while (attGR.next()) {\n        gs.info(\n            \"[ \" + formatBytes(attGR[sizeField]) + \" ] \" +\n            attGR.getValue(\"file_name\") +\n            \"  |  Table: \" + attGR.getValue(\"table_name\") +\n            \"  |  Record: \" + attGR.getValue(\"table_sys_id\")\n        );\n    }\n\n    gs.info(\"\\n=== Attachment Size Audit Complete ===\");\n\n    // ------------- FORMATTER ----------------\n    function formatBytes(bytes) {\n        bytes = parseInt(bytes, 10) || 0;\n        if (bytes < 1024) return bytes + \" B\";\n        var i = Math.floor(Math.log(bytes) / Math.log(1024));\n        var sizes = [\"B\", \"KB\", \"MB\", \"GB\", \"TB\"];\n        var value = (bytes / Math.pow(1024, i)).toFixed(2);\n        return value + \" \" + sizes[i];\n    }\n\n})();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Auto-Deactivate Users Not Logged In for X Days/Auto_Deactivate_Users_Not_Logged_In_for_X_Days.js",
    "content": "/**\n * Deactivate Dormant Users (Parameterized)\n * \n * Deactivates users in ServiceNow who have not logged in for the specified number of days.\n * Example: deactivateDormantUsers(60);\n */\n\nfunction deactivateDormantUsers(daysInactive) {\n    if (!daysInactive || isNaN(daysInactive)) {\n        gs.error(\"❌ Please provide a valid number of days. Example: deactivateDormantUsers(90);\");\n        return;\n    }\n\n    var cutoff = new GlideDateTime();\n    cutoff.addDaysUTC(-daysInactive);\n\n    var gr = new GlideRecord('sys_user');\n    gr.addNotNullQuery(\"last_login_time\");\n    gr.addQuery('last_login_time', '<', cutoff);\n    gr.addQuery('active', true);\n    gr.query();\n\n    var count = 0;\n    while (gr.next()) {\n        gr.active = false;\n        gr.update();\n        count++;\n    }\n\n    gs.info('✅ Deactivated ' + count + ' users inactive for over ' + daysInactive + ' days (before ' + cutoff.getDisplayValue() + ').');\n}\n\n// Example run\n// deactivateDormantUsers(90);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Auto-Deactivate Users Not Logged In for X Days/README.md",
    "content": "# 🧹 ServiceNow Dormant User Cleanup\n\n**ServiceNow Background Script** to automatically **deactivate users** who haven't logged in for a specified number of days.\n\n## 🚀 Usage\n1. Navigate to **System Definition → Scripts - Background**.  \n2. Paste the script and execute:\n   ```javascript\n   deactivateDormantUsers(90);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Change of Incident Priority Based on Category/Bulk Change of Incident Priority Based on Category.js",
    "content": "var priorityMapping = {\n    'Network': 1, \n    'Application': 2, \n    'Hardware': 3 \n};\n\nvar incidentGR = new GlideRecord('incident');\nincidentGR.addQuery('active', true);\nincidentGR.query();\n\nwhile (incidentGR.next()) {\n    var category = incidentGR.category.toString();\n    var newPriority = priorityMapping[category];\n\n    if (newPriority) {\n        incidentGR.priority = newPriority;\n        incidentGR.update();\n        gs.info('Updated Incident: ' + incidentGR.number + ' to Priority: ' + newPriority);\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Change of Incident Priority Based on Category/README.md",
    "content": "# Bulk Change of Incident Priority Based on Category\n\nA background script that updates incident priorities for active incidents based on predefined category-to-priority mappings.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content\n3. Modify the `priorityMapping` object with your category-to-priority rules\n4. Click \"Run script\"\n\n## What It Does\n\nThe script:\n1. Defines a mapping between incident categories and priority levels (e.g., 'Network': 1)\n2. Queries all active incidents\n3. Checks each incident's category against the mapping\n4. Updates the incident priority if a match is found\n5. Logs each updated incident number and new priority"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Create Records in Multiple Tables/BulkCreateRecordsMultipleTables.js",
    "content": "/**\n * Creates multiple records in specified tables based on provided data.\n *\n * @param {Object} target - An object where each key is the name of a table,\n *                          and each value is an array of objects representing\n *                          the records to be created. Each object in the array\n *                          should contain field-value pairs for the respective table.\n *\n * Example usage:\n * bulkCreateRecords({\n *     'incident': [\n *         { short_description: 'Network issue', caller_id: '681ccaf9c0a8016401c5a33be04be441', priority: 2 },\n *         { short_description: 'Email outage', caller_id: '681ccaf9c0a8016401c5a33be04be442', priority: 1 }\n *     ],\n *     'change_request': [\n *         { short_description: 'Server upgrade', assigned_to: '681ccaf9c0a8016401c5a33be04be443', state: 'new' }\n *     ]\n * });\n *\n * This creates two new records in the 'incident' table and one new record in the\n * 'change_request' table with the specified field values.\n */\n\nfunction bulkCreateRecords(target) {\n    for (var table in target) {\n        if (target.hasOwnProperty(table)) {\n\t\t\tvar recordData = target[table];\n            recordData.forEach(function(data) {\n                var gr = new GlideRecord(table);\n                gr.initialize();\n                for (var field in data) {\n                    if (data.hasOwnProperty(field)) {\n                        gr.setValue(field, data[field]);\n                    }\n                }\n                gr.insert();\n            });\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Create Records in Multiple Tables/README.md",
    "content": "# Function: `bulkCreateRecords(target)`\n\nCreates multiple records in specified tables based on provided data.\n\n## Parameters\n\n- **`target`** (`Object`): An object where each key is the name of a table, and each value is an array of objects representing the records to be created. Each object in the array should contain field-value pairs for the respective table.\n\n## Example Usage\n\n```javascript\nbulkCreateRecords({\n    'incident': [\n        { short_description: 'Network issue', caller_id: '681ccaf9c0a8016401c5a33be04be441', priority: 2 },\n        { short_description: 'Email outage', caller_id: '681ccaf9c0a8016401c5a33be04be442', priority: 1 }\n    ],\n    'change_request': [\n        { short_description: 'Server upgrade', assigned_to: '681ccaf9c0a8016401c5a33be04be443', state: 'new' }\n    ]\n});"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Delete Records in Multiple Tables with Conditions/BulkDeleteRecordsMultipleTablesWithConditions.js",
    "content": "\n\n/**\n * Deletes records from multiple tables based on provided encoded queries.\n *\n * @param {Object} target - An object where each key is the name of a table and each value is an encoded query string.\n *                          The function will delete all records matching the encoded query for each specified table.\n *\n * Example usage:\n * bulkDelete({\n *     'incident': 'priority=1^state=2',\n *     'change_request': 'state=3^risk=high'\n * });\n * \n * This deletes all records in the 'incident' table where the priority is 1 and the state is 2,\n * and all records in the 'change_request' table where the state is 3 and risk is 'high'.\n */\nfunction bulkDelete(target) {\n\n    for (var table in target) {\n        if (target.hasOwnProperty(table)) {\n            var getRecord = new GlideRecord(table);\n            getRecord.addEncodedQuery(target[table]);\n            getRecord.query();\n            while (getRecord.next()) {\n                \n                getRecord.deleteRecord();\n            }\n        }\n    }\n}\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Delete Records in Multiple Tables with Conditions/README.md",
    "content": "# Bulk Delete Function Documentation - Use the code/function to bulk-deletes records from multiple tables based on provided encoded queries.\n\n# Function: `bulkDelete(target)`\n\nDeletes records from multiple tables based on provided encoded queries.\n\n## Parameters\n\n- **`target`** (`Object`): An object where each key is the name of a table, and each value is an encoded query string. \n  - The function will delete all records matching the encoded query for each specified table.\n\n## Example Usage\n\n```javascript\nbulkDelete({\n    'incident': 'priority=1^state=2',\n    'change_request': 'state=3^risk=high'\n});\n```\n \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Resolve Old Incidents/README.md",
    "content": "Use this script in background scripts to Bulk Close Older Incidents. Example mentioned in the script has 'Created 1 year ago' filter which can be updated as required.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Resolve Old Incidents/script.js",
    "content": "var incGr = new GlideRecord('incident');\nincGr.addActiveQuery();\nincGr.addEncodedQuery(\"sys_created_onRELATIVELT@year@ago@1\"); //Created 1 year ago. Update query as required.\nincGr.query();\nwhile(incGr.next())\n{\n\tincGr.state = 6;\n\tincGr.close_code = 'No resolution provided'; \n\tincGr.close_notes = 'Bulk Closing Old Incidents';\n\tincGr.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Role Assignment Based on Conditions/README.md",
    "content": "Description: This is a script that assigns roles to users in bulk based on specific conditions such as department,location or job title. This script can simplify the process of managing user roles and permissions. Use Case: Assign the 'itil' role to all users in the 'IT' department who are located in a specific region.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Role Assignment Based on Conditions/script.js",
    "content": "// Define the role to be assigned\nvar roleName = 'itil';\n\n// Define the conditions for user selection\nvar department = 'IT';\nvar location = 'San Diego';\n\n// Fetch the role record\nvar roleGR = new GlideRecord('sys_user_role');\nroleGR.addQuery('name', roleName);\nroleGR.query();\nif (!roleGR.next()) {\n    gs.error('Role not found: ' + roleName);\n    return;\n}\n\n// Fetch users matching the conditions\nvar userGR = new GlideRecord('sys_user');\nuserGR.addQuery('department.name', department);\nuserGR.addQuery('location.name', location);\nuserGR.query();\n\nvar count = 0;\nwhile (userGR.next()) {\n    // Check if the user already has the role\n    var userRoleGR = new GlideRecord('sys_user_has_role');\n    userRoleGR.addQuery('user', userGR.sys_id);\n    userRoleGR.addQuery('role', roleGR.sys_id);\n    userRoleGR.query();\n    if (!userRoleGR.next()) {\n        // Assign the role to the user\n        var newUserRoleGR = new GlideRecord('sys_user_has_role');\n        newUserRoleGR.initialize();\n        newUserRoleGR.user = userGR.sys_id;\n        newUserRoleGR.role = roleGR.sys_id;\n        newUserRoleGR.insert();\n        count++;\n    }\n}\n\ngs.info('Assigned role \"' + roleName + '\" to ' + count + ' users.');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Share - Reports/readme.md",
    "content": "How often we come across cases where report created by one user needs to be shared with multiple users. We can use the 'Share' option but will be time consuming.\nImagine being able to do it via script by just passing sys_id of users.\nUse this script to share report in bulk by executing it as Background script\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Share - Reports/script.js",
    "content": "var list = \"4b6c76e3dbcfaf0000ec7abe3b961912,9e67d6d1db736300d43c54e848961934\"; //pass user sys_id here who needs access to reports\nvar arr = list.split(\",\");\nfor(i=0;i<arr[i].length;i++){\n    var rep = new GlideRecord('sys_report');\n    rep.addEncodedQuery('sys_created_by=abc@gmail.com');//replace with appropriate user mail who creaed the report\n    rep.query();\n    if(rep.next()){\n        var usr = new GlideRecord('sys_report_users_groups');\n        usr.initilize();\n        usr.user_id = arr[i];\n        usr.report_id = rep.sys_id;\n        usr.insert();\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Update Tables/BulkUpdateWithConditions.js",
    "content": "\n\n/**\n * Performs a bulk update on a specified table, applying the given data to all records that match the query.\n *\n * @param {string} table - The name of the table where the bulk update is to be performed.\n * @param {string} query - The encoded query string that filters which records to update.\n * @param {Object} data - An object representing the field-value pairs to update. \n *                        Each key is a field name, and the value is the new value for that field.\n *\n * Example usage:\n * bulkUpdate('incident', 'priority=1^state=2', { priority: 2, state: 3 });\n * \n * This updates all incidents where priority is 1 and state is 2, setting priority to 2 and state to 3.\n */\nfunction bulkUpdate(table, query, data) {\n\n    var getRecord = new GlideRecord(table);\n    getRecord.addEncodedQuery(query);\n    getRecord.query();\n    while (getRecord.next()) {\n        for (var field in data) {\n            if (data.hasOwnProperty(field)) {\n\t\t\t\tgetRecord.setValue(field, data[field]);\n            }\n        }\n\t\tgetRecord.update();\n    }\n}\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Update Tables/README.md",
    "content": "# Bulk Update Function Documentation - Use the code/function to bulk change some fields in any tables.\n\n## `bulkUpdate(table, query, data)`\n\nPerforms a bulk update on a specified table, applying the given data to all records that match the query.\n\n### Parameters\n\n- **`table`** (`string`): The name of the table where the bulk update is to be performed.\n- **`query`** (`string`): The encoded query string that filters which records to update.\n- **`data`** (`Object`): An object representing the field-value pairs to update. \n  - Each key is a field name, and the value is the new value for that field.\n\n### Example Usage\n\n```javascript\nbulkUpdate('incident', 'priority=1^state=2', { priority: 2, state: 3 });\n```\n \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Update of Fulfillment Group References in Published KB Articles/README.md",
    "content": "Description:\nThis background script is used to automatically update the content of all published Knowledge Base articles by replacing outdated group references, ensuring consistency and relevance across documentation without the need to manually check out and edit each article\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Bulk Update of Fulfillment Group References in Published KB Articles/script.js",
    "content": "var old_reference = \"Service desk\"; // Old group name\nvar new_reference = \"Help desk\"; // New group name\n\nvar regexPattern = new RegExp('(?is)'+ old_reference, 'gi'); // Building Regex to generate case-insensitive pattern\n\nvar kb_article = new GlideRecord('kb_knowledge');\nkb_article.addEncodedQuery('workflow_state=published');\nkb_article.query();\nwhile(kb_article.next()){\n\tkb_article.text = kb_article.text.replace(regexPattern,new_reference); // Replacing the old group reference with the new group\n\tkb_article.setWorkflow(false);\n\tkb_article.update();\n\tgs.info('Updated Article: ' + kb_article.number);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Calculate Week/Readme.md",
    "content": "This script determines the number of weeks between the start and end date\nProvide the date value in start start_date and end_date in the script to calculate number of weeks between 2 dates  \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Calculate Week/week_calculation.js",
    "content": "//Determines the number of weeks between the start and end dates\nvar startDate = new GlideDateTime(<start_date>);\nvar endDate = new GlideDateTime(<end_date>);\n\nvar millisecondsBetween = endDate.getNumericValue() - startDate.getNumericValue();\nvar weeks = millisecondsBetween / (1000 * 60 * 60 * 24 * 7);\n\nvar weeks_roundoff =  Math.floor(weeks);\n\ngs.info(weeks_roundoff);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Capitalize Title Words/CapitalizeTitleWords.js",
    "content": "/*\n* According to MLA Format, the first and last words will always be capitalized even if normally excluded.\n* Otherwise, you can add to the list of excluded words via the \"excludedWords\" array in the \"processTitle\" function.\n* Set the \"title\" variable to the text you want to format. Often this will be a Short Description or similar field. Or you can call \"processTitle\" and pass in the string\n* You can use a Business Rule (on insert) to make the modifications and provide more consistent formatting even when you have users who love to exclusively use lower case for everything.\n*/\n\nvar title = \"the ultimate short description of the 21st century\";\n\ngs.info(processTitle(title));\n\nfunction processTitle(title) {\n  var excludedWords = [\"a\", \"and\", \"as\", \"at\", \"but\", \"by\", \"down\", \"for\", \"from\", \"if\", \"in\", \"into\", \"like\", \"near\", \"nor\", \"of\", \"off\", \"on\", \"once\", \"onto\", \"or\", \"over\", \"past\", \"so\", \"than\", \"that\", \"the\", \"to\", \"upon\", \"when\", \"with\", \"yet\"];\n  \n\tvar indexList = [0];\n\tvar startIndex = -1;\n\tvar currentToken = [];\n\tfor (var i = 0; i < title.length; i++) {\n\t\tvar c = title[i];\n\t\tvar cNum = c.charCodeAt(0);\n\t\tif ((cNum >= 65 && cNum <= 90) || (cNum >= 97 && cNum <= 122) || (cNum >= 48 && cNum <= 57) || (cNum == 39)) {\n\t\t\tif (currentToken.length == 0)\n\t\t\t\tstartIndex = i;\n\t\t\tcurrentToken.push(c);\n\t\t} else {\n\t\t\tif (excludedWords.indexOf(currentToken.join(\"\")) == -1) {\n\t\t\t\tindexList.push(startIndex);\n\t\t\t}\n\t\t\tcurrentToken = [];\n\t\t}\n\t}\n\n\tindexList.push(startIndex);\n\n\tvar titleArray = [];\n\tfor (var i2 = 0; i2 < title.length; i2++) {\n\t\tif (indexList.indexOf(i2) != -1) {\n\t\t\ttitleArray.push(title[i2].toUpperCase());\n\t\t} else {\n\t\t\ttitleArray.push(title[i2]);\n\t\t}\n\t}\n\n\treturn titleArray.join(\"\");\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Capitalize Title Words/README.md",
    "content": "This script will take a string and capitalize words that are not part of an exclusion list.\nThe first and last words will always be capitalized even if normally excluded, according to MLA Format.\n\nThis is great for providing more consistently formatted Titles and Short Descriptions, especially when you have those users who just LOVE to exclusively use lowercase for everything.\n\nYou can use this code in a Business Rule (on insert) to make the modifications and provide more consistent formatting where applicable.\n\nYou can add to or remove from the list of excluded words via the \"excludedWords\" array in the \"processTitle\" function.\n\nSet the \"title\" variable to the text you want to format. Often this will be a Short Description or similar field. Or you can call the \"processTitle\" function and pass in the string\n\n*** An example run ***\n\nBefore String:\nthe ultimate short description of the 21st century\n\nAfter String:\nThe Ultimate Short Description of the 21st Century\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Capture Scheduled Job in an Update set/README.md",
    "content": "Scheduled Job Update Set Capture Script\n\nThis ServiceNow background script addresses a critical deployment challenge by programmatically capturing scheduled jobs in update sets. \nBy default, ServiceNow scheduled jobs are not automatically captured in update sets, making them difficult to migrate between environments. \nThis script uses the GlideUpdateManager2 API to force a scheduled job record into the current update set, enabling seamless deployment through standard update set processes.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Capture Scheduled Job in an Update set/captureScheduledJob.js",
    "content": "var gr = new GlideRecord('sysauto_script');\ngr.get('<sys_id of your scheduled job>');\nvar gum = new GlideUpdateManager2();\ngum.saveRecord(gr);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Capturing a record in to the current update set/Capturing a record in to the current update set using background script.js",
    "content": "var gr = new GlideRecord('<table_name>');//in <table_name> provide the table name in which the record is present\ngr.get('<sys_id of the record>');//in <sys_id of the record> provide the sys_id of the record which you need to capture in the update set\nvar gum = new GlideUpdateManager2(); //more details on GlideUpdateManager2 API is provided in the readme.md file\ngum.saveRecord(gr);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Capturing a record in to the current update set/README.md",
    "content": "Using this script present in \"Capturing a record in to the current update set using background script.js\" file we can capture a record from a table (eg; groups, approval configurations) to the current update set\n\nWe have to provide the table name and the sys_id of the record properly as mentioned in the script.\n\nWhen using the GlideUpdateManager2 API, a record is created in the sys_update_version table, and an XML file is created under the customer update folder because it is a part of the mechanism that allows adding records to an update set.\n\nGlideUpdateManager2() will only work in global scope. If you try to create an update sect in scoped application and try to use GlideUpdateManager2 API then it will capture the update in the crrent scoped update set but the update will be in global scope. So there will be conflict while moving the update set.\n\nNote: GlideUpdateManager2 API is undocumented API.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Change Approver/BgScript.js",
    "content": "\nvar ab=new GlideRecord('sysapproval_approver');\nab.addQuery('sysapproval','<Approval_record_sys_id>');\nab.query();\nif(ab.next())\n{\nab.approver.setDisplayValue('<User_Display_Name>')';\nab.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Change Approver/README.md",
    "content": "Use the code to change approver for any record from background script\nReplace <Approval_record_sys_id> with sys_id of record for which approval is triggered\nReplace <User_Display_Name> with name of user who needs to be set as approver\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Change Update Set Application Scope/README.md",
    "content": "# Change update set application scope\nIn ServiceNow, there are instances where an update set is mistakenly created in the incorrect application scope. To rectify this, I've developed a background script that facilitates the alteration of the update set's application scope to the appropriate one.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Change Update Set Application Scope/changeApplicationScope.js",
    "content": "function changeUpdateSetApplicationScope(updateSetName, currentApplicationScope, newApplicationScope) {\n\n    var newApplicationScopeSysId = getApplicationScopeSysId(newApplicationScope);\n\n    var updateSetGR = new GlideRecord(\"sys_update_set\");\n    updateSetGR.addQuery('name', updateSetName);\n    updateSetGR.addQuery('application.name', currentApplicationScope);\n    updateSetGR.query();\n\n    if (updateSetGR.next()) {\n        updateSetGR.setValue('application', newApplicationScopeSysId);\n        updateSetGR.update();\n    }\n\n}\n\nfunction getApplicationScopeSysId(scopeName) {\n  \n    var appGR = new GlideRecord('sys_app');\n    appGR.addQuery('name', scopeName);\n    appGR.query();\n    if (appGR.next()) {\n        return appGR.getValue('sys_id');\n    }\n}\n\nchangeUpdateSetApplicationScope('updateSetName', 'currentApplicationScope', 'newApplicationScope');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Check Record Creation over 90 days and output age/README.md",
    "content": "**Usecase**:\nThis piece of code can be used to check whether a record was created more than 90 days ago or not.\nIt returns an output of true/false as well as the actual age (in days) of the record.\n\n**Type**: \nBackground Script\n\n**How it works**:\nThere is a pastDateString variable which can contain a date-time which you received by querying any particular record's sys_created_on field or any other relevant field. \nIn the actual code I have hard-coded a value to be used as an example.\n-There are two gliderecord objects to store the current date as well as past date\n-GlideDateTime.subtract() is used to calculate the time difference as a GlideDuration object\n-The time duration in milliseconds is converted to days by dividing with 86400000(milliseconds in a day)\n-Comparison is done between the results\n-The final age of the record and true/false result of the comparison is the output.\n[Note: Output may vary according to timezones]\n\n**Example**:\npastDateString = 2025-05-01 10:00:00\nOutput:\n\n    Record Age (Days): 165\n    Older than 90 days? true\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Check Record Creation over 90 days and output age/checkdateover90.js",
    "content": "//the pastDateString variable can contain a date-time which you received by querying any particular record's sys_created_on field or any other relevant field. Here I'm hard-coding a value.\n\nvar pastDateString = '2025-05-01 10:00:00'; //input date and time\nvar ageThresholdDays = 90;\n\nvar gdtPast = new GlideDateTime(pastDateString);\nvar gdtNow = new GlideDateTime();\nvar duration = GlideDateTime.subtract(gdtPast, gdtNow);\nvar durationMs = duration.getNumericValue();\n\n//Calculate the total days (1 day = 86,400,000 milliseconds)\nvar totalDaysOld = Math.floor(durationMs / 86400000);\n\nvar isOlderThanThreshold = false;\nif (totalDaysOld > ageThresholdDays) {\n    isOlderThanThreshold = true;\n}\n\ngs.info(\"Record Age (Days): \" + totalDaysOld);\ngs.info(\"Older than \" + ageThresholdDays + \" days? \" + isOlderThanThreshold);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Check String is Valid JSON/README.md",
    "content": "# Function to check if string is a valid JSON\n\n## Problem statement\nWhen working with serialized data, you might come across some malformed or invalid JSON strings from time to time. While JavaScript doesn't have a built-in validation method for JSON, it has a handy JSON.parse() method that can be used to check if a string is a valid JSON format.\n\n## Description\nThe `isValidJSON` function checks if a given string is a valid JSON format. It tries to parse the string and returns `true` if the parsing is successful and `false` if an error occurs during the parsing process. \n\n## Usage\nThis function is useful for validating JSON strings before attempting to use them in your application. \n\n## Examples\n\n### Example 1\n```javascript\nconst str = '{ \"firstName\": \"John\", \"lastName\": \"Doe\" }';\n```\nThis will output: `String is valid JSON`\n\n### Example 2\n```javascript\nconst invalidStr = '{ firstName: John, lastName: Doe }';\n```\nThis will output: `String is not valid JSON`\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Check String is Valid JSON/checkStringisValidJson.js",
    "content": "// Function to check if a string is a valid JSON\nfunction isValidJSON(str) {\n    try {\n        // Try to parse the string as JSON\n        JSON.parse(str);\n    } catch (e) {\n        // If an error occurs, the string is not valid JSON\n        return false;\n    }\n    // If no error occurs, the string is valid JSON\n    return true;                    \n}\n\n// Example JSON string\nconst str = '{ \"firstName\":\"John\" , \"lastName\": \"Doe\"}'; \n\n// Check if the string is valid JSON and log the result\nif (isValidJSON(str)) {\n  console.log('String is valid JSON');\n} else {\n  console.log('String is not valid JSON');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Check for duplicates on multiple criteria/README.md",
    "content": "Use this script to check for duplicates (and delete if necessary) in a table based on 2 or more criteria. \nFor reference: https://www.servicenow.com/community/developer-blog/search-for-duplicates-delete-based-on-2-columns/ba-p/2279274\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Check for duplicates on multiple criteria/check-for-ducplicates.js",
    "content": "deleteDuplicates('table_name', 'criteria1', 'criteria2');\n\nfunction deleteDuplicates(tableName, field1, field2){\n\n// declare an array\nvar dupRecords = [];\nvar duplicateCheck = new GlideAggregate(tableName);\nduplicateCheck.addNotNullQuery(field1);\nduplicateCheck.addNotNullQuery(field2);\nduplicateCheck.groupBy(field1);\nduplicateCheck.groupBy(field2);\nduplicateCheck.addHaving('COUNT', '>', 1); // addHaving func won't work in scope app\nduplicateCheck.query();\nwhile(duplicateCheck.next()) {\nvar jsonObj = {}; // declare a json object\njsonObj[field1] = duplicateCheck[field1].toString();\njsonObj[field2] = duplicateCheck[field2].toString()\ndupRecords.push(jsonObj);\n}\n\nvar jsonString = JSON.stringify(dupRecords); // convert json object to string\n\nvar parser = new JSONParser();\nvar parsedData = parser.parse(jsonString);\nvar length = parsedData.length;\n\nfor(var i=0; i<length; i++){\n\nvar encodedQuery = field1 + '=' + parsedData[i][field1] + '^' + field2 + '=' + parsedData[i][field2];\n\nvar tableRec = new GlideRecord(tableName);\ntableRec.addEncodedQuery(encodedQuery);\ntableRec.query();\nif(tableRec.next()){\ngs.info('Repeated Data is: User -> ' + tableRec.getDisplayValue('user') + ' Group -> ' + tableRec.getDisplayValue('group'));\ntableRec.deleteRecord();\n}\n}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Clone User Groups/cloneUserGroups.js",
    "content": "  // Replace with the sys_ids of the users\n    var sourceUserSysId = 'SOURCE_USER_SYS_ID'; // Copy groups from this user\n    var targetUserSysId = 'TARGET_USER_SYS_ID'; // Copy groups to this user\n\n    // Validate both users exist\n    var sourceUser = new GlideRecord('sys_user');\n    if (!sourceUser.get(sourceUserSysId)) {\n        gs.error('Source user not found: ' + sourceUserSysId);\n        return;\n    }\n\n    var targetUser = new GlideRecord('sys_user');\n    if (!targetUser.get(targetUserSysId)) {\n        gs.error('Target user not found: ' + targetUserSysId);\n        return;\n    }\n\n    gs.info('Cloning group memberships from ' + sourceUser.name + ' to ' + targetUser.name);\n\n    var addedCount = 0;\n    var skippedCount = 0;\n\n    var srcGroups = new GlideRecord('sys_user_grmember');\n    srcGroups.addQuery('user', sourceUserSysId);\n    srcGroups.query();\n\n    while (srcGroups.next()) {\n        var groupId = srcGroups.group.toString();\n\n        // Check if target user is already in the group\n        var existing = new GlideRecord('sys_user_grmember');\n        existing.addQuery('user', targetUserSysId);\n        existing.addQuery('group', groupId);\n        existing.query();\n\n        if (existing.next()) {\n            skippedCount++;\n            gs.info('Skipped: ' + targetUser.name + ' is already a member of group ' + srcGroups.group.name);\n            continue;\n        }\n\n        // Add target user to the group\n        var newMember = new GlideRecord('sys_user_grmember');\n        newMember.initialize();\n        newMember.user = targetUserSysId;\n        newMember.group = groupId;\n        newMember.insert();\n\n        gs.info('Added ' + targetUser.name + ' to group ' + srcGroups.group.name);\n        addedCount++;\n    }\n\n    gs.info('Group cloning complete. ' + addedCount + ' groups added, ' + skippedCount + ' skipped.');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Clone User Groups/readme.md",
    "content": "This ServiceNow Background Script copies all group memberships from one user to another. It checks for duplicates and logs all actions.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Clone User Record/README.md",
    "content": "Clone a user to another user after creating a new user from SN and run the below script from Background Script under System Definition app. \nThe sys id's are needed from both users to run the background script.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Clone User Record/cloneUserRec.js",
    "content": "createUserRoles();\nfunction createUserRoles(){\nvar gr = new GlideRecord('sys_user_has_role');\ngr.addQuery('user', '<old sys id for that user instance>'); //old sys_id\ngr.query();\nwhile(gr.next()){\nvar gr1 = new GlideRecord('sys_user_has_role');\ngr1.initialize();\ngr1.user = '<new sys id for that user instance>';//new user sys_id\ngr1.role = gr.role;\ngr1.insert();\n}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Clone User with Roles and Groups/README.md",
    "content": "# Clone User with Roles and Groups\n\nA background script that clones an existing user's profile including all their roles and group memberships to a new user account.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content\n3. Update the function call at the bottom with the source and target user IDs:\n   ```javascript\n   cloneUser('source.username', 'new.username');\n   ```\n4. Click \"Run script\"\n\n## What It Does\n\nThe script:\n1. Creates a new user record with the specified username\n2. Copies all field values from the source user to the new user (except fields already set)\n3. Clones all directly assigned roles (excludes inherited roles)\n4. Clones all group memberships\n5. Returns the sys_id of the newly created user\n\n## Use Cases\n\n- Onboarding new team members with similar access needs\n- Creating test users with specific role/group combinations\n- Setting up backup user accounts with identical permissions\n- Standardizing user setup based on role templates\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Clone User with Roles and Groups/cloneUser.js",
    "content": "function cloneUser(currentUserId, newUserId) {\n\n    var newUserRecordSysId = createNewUserRecord(newUserId);\n    cloneCurrentUserFields(currentUserId, newUserId);\n    cloneUserRoles(currentUserId, newUserRecordSysId);\n    cloneUserGroups(currentUserId, newUserRecordSysId);\n}\n\nfunction cloneCurrentUserFields(currentUserId, newUserId) {\n\n    var currentUserGR = new GlideRecord(\"sys_user\");\n    currentUserGR.addQuery(\"user_name\", currentUserId);\n    currentUserGR.query();\n\n    if (currentUserGR.next()) {\n        var newUserGR = new GlideRecord(\"sys_user\");\n        newUserGR.addQuery(\"user_name\", newUserId);\n        newUserGR.query();\n        if (newUserGR.next()) {\n            var userGRU = new GlideRecordUtil();\n            var fieldList = userGRU.getFields(currentUserGR);\n            for (var index = 0; index < fieldList.length; index++) {\n                var fieldName = fieldList[index];\n                if (!newUserGR.getValue(fieldName)) {\n                    newUserGR.setValue(fieldName, currentUserGR.getValue(fieldName));\n                    newUserGR.update();\n                }\n            };\n        }\n    }\n}\n\nfunction createNewUserRecord(userId) {\n\n    var userGR = new GlideRecord(\"sys_user\");\n    userGR.initialize();\n    userGR.setValue(\"user_name\", userId);\n    var sysId = userGR.insert();\n    return sysId;\n}\n\nfunction cloneUserRoles(currentUserId, newUserRecordSysId) {\n\n    var currentUserRoleGR = new GlideRecord(\"sys_user_has_role\");\n    currentUserRoleGR.addQuery('user.user_name', currentUserId);\n    currentUserRoleGR.addQuery('inherited', 'false');\n    currentUserRoleGR.query();\n\n    while (currentUserRoleGR.next()) {\n        var newUserRoleGR = new GlideRecord(\"sys_user_has_role\");\n        newUserRoleGR.initialize();\n        newUserRoleGR.setValue('user', newUserRecordSysId);\n        newUserRoleGR.setValue('role', currentUserRoleGR.getValue('role'));\n        newUserRoleGR.insert();\n    }\n}\n\nfunction cloneUserGroups(currentUserId, newUserRecordSysId) {\n\n    var currentUserGroupGR = new GlideRecord(\"sys_user_grmember\");\n    currentUserGroupGR.addQuery('user.user_name', currentUserId);\n    currentUserGroupGR.query();\n\n    while (currentUserGroupGR.next()) {\n        var newUserGroupGR = new GlideRecord(\"sys_user_grmember\");\n        newUserGroupGR.initialize();\n        newUserGroupGR.setValue('user', newUserRecordSysId);\n        newUserGroupGR.setValue('group', currentUserGroupGR.getValue('group'));\n        newUserGroupGR.insert();\n    }\n}\n\ncloneUser('currentUserId', 'newUserId'); //currentUserId: Id of the you that we want to clone, newUserId: Id of the new user record. \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Compare Roles Between Two Users/README.md",
    "content": "# Compare Roles of Two Users\n\nProvide the usernames of two users and this script will print out the roles they share and the roles they don't share.\n\n> ⚠️ **Note:** ServiceNow provides out-of-the-box functionality for comparing user access via the **Access Analyzer** . You can learn more here: https://www.servicenow.com/docs/bundle/zurich-platform-security/page/integrate/identity/task/comparing-access-controlss**\n>\n**Parameters:** \n- **includeInheritedRoles:**\n  - `false` – only directly assigned roles  \n  - `true` – include roles inherited from other roles or groups\n\n- **usernameA**  \n  - Username of a `sys_user`\n\n- **usernameB**  \n  - Username of a `sys_user`\n\nThe script will output:\n- Roles exclusive to user A\n- Roles exclusive to user B\n- Shared roles\n\n\n## Example Result\n![compare-roles](example-output.PNG)\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Compare Roles Between Two Users/compare-roles-2-users.js",
    "content": "(function() {\n    // Configuration: Set to true to include inherited roles, false for directly assigned roles only\n    var includeInheritedRoles = false;\n\n    // Usernames to compare replace abel.tuter and abraham.lincoln with the user names you want to compare\n    var usernameA = \"abel.tuter\";\n    var usernameB = \"abraham.lincoln\";\n\n    /**\n     * Fetch active roles for a given user.\n     * @param {string} username - The username to query roles for.\n     * @param {boolean} includeInherited - Whether to include inherited roles.\n     * @returns {Array} - Array of unique role names.\n     */\n    function getUserRoles(username, includeInherited) {\n        var roles = [];\n        var gr = new GlideRecord(\"sys_user_has_role\");\n        gr.addQuery(\"user.user_name\", username);\n        if (!includeInherited) {\n            gr.addQuery(\"inherited\", false);\n        }\n        gr.addQuery(\"state\", \"active\");\n        gr.query();\n        while (gr.next()) {\n            var roleName = gr.role.name.toString();\n            // Ensure uniqueness\n            if (roles.indexOf(roleName) === -1) {\n                roles.push(roleName);\n            }\n        }\n        return roles;\n    }\n\n    /**\n     * Get items in list1 that are not in list2.\n     * @param {Array} list1\n     * @param {Array} list2\n     * @returns {Array}\n     */\n    function difference(list1, list2) {\n        var result = [];\n        for (var i = 0; i < list1.length; i++) {\n            if (list2.indexOf(list1[i]) === -1) {\n                result.push(list1[i]);\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Get items that exist in both lists.\n     * @param {Array} list1\n     * @param {Array} list2\n     * @returns {Array}\n     */\n    function intersection(list1, list2) {\n        var result = [];\n        for (var i = 0; i < list1.length; i++) {\n            if (list2.indexOf(list1[i]) !== -1 && result.indexOf(list1[i]) === -1) {\n                result.push(list1[i]);\n            }\n        }\n        return result;\n    }\n\n    // Fetch roles for both users\n    var rolesUserA = getUserRoles(usernameA, includeInheritedRoles);\n    var rolesUserB = getUserRoles(usernameB, includeInheritedRoles);\n\n    // Compare roles\n    var exclusiveToA = difference(rolesUserA, rolesUserB);\n    var exclusiveToB = difference(rolesUserB, rolesUserA);\n    var sharedRoles = intersection(rolesUserA, rolesUserB);\n\n    // Output results\n    gs.info(\"\\nExclusive Role(s) to \" + usernameA + \":\\n\\t\" + exclusiveToA.join(\"\\n\\t\"));\n    gs.info(\"\\nExclusive Role(s) to \" + usernameB + \":\\n\\t\" + exclusiveToB.join(\"\\n\\t\"));\n    gs.info(\"\\nShared Role(s):\\n\\t\" + sharedRoles.join(\"\\n\\t\"));\n})();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Console timing API/README.md",
    "content": "**This can be used in Yokohama++ instances**\n\nAs part of Yokohama release the problem of logging and calculating time taken by a piece of script has been addressesed by bringing standardized timing tools to the platform, enabling precise performance analysis with minimal effort and developers can isolate slow operations.\n\nUse Case: Query overall incident record and bulk update with specific values. Need time taken for query and update.\n\n\n​console.time(label)\n   Starts a timer with a unique label. Use this at the beginning of a code block you want to measure.  \n \n​console.timeLog(label, [data])\n   Logs the current duration of the timer without stopping it. Optional data can be appended for context.  \n \n​console.timeEnd(label)\n   Stops the timer and logs the total execution time. \n\nServiceNow Documentation: https://www.servicenow.com/docs/bundle/yokohama-api-reference/page/app-store/dev_portal/API_reference/Console/concept/ConsoleAPI.html\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Console timing API/code.js",
    "content": "/* As part of Yokohama release the problem of logging and calculating time taken by a piece of script has been addressesed by bringing standardized timing tools to the platform, enabling precise performance analysis with minimal effort and developers can isolate slow operations.\n\nUse Case: Query overall incident record and bulk update with specific values. Need time taken for query and update.\n*/\n\n/*\n​console.time(label)\n   Starts a timer with a unique label. Use this at the beginning of a code block you want to measure.  \n \n​console.timeLog(label, [data])\n   Logs the current duration of the timer without stopping it. Optional data can be appended for context.  \n \n​console.timeEnd(label)\n   Stops the timer and logs the total execution time. \n*/\n\nconsole.time(\"ProcessIncident\");\n\nvar incidents = fetchData();\nconsole.timeLog(\"ProcessIncident\", \"Data fetched\");\n\ntransformData(incidents);\nconsole.timeLog(\"ProcessIncident\", \"Data transformed\");\n\nconsole.timeEnd(\"ProcessIncident\");\n\nfunction fetchData() {\n    var gr = new GlideRecord(\"incident\");\n    gr.addQuery(\"active\", \"true\");\n    gr.query();\n\n    var results = [];\n    while (gr.next()) {\n        results.push(gr.getUniqueValue()); \n    }\n    return results;\n}\n\nfunction transformData(incidentIds) {\n    var count = 0;\n    for (var i = 0; i < incidentIds.length && count < 10; i++) {\n        var gr = new GlideRecord(\"incident\");\n        if (gr.get(incidentIds[i])) {\n            gr.setValue(\"state\", \"7\");\n            gr.setValue(\"close_code\", \"known error\");\n            gr.setValue(\"close_notes\", \"Incident closed as part of bulk close\");\n            gr.setValue(\"work_notes\", \"Incident closed as part of bulk close\");\n            gr.update();\n            count++;\n        }\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert Date Time/README.md",
    "content": "This is the simple code to run independently from AES -> Background Scripts module and convert the datetime from one time zone to other\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert Date Time/convertTimeZone.js",
    "content": "var strConvertedDateTime=new GlideScheduleDateTime(\"2022-03-03 06:30:00\").convertTimeZone(\"CET\", \"IST\"); // Instantiate the object by passing the timezones\nvar gdtConvertedDateTime = new GlideDateTime(strConvertedDateTime); //Call the method to convert the date time from CET to IST\ngs.info(gdtConvertedDateTime); //Print the converted value\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert Incident Records to JSON/README.md",
    "content": "### Overview of Converting Incident Records to JSON\n\nThe provided script fetches all active incident records from the ServiceNow database and converts them into a JSON format.  \n\n### Importance of Understanding JSON in ServiceNow\n\nIn today's complex IT environments, effective data interchange between systems is crucial. JSON has become a standard format for APIs and data exchange due to its simplicity and readability. By understanding how to manipulate JSON in ServiceNow, developers can integrate more seamlessly with external applications, automate workflows, and enhance data processing capabilities. The ability to convert records into JSON format is foundational for working with REST APIs, enabling the sharing of information across platforms. Therefore, providing basic code examples that demonstrate these principles can help developers grasp the importance of JSON, fostering a deeper understanding of how to implement more complex logic in ServiceNow. This foundational knowledge is essential for anyone looking to leverage the full potential of ServiceNow's capabilities in modern IT service management.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert Incident Records to JSON/Readme.md",
    "content": "# Active Incidents JSON Export – ServiceNow\n\nThis repository contains **two approaches** to fetch active incidents from ServiceNow and convert them to JSON. Both use `GlideRecord` but differ in flexibility and readability.\n\n---\n\n## 1. Simple Approach\n\nThis method fetches a **fixed set of fields** and converts them directly to JSON.\n\n```javascript\nvar incidents = [];\nvar gr = new GlideRecord('incident');\ngr.addQuery('active', true);\ngr.query();\nwhile (gr.next()) {\n    incidents.push({\n        number: gr.number.toString(),\n        short_description: gr.short_description.toString(),\n        state: gr.state.toString(),\n        assigned_to: gr.assigned_to.getDisplayValue('name'),\n        created_on: gr.sys_created_on.getDisplayValue()\n    });\n}\n\nvar jsonOutput = JSON.stringify(incidents);\ngs.info(jsonOutput);\n```\n\n### ✅ Pros\n- Simple and easy to implement\n- Works fine for a fixed set of fields\n- Direct JSON output\n\n### ❌ Cons\n- Fields are hard-coded → not reusable for other tables\n- Reference fields handling is not dynamic\n- Numeric state values are not human-readable\n\n---\n\n## 2. Flexible & Dynamic Approach\n\nThis method allows dynamic fields, handles reference fields, and adds human-readable state labels.\n\n```javascript\nvar tableName = 'incident';\nvar fieldsToInclude = ['number', 'short_description', 'state', 'assigned_to', 'sys_created_on'];\nvar incidentList = [];\n\nvar gr = new GlideRecord(tableName);\ngr.addQuery('active', true);\ngr.query();\n\nwhile (gr.next()) {\n    var incidentObj = {};\n    \n    fieldsToInclude.forEach(function(field) {\n        if (gr.isValidField(field) && gr[field].getRefRecord) {\n            incidentObj[field] = gr[field].getDisplayValue();\n        } else if (gr.isValidField(field)) {\n            incidentObj[field] = gr[field].toString();\n        } else {\n            incidentObj[field] = null;\n        }\n    });\n\n    // Map numeric state to human-readable label\n    var stateMap = {\n        '1': 'New',\n        '2': 'In Progress',\n        '3': 'On Hold',\n        '6': 'Resolved',\n        '7': 'Closed'\n    };\n    incidentObj.state_label = stateMap[incidentObj.state] || 'Unknown';\n\n    incidentList.push(incidentObj);\n}\n\nvar jsonOutput = JSON.stringify(incidentList, null, 2);\ngs.info(\"Here's your JSON for active incidents:\\n\" + jsonOutput);\n```\n\n### ✅ Pros\n- Dynamic → easily reusable for any table and fields\n- Handles reference fields gracefully\n- Adds human-readable state labels\n- JSON is formatted for readability\n- Checks `isValidField` to prevent errors\n\n### ❌ Cons\n- Slightly more complex than the simple version\n- Requires manual mapping for fields like state labels\n\n\n## Summary\n\n- **Simple Approach**: Best for quick tasks with fixed fields\n- **Flexible Approach**: Best for reusable scripts, handling dynamic tables, reference fields, and human-readable output"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert Incident Records to JSON/convert code.js",
    "content": "var incidents = [];\nvar gr = new GlideRecord('incident');\ngr.addQuery('active', true);\ngr.query();\nwhile (gr.next()) {\n    incidents.push({\n        number: gr.number.toString(),\n        short_description: gr.short_description.toString(),\n        state: gr.state.toString(),\n        assigned_to: gr.assigned_to.getDisplayValue('name'),\n        created_on: gr.sys_created_on.getDisplayValue()\n    });\n}\n\nvar jsonOutput = JSON.stringify(incidents);\ngs.info(jsonOutput);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert Incident Records to JSON/convert incidents to JSON.js",
    "content": "// Table and fields you want to include\nvar tableName = 'incident'; // Change to any table you like\nvar fieldsToInclude = ['number', 'short_description', 'state', 'assigned_to', 'sys_created_on'];// Change whichever field you wish for\n\n// Store all incident data here\nvar incidentList = [];\n\n// Get active incidents\nvar gr = new GlideRecord(tableName);\ngr.addQuery('active', true);\ngr.query();\n\n// Go through each record and build a friendly object\nwhile (gr.next()) {\n    var incidentObj = {};\n    \n    fieldsToInclude.forEach(function(field) {\n        if (gr.isValidField(field) && gr[field].getRefRecord) {\n            // Get display value for reference fields\n            incidentObj[field] = gr[field].getDisplayValue();\n        } else if (gr.isValidField(field)) {\n            incidentObj[field] = gr[field].toString();\n        } else {\n            incidentObj[field] = null;\n        }\n    });\n\n    // Add human-readable state\n    var stateMap = {\n        '1': 'New',\n        '2': 'In Progress',\n        '3': 'On Hold',\n        '6': 'Resolved',\n        '7': 'Closed'\n    };\n    incidentObj.state_label = stateMap[incidentObj.state] || 'Unknown';\n\n    incidentList.push(incidentObj);\n}\n\n// Convert the list to JSON in a readable format\nvar jsonOutput = JSON.stringify(incidentList, null, 2);\n\n// Show the JSON output in system logs\ngs.info(\"Here’s your JSON for active incidents:\\n\" + jsonOutput);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert comma separated values in string to columns/README.md",
    "content": "1.Take any comma separated string to be displayed in columns\n2.Apply above logic\n3.update to field /Print /Use this in mail scripts to include in the notification.\n4. we can also add this arryUtils script include (to convert arry elements to colums)\n\n![image](https://user-images.githubusercontent.com/42912180/195672456-f282c82e-8516-4925-9e71-c80da8329ef3.png)\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Convert comma separated values in string to columns/script.js",
    "content": "var Str = \"Element1,Element2,Element3,Element4,Element5,Element6\";\nresult =[];\nvar myArray = Str.split(\",\");\nfor(var i =0; i<=myArray.length;i++){\nresult.push(myArray[i]);\n}\nvar output= \"\\n\" +result.join(\"\\n\");\ngs.info(output);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy Field Values and Insert in Target Record/README.md",
    "content": "\nScript Usage :\n\nThis function takes the parameters such as source table, source record sys_id, target table, fields that needs to be copied to target table.\n\nAs a validation check, the fields from source table should be similar to target else abort inserting the record.\n\n\nSame Code to invoke the function: \ncopyFieldsValidated(\n    'dmn_demand',\n    '8c10306edbc00810f777526adc961976',\n    'pm_project',\n    ['name', 'short_description']   //will throw error since name field not common in both tables\n);\n\n\ncopyFieldsValidated(\n    'dmn_demand',\n    '8c10306edbc00810f777526adc961976',\n    'pm_project',\n    ['short_description']   //Insert the record since short_description is common in both tables\n);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy Field Values and Insert in Target Record/script.js",
    "content": "/**\n * Copies specified fields from a source record to a newly created target record,\n * validating all fields before performing the insert operation.\n *\n * @param {string} sourceTable - The name of the source table.\n * @param {string} sourceId - The sys_id of the source record.\n * @param {string} targetTable - The name of the target table.\n * @param {string[]} fields - An array of field names to copy.\n * @returns {string|null} The sys_id of the newly created target record, or null on failure.\n */\nfunction copyFieldsValidated(sourceTable, sourceId, targetTable, fields) {\n    var src = new GlideRecord(sourceTable);\n    if (!src.get(sourceId)) {\n        gs.error(\"Source record not found in \" + sourceTable + \" with sys_id: \" + sourceId);\n        return null;\n    }\n\n    var tgt = new GlideRecord(targetTable);\n    tgt.initialize();\n    var allFieldsAreValid = true;\n    // First, validate all fields before doing any work\n    fields.forEach(function(f) {\n        if (!src.isValidField(f) || !tgt.isValidField(f)) {\n            gs.warn(\"Field [\" + f + \"] is not valid in both \" + sourceTable + \" and \" + targetTable + \". Aborting insert.\");\n            allFieldsAreValid = false;\n        }\n    });\n\n    // Proceed with copying and inserting only if all fields are valid\n    if (allFieldsAreValid) {\n        fields.forEach(function(f) {\n            tgt.setValue(f, src.getValue(f));\n        });\n        var newId = tgt.insert();\n        if (newId) {\n            gs.info(\"Record copied from \" + sourceTable + \" → \" + targetTable + \". New sys_id: \" + newId);\n            return newId;\n        } else {\n            gs.error(\"Failed to insert new record into \" + targetTable);\n            return null;\n        }\n    } else {\n        gs.error(\"Aborting record insert due to invalid fields.\");\n        return null;\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy Source User Group Memberships to Selected Users/Readme.md",
    "content": "Background Script — Copy Source User’s Groups to Specific Users\n\nWorking:\nIt retrieves all groups of the source user.\nLoops through all active users (except the source).\nChecks whether the user is already a member of that group.\nIf not, it inserts a new record in sys_user_grmember.\n\nNote:\nsourceUserSysId → sys_id of the user whose groups you want to copy.\nThe 3 entries in targetUserSysIds → sys_ids of the target users.\nIt checks for duplicates, so no errors even if the user is already in that group.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy Source User Group Memberships to Selected Users/copy_source_user_group_memberships_to_selected_users.js",
    "content": "// Background script in ServiceNow\n// Copies all group memberships from one user to specific other users\n\nvar sourceUserSysId = 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // sys_id of the source user\nvar targetUserSysIds = [\n    'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaa', // target user 1\n    'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbb', // target user 2\n    'cccccccccccccccccccccccccccccc'  // target user 3\n];\n\nvar sourceGroups = [];\n\n// Step 1: Fetch all groups of the source user\nvar grMember = new GlideRecord('sys_user_grmember');\ngrMember.addQuery('user', sourceUserSysId);\ngrMember.query();\n\nwhile (grMember.next()) {\n    sourceGroups.push(grMember.group.toString());\n}\ngs.info('Source user belongs to ' + sourceGroups.length + ' groups.');\n\n// Step 2: For each target user, add them to each group (if not already a member)\nfor (var i = 0; i < targetUserSysIds.length; i++) {\n    var targetUserId = targetUserSysIds[i];\n\n    for (var j = 0; j < sourceGroups.length; j++) {\n        var groupId = sourceGroups[j];\n\n        var existing = new GlideRecord('sys_user_grmember');\n        existing.addQuery('user', targetUserId);\n        existing.addQuery('group', groupId);\n        existing.query();\n\n        if (!existing.next()) {\n            var newMember = new GlideRecord('sys_user_grmember');\n            newMember.initialize();\n            newMember.user = targetUserId;\n            newMember.group = groupId;\n            newMember.insert();\n\n            gs.info('Added user [' + targetUserId + '] to group [' + groupId + ']');\n        } else {\n            gs.info('User [' + targetUserId + '] already in group [' + groupId + ']');\n        }\n    }\n}\n\ngs.info('--- Group replication completed successfully ---');\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy table fields from one table to another/Copy fields from one table to another",
    "content": "/*\nThis is a sample script, please run this script in non-prod environment first and test for all the scenarios. \nThis script could take few minutes to execute depending on number of fields to copy.\n*/\n\nvar copyFromTable = \"TABLE A\"; //Change the name of table here.\nvar copyToTable = \"TABLE B\"; //Change the name of table here.\nvar fieldsToCopy = [\"FIELD 1\",\"FIELD 2\",\"FIELD 3\"]; //Enter list of all fields separated by comma here which you want to copy.\nvar fieldCouner = 0;\n\nfor (var i = 0; i < fieldsToCopy.length; i++) {\n  var getField = new GlideRecord(\"sys_dictionary\");\n  getField.addQuery(\"name\", copyFromTable);\n  getField.addQuery(\"element\", fieldsToCopy[i]);\n  getField.query();\n  if (getField.next()) {\n    copyFields(copyToTable, getField);\n  } else {\n    gs.info(\n      \"Background Script: Could not find the field [{0}] on table [{1}]\",\n      fieldsToCopy[i],\n      copyFromTable\n    );\n  }\n}\n\nfunction copyFields(copyToTable, getField) {\n  var checkIfExists = new GlideRecord(\"sys_dictionary\");\n  checkIfExists.addQuery(\"name\", copyToTable);\n  checkIfExists.addQuery(\"element\", getField.element.toString());\n  checkIfExists.query();\n  if (checkIfExists.hasNext()) {\n    gs.info(\n      \"Background Script: Field with same name [{0}] already exists on table [{1}]\",\n      getField.element.toString(),\n      copyToTable\n    );\n  } else {\n    var fieldCreated = false;\n    var createNewField = new GlideRecord(\"sys_dictionary\");\n    createNewField.newRecord();\n    createNewField.setValue(\"name\", copyToTable);\n    createNewField.setValue(\"internal_type\", getField.internal_type.toString());\n    createNewField.setValue(\"column_label\", getField.column_label.toString());\n    createNewField.setValue(\"element\", getField.element.toString());\n    createNewField.setValue(\"max_length\", getField.max_length.toString());\n    createNewField.setValue(\"default_value\", getField.default_value.toString());\n    createNewField.setValue(\"choice\", getField.choice.toString());\n    createNewField.setValue(\"reference\", getField.reference.toString());\n    createNewField.setValue(\n      \"reference_qual_condition\",\n      getField.reference_qual_condition.toString()\n    );\n    createNewField.setValue(\"read_only\", getField.read_only.toString());\n    createNewField.setValue(\"mandatory\", getField.mandatory.toString());\n    createNewField.setValue(\"display\", getField.display.toString());\n    fieldCreated = createNewField.insert();\n\n    //Check if field is successfully created and field type is choice or strin or integer (all fields could have choices), if yes, also copy the choices.\n    if (\n      fieldCreated.length == 32 &&\n      (getField.internal_type.toString() == \"choice\" ||\n        getField.internal_type.toString() == \"string\" || getField.internal_type.toString() == \"integer\")\n    ) {\n      copyChoices(copyToTable, getField, createNewField.element.toString());\n    }\n    gs.info(\n      \"Background Script: Copy Finished - Copied field [{0}] from [{1}] to [{2}]\",\n      getField.element.toString(),\n      copyFromTable,\n      copyToTable\n    );\n  }\n}\n\nfunction copyChoices(copyToTable, getField,newFieldName) {\n  var choiceList = [];\n  var checkChoices = new GlideRecord(\"sys_choice\");\n  checkChoices.addQuery(\"name\", getField.name.toString());\n  checkChoices.addQuery(\"element\", getField.element.toString());\n  checkChoices.setLimit(1);\n  checkChoices.query();\n  if (checkChoices.hasNext()) {\n    var getChoices = new GlideRecord(\"sys_choice\");\n    getChoices.addQuery(\"name\", getField.name.toString());\n    getChoices.addQuery(\"element\", getField.element.toString());\n    getChoices.query();\n    while (getChoices.next()) {\n      choiceList.push({\n        label: getChoices.label.toString(),\n        language: getChoices.language.toString(),\n        value: getChoices.value.toString(),\n        sequence: getChoices.sequence.toString(),\n        inactive: getChoices.inactive.toString(),\n        dependent_value: getChoices.dependent_value.toString(),\n        hint: getChoices.hint.toString(),\n      });\n    }\n\n    for (var c = 0; c < choiceList.length; c++) {\n      var createChoice = new GlideRecord(\"sys_choice\");\n      createChoice.newRecord();\n      createChoice.setValue(\"name\", copyToTable);\n      createChoice.setValue(\"element\",newFieldName.toString());\n      createChoice.setValue(\"label\", choiceList[c].label);\n      createChoice.setValue(\"language\", choiceList[c].language);\n      createChoice.setValue(\"value\", choiceList[c].value);\n      createChoice.setValue(\"sequence\", choiceList[c].sequence);\n      createChoice.setValue(\"inactive\", choiceList[c].inactive);\n      createChoice.setValue(\"dependent_value\", choiceList[c].dependent_value);\n      createChoice.setValue(\"hint\", choiceList[c].hint);\n      createChoice.insert();\n    }\n  }else{\n    gs.info(\"Background Script: No choices found for field [{0}]\",getField.element.toString())\n  }\n}\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy table fields from one table to another/README.md",
    "content": "# Copy Fields from One Table to Another\n\nThis script facilitates copying fields from one table to another. It accepts Table A and Table B as input along with the field names to copy.\n\n## Use-Cases\n\n- This is particularly useful for building demos/POCs on PDI instances when you frequently need to create new tables. In most cases, instead of creating fields one by one, you can copy common fields from one table to another.\n\n**Note:** This is a sample script. Please run this script in a non-production environment first and test for all scenarios. The execution time of this script may vary depending on the number of fields to copy.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy table name list header action/README.md",
    "content": "# Add \"Copy Table Name\" menu item to the List context\n* **Description:** This background script programmatically adds a menu item to any list context menu that will copy the respective table name of that list.\n\n    >![Copy Table Name](menu.jpg)\n**Example:** In this case, clicking the \"Copy Table Name\" menu item will copy ```cmdb_ci_win_server``` to your clipboard\n* **Usage:** \n    - **addMenuItem.js:** Run this background script to add the menu item to the list context menu.\n    - **removeMenuItem.js:** Run this background script to remove a previously added menu item."
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy table name list header action/addMenuItem.js",
    "content": "var agg = new GlideAggregate(\"sys_ui_context_menu\");\nagg.addEncodedQuery(\"name=Copy Table Name^order=9876\");\nagg.addAggregate(\"COUNT\");\nagg.query();\nvar count = 0;\n// Process returned records\nwhile (agg.next()) {\n  count = agg.getAggregate(\"COUNT\");\n}\nif (count > 0) {\n  gs.info(\"No Action: The menu item already exists\");\n} else {\n  var gr = new GlideRecord(\"sys_ui_context_menu\");\n  gr.initialize();\n  gr.name = \"Copy Table Name\";\n  gr.active = true;\n  gr.order = 9876;\n  gr.type.setDisplayValue(\"action\");\n  gr.action_script = \"copyToClipboard(g_list.tableName);\";\n  gr.menu.setDisplayValue(\"list_title\");\n  gr.insert();\n  gs.info('List context \"Copy Table Name\" menu item was added');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Copy table name list header action/removeMenuItem.js",
    "content": "//Find existing Copy Table Name menu item and remove it\nvar gr = new GlideRecord('sys_ui_context_menu');\ngr.addEncodedQuery('name=Copy Table Name^order=9876');\ngr.query();\nvar found = false;\nwhile (gr.next()) {\n  found = true;\n  gr.deleteRecord();\n  gs.info('The menu item was removed');\n}\nif (!found) {\n  gs.info('No Action: The menu item was not found.');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Currency Conversion/README.md",
    "content": "# Convert the currency from one country denomination to other\nThis is for the currency conversion purpose and the js file has the code snippet \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Currency Conversion/currencyConvert.js",
    "content": "var conv = new sn_currency.GlideCurrencyConverter('EUR', 'USD'); // call to API by passing Europe and USA\nconv.setAmount(100); //currency value\ngs.info(conv.convert()); //call the method from the API\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Currency Formatting/README.md",
    "content": "Format currency values to your preferred locale and format.\nExamples are provided for EUR, HUF with English and Hungarian number format.\n\nformatString values:\n    Valid values:\n    \n    %s: Replaced by the currency symbol associated with the country code specified in the format() call.\n    %v: Replaced by the currency amount, such as 123.45.\n    %c: Replaced by the ISO currency code specified in the format() call, such as USD or EUR.\n    %l: Replaced with the passed in value, no formatting performed.\n    %p: Replaced by the percent sign (%).\n    For example, if the format string is '%s%v%c' and the value to format is 123.45 in US dollars, the returned formatted string is $123.45 USD. If the format string is '%s%l%c' and the value string to format is '56M' in Euros, the returned formatted string is €56M EUR.\n\nsetMaxFractionDigits:\n    Maximum number of fraction digits to return. Does a rounding based on the closest right-most digit.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Currency Formatting/currencyFormatting.js",
    "content": "var amount = '9123456.123456789';\nvar currencyCode = 'EUR';\nvar formatString = '%s%v %c';\nvar exchangeValue = new sn_currency.GlideCurrencyFormatter(formatString);\nexchangeValue.setLocale(\"en\", \"EN\"); // Language = en Country = EN\n\ngs.info('Formatted currency: ' + exchangeValue.setMaxFractionDigits(1).format(amount, currencyCode));\n// expected output: Formatted currency: €9,123,456.1 EUR\n\namount = '9123456.127456789';\ncurrencyCode = 'HUF';\nformatString = '%v %c';\nexchangeValue = new sn_currency.GlideCurrencyFormatter(formatString);\nexchangeValue.setLocale(\"hu\", \"HU\"); // Language = hu Country = HU\n\ngs.info('Formatted currency: ' + exchangeValue.setMaxFractionDigits(2).format(amount, currencyCode));\n// expected output: Formatted currency: 9 123 456,13 HUF\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Custom Table Usage/README.md",
    "content": "# Custom Table Usage Count\n\nThis script provides a way of counting where any custom tables (u_) are used in the instance.\n\n**This script is not to be used to determine subscription and license usage.  It is simply to determine how widespread the use of a custom table is in your instance to assist with tidying up unused tables.**\n\nReturns JSON object similar to the following:\n\n```json\n[\n   {\n      \"name\": \"u_cmdb_ci_key_value_staging\",\n      \"label\": \"Key Value Staging\",\n      \"references\": {\n         \"Dictionary\": 0,\n         \"Variables\": 0,\n         \"Business Rules\": 0,\n         \"Client Scripts\": 0,\n         \"Dictionary Entries\": 86,\n         \"Dictionary Overrides\": 0,\n         \"UI Actions\": 0,\n         \"ACL\": 0,\n         \"UI Policies\": 0,\n         \"Data Policy\": 0,\n         \"Styles\": 0,\n         \"View Rules\": 0,\n         \"Workflows\": 0,\n         \"Flows\": 0\n      }\n   }\n]\n```\n\nEasily extended by adding more entries in the USAGE_COUNT_CONFIG object.\n\n```javascript\nconst USAGE_COUNT_CONFIG = [\n    { \"table\": \"sys_dictionary\", \"field\": \"reference\", \"title\": \"Dictionary\" },\n    { \"table\": \"item_option_new\", \"field\": \"reference\", \"title\": \"Variables\" },\n    { \"table\": \"sys_script\", \"field\": \"collection\", \"title\": \"Business Rules\" },\n    { \"table\": \"sys_script_client\", \"field\": \"table\", \"title\": \"Client Scripts\" },\n    { \"table\": \"sys_dictionary\", \"field\": \"name\", \"title\": \"Dictionary Entries\" },\n    { \"table\": \"sys_dictionary_override\", \"field\": \"name\", \"title\": \"Dictionary Overrides\" },\n    { \"table\": \"sys_ui_action\", \"field\": \"table\", \"title\": \"UI Actions\" },\n    { \"table\": \"sys_security_acl\", \"field\": \"name\", \"title\": \"ACL\", \"query\": \"STARTSWITH\" },\n    { \"table\": \"sys_ui_policy\", \"field\": \"table\", \"title\": \"UI Policies\", },\n    { \"table\": \"sys_data_policy2\", \"field\": \"model_table\", \"title\": \"Data Policy\" },\n    { \"table\": \"sys_ui_style\", \"field\": \"name\", \"title\": \"Styles\" },\n    { \"table\": \"sysrule_view\", \"field\": \"table\", \"title\": \"View Rules\" },\n    { \"table\": \"wf_workflow\", \"field\": \"table\", \"title\": \"Workflows\" },\n    { \"table\": \"sys_hub_flow\", \"field\": \"sys_id\", \"title\": \"Flows\", \"query\": \"\", \"query_field\": \"sys_id\" },\n    { \"table\": \"sys_script_include\", \"field\": \"script\", \"title\": \"Script Include\", 'query': 'CONTAINS'}\n];\n```\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Custom Table Usage/customTableUsage.js",
    "content": "/* \n * This script is not to be used to determine subscription and license usage.  \n * It is simply to determine how widespread the use of a custom table is in your instance to assist with tidying up unused tables.\n */\n\nconst USAGE_COUNT_CONFIG = [\n    { \"table\": \"sys_dictionary\", \"field\": \"reference\", \"title\": \"Dictionary\" },\n    { \"table\": \"item_option_new\", \"field\": \"reference\", \"title\": \"Variables\" },\n    { \"table\": \"sys_script\", \"field\": \"collection\", \"title\": \"Business Rules\" },\n    { \"table\": \"sys_script_client\", \"field\": \"table\", \"title\": \"Client Scripts\" },\n    { \"table\": \"sys_dictionary\", \"field\": \"name\", \"title\": \"Dictionary Entries\" },\n    { \"table\": \"sys_dictionary_override\", \"field\": \"name\", \"title\": \"Dictionary Overrides\" },\n    // { \"table\": \"sysevent_email_action\", \"field\": \"collection \", \"title\": \"Notifications\", \"query\": \"\" },\n    { \"table\": \"sys_ui_action\", \"field\": \"table\", \"title\": \"UI Actions\" },\n    { \"table\": \"sys_security_acl\", \"field\": \"name\", \"title\": \"ACL\", \"query\": \"STARTSWITH\" },\n    { \"table\": \"sys_ui_policy\", \"field\": \"table\", \"title\": \"UI Policies\", },\n    { \"table\": \"sys_data_policy2\", \"field\": \"model_table\", \"title\": \"Data Policy\" },\n    { \"table\": \"sys_ui_style\", \"field\": \"name\", \"title\": \"Styles\" },\n    { \"table\": \"sysrule_view\", \"field\": \"table\", \"title\": \"View Rules\" },\n    { \"table\": \"wf_workflow\", \"field\": \"table\", \"title\": \"Workflows\" },\n    { \"table\": \"sys_hub_flow\", \"field\": \"sys_id\", \"title\": \"Flows\", \"query\": \"\", \"query_field\": \"sys_id\" },\n    { \"table\": \"sys_script_include\", \"field\": \"script\", \"title\": \"Script Include\", 'query': 'CONTAINS'}\n];\n\n// get list of fields to query from the table\n// grab any fields which are listed as query_fields in the usage config, and add name and label.\nvar selectFields = USAGE_COUNT_CONFIG.map(function (_obj) {\n    return _obj.query_field;\n}).filter(Boolean);\n\nselectFields.push('name');\nselectFields.push('label');\n\nvar gqTables = new global.GlideQuery('sys_db_object')\n    .where('name', 'STARTSWITH', 'u_')\n    // don't want m2m tables\n    .where('name', 'NOT LIKE', 'm2m')\n    // don't want tables extended from Import Set Row or Query Builder Results\n    // apologies for the hard-coded sys_ids, they'll never change, right?\n    .where('super_class', 'NOT IN', ['88d993c3b4232110320f8dc279c8042b', '897b97c7b4632110320f8dc279c80489'])\n    .select(selectFields)\n    .map(function (_table) {\n        var references = {};\n        _table.references = references;\n\n        USAGE_COUNT_CONFIG.forEach(function (_usageConfig) {\n            var query_type = _usageConfig['query'] ? _usageConfig['query'] : \"=\";\n            var query_field = _usageConfig['query_field'] ? _usageConfig['query_field'] : 'name';\n\n            var gqUsageCount = new global.GlideQuery(_usageConfig.table)\n                .where(_usageConfig.field, query_type, _table[query_field])\n                .count();\n\n            references[_usageConfig.title] = gqUsageCount;\n        })\n        delete _table[\"sys_id\"]; // get rid of the sys_id field\n\n        return _table;\n    })\n    .reduce(function (arr, e) { arr.push(e); return arr; }, []);\n\ngs.info(JSON.stringify(gqTables, '', 3))\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Deactivate groups with no members and inactive manager/README.md",
    "content": "\n# Deactivate groups with no members and inactive manager\n\n**Use case** : Background script that will deactivate all the groups in which there are 0 members and group manager is inactive\n\n*info* : This method is to achieve the above use-case just with one time run of background script\n\n**Solution** : Open `Scripts - Background` from the application navigator and run the script present in [script.js](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/Business%20Rules/Prevent%20adding%20user%20to%20group%20if%20manager%20is%20inactive/Script.js](https://github.com/ServiceNowDevProgram/code-snippets/blob/Copy-of-Main/Background%20Scripts/Deactivate%20groups%20with%20no%20members%20and%20inactive%20manager/script.js))\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Deactivate groups with no members and inactive manager/script.js",
    "content": "var gr = new GlideRecord('sys_user_group');\ngr.addEncodedQuery(\"active=true^manager.active=false^RLQUERYsys_user_grmember.group,<1,m2m^ENDRLQUERY\");\ngr.query();\ngr.active = false;\ngr.updateMultiple();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Decrypt Password Field/README.md",
    "content": "Sometimes we forget the password we are using e.g. the Credentials records for integrations. \nWith this code, you can go into the \"basic_auth_credentials\" table and decrypt the field \"password\" which is of the type password2.\nThe API GlideEncrypter does only work in the Global scope.\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Decrypt Password Field/decryptfield.js",
    "content": "//Remember to run this in the global scope\n\n//Get the record that has the password2 field.\nvar getBasicAuthGR = new GlideRecord('basic_auth_credentials');\ngetBasicAuthGR.get('INSERT_SYS_ID');\n\n//Decrypt the password and show it.\nvar Encrypter = new GlideEncrypter();  \nvar decryptedPassword= Encrypter.decrypt(getBasicAuthGR.password);\n\ngs.info(\"password is: \" + decryptedPassword);//Remember this also ends up in the logfile.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Delete Attachments - Closed Approvals/readme.md",
    "content": "Many organizations still prefer copying over the attachments from RITM or any ticket type that has approval required to its corresponding approvals by using GlideSysAttachment.copy() \nThis ideally makes sense till the time approval is needed however, when approvals are actioned (approved/rejected) there is no need for attachments to stay on the approval record till eternity (it will be stored at its ticket level). Use this script as a scheduled script to remove attachments from closed (approved/rejected) approval records and help consume less storage.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Delete Attachments - Closed Approvals/script.js",
    "content": "deleteinactiveattachments();\n\n//deletes from approval table\nfunction deleteinactiveattachments() {\n    var appr = new GlideRecord('sysapproval_approver');\n//\tappr.addQuery('sys_id','2d7f326287351ad0195eb99c8bbb35b5'); //uncomment this and replace sys_id and use this to check for 1 record\n    appr.addEncodedQuery('state!=requested^ORstate=NULL');\n    appr.query();\n    while (appr.next()) {\n        var answer = deleteparentattachment(appr.sys_id);\n        deletechildattachment(appr.sys_id);\n        if (answer == true || answer == 'true') {\n            appr.comments = \"Attachment's were removed from this record. If still needed it can be located at the corresponding ticket level.\";\n            appr.autoSysFields(false);\n            appr.setWorkflow(false);\n            appr.update();\n        }\n    }\n}\n//deletes from sys_attachment table\nfunction deleteparentattachment(record) {\n    var attach_found = false;\n    var attach_primary = new GlideRecord('sys_attachment');\n    attach_primary.addQuery('table_sys_id', record);\n    attach_primary.query();\n    while (attach_primary.next()) {\n        attach_primary.deleteRecord();\n        attach_found = 'true';\n    }\n    return attach_found;\n}\n\n//deletes from sys_attachment_doc table\nfunction deletechildattachment(record) {\n    var attach_child = new GlideRecord('sys_attachment_doc');\n    attach_child.addQuery('sys_attachment.table_sys_id', record);\n    attach_child.query();\n    while (attach_child.next()) {\n        attach_child.deleteRecord();\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Discover Datacenters for Service Accounts/README.md",
    "content": "# Discover the data centers for all the cloud service accounts.\n* Description: This script will discover the data centers for all the service accounts. \n> If you have a lot of service accounts, it is best to run the script as a *Scheduled Job*\n* Motivation: Using the UI to discover the data centers is impractical if you have many service accounts. It could potentially take hours. This script will perform the task programmatically"
  },
  {
    "path": "Server-Side Components/Background Scripts/Discover Datacenters for Service Accounts/script.js",
    "content": "var g = new GlideRecord('cmdb_ci_cloud_service_account');\ng.query();\ngs.info('Record Count ' + g.getRowCount());\n\nwhile (g.next()) {\n    var scheduleConfig = new global.CloudDiscoveryScheduleConfig();\n    var accountSysId = g.sys_id + '';\n    var result = {};\n    gs.info(\"Discovering datacenters for: \" + g.name + \" \" + accountSysId);\n    try {\n        result = scheduleConfig.discoverDatacenters(accountSysId);\n        gs.info(\"Result: Success\");\n    } catch(err) {\n        result.error = err;\n    }\n \n    if (result.error){\n        gs.addErrorMessage(result.error);\n    }\n}"
  },
  {
    "path": "Server-Side Components/Background Scripts/Duplicate Client Script Audit for Tables/README.md",
    "content": "# Duplicate Client Script Audit for Table\n\nDetects duplicate Client Scripts created on a specific table such as Incident, Change Request, or Problem. Helps identify redundant client scripts that share the same configuration.\n\n## Use Case\n\nEvery ServiceNow developer Junior or senior has been there, you're building something late at night, testing a client script, and you hit **“Insert and Stay”** instead of **“Update.”**  It looks harmless at first until a few days later, your form starts behaving strangely because multiple client scripts with the same logic are firing at once.\nThis script was created for exactly that situation:  \n- It helps you **find and report duplicate client scripts** on any table, so you can clean them up before they cause issues.\n- It scans through active client scripts and highlights cases where multiple scripts:\n  - Have the same **name**, **UI type**, **type**, **order**, and **field** (for *onChange* scripts)\n  - Exist within the same table\n  - Are still **active** in the system\n  - By running this audit, you can quickly detect redundant client scripts that may have been unintentionally cloned\n\n\n### Functionality\nUses a Background Script to:\n- Query all active client scripts for the selected table\n- Sort them by creation date (oldest to newest)\n- Detect duplicates with identical configurations\n- Display results as:\n  - [ORIGINAL] – first created client script\n  - This script has X duplicates – count of newer scripts with the same trigger setup\n- If no duplicates are found, a message confirms:\n  - No duplicate client scripts found for this table\n\n### Steps to Run\n\n1. Navigate to **System Definition → Scripts - Background**  \n2. Click **New**  \n3. Paste the provided script and modify this line as needed:\n   - var targetTable = 'incident'; // or 'change_request', 'wm_order' etc....\n4. Run Script\n\n---\n\n### Example:1 Run The Bacground Script For Change Request Table\n\n![Duplicate Client Script Audit Output](Backgroundscript_clientscript_duplicate_4.png)\n\n---\n\n### Cross Checking The result from output of Script Run\n\n![Duplicate Client Script Audit Output](Backgroundscript_clientscript_duplicate_1.png)\n\n---\n\n### Example:2 Run The Bacground Script For Incident Table\n\n![Duplicate Client Script Audit Output](Backgroundscript_clientscript_duplicate_3.png)\n\n\n\n\n\n\n\n\n\n\n\n   \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Duplicate Client Script Audit for Tables/backGroundScriptDuplicateClientScript.js",
    "content": "// Target table\nvar targetTable = 'change_request'; // < can be incident, change_request , wm_order etc.... > \n\n// counters\nvar seen = {};\nvar duplicateCount = 0;\nvar totalCount = 0;\n\n// Query ACTIVE Client Scripts sorted by creation date (oldest → newest)\nvar gr = new GlideRecord('sys_script_client');\ngr.addQuery('table', targetTable);\ngr.addQuery('active', true);\ngr.orderBy('sys_created_on');\ngr.query();\n\ngs.print('--- Duplicate Client Script Audit for table: ' + targetTable + ' ---');\ngs.print('Sorted by creation date (oldest → newest)');\ngs.print('MODE: Detection only (no updates performed).');\ngs.print('');\n\n// group Client Scripts\nwhile (gr.next()) {\n    totalCount++;\n\n    // Build unique trigger key\n    var key = gr.name + '_' + gr.ui_type + '_' + gr.type + '_' + gr.order;\n    if (gr.type == 'onChange') key += '_' + gr.field;\n\n    // Build readable script info\n    var info = gr.name +\n           ' | Type: ' + gr.type +\n           (gr.type == 'onChange' ? ' | Field: ' + gr.field : '') +\n           ' | Order: ' + gr.order +\n           ' | Created: ' + gr.sys_created_on.getDisplayValue() +\n           ' | Sys_id: (' + gr.sys_id + ')';\n\n\n    // If first occurrence consider it as key\n    if (!seen[key]) {\n        seen[key] = {\n            original: info,\n            duplicates: []\n        };\n    }\n    // If key already exists put into duplicate\n    else {\n        seen[key].duplicates.push(info);\n        duplicateCount++;\n    }\n}\n\n// grouping the scripts \nvar groupsWithDuplicates = 0;\nfor (var key in seen) {\n    var group = seen[key];\n    if (group.duplicates.length > 0) {\n        groupsWithDuplicates++;\n\n        // Original\n        gs.print(group.original);\n\n        // Summary line\n        // gs.print('This ' +  seen.key + 'script has ' + group.duplicates.length + ' duplicate' );\n\t\tvar trimmedKey = key.split('_')[0];\n\t\tgs.print('This ' + trimmedKey + ' script has ' + group.duplicates.length + ' duplicate' + (group.duplicates.length > 1 ? 's.' : '.'));\n\n\t\t// Separator\n        gs.print('--------------------------------------');\n    }\n}\n\n// no duplicates found\nif (groupsWithDuplicates === 0) {\n    gs.print('No duplicate client scripts found for this table.');\n}\n\n// ✅ Final summary\ngs.print('\\n--------------------------------------');\ngs.print('✅ SUMMARY for table: ' + targetTable);\ngs.print('Total active scripts scanned: ' + totalCount);\ngs.print('Originals with duplicates: ' + groupsWithDuplicates);\ngs.print('Total duplicates detected: ' + duplicateCount);\ngs.print('--------------------------------------');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesByCombination.js",
    "content": "/**\n *\tFinds and reports records that have duplicate values across a combination of specified fields instead of a single field\n *\tUseful for finding for example duplicate items where unique key is not just 1 field\n */\n\n// --- UPDATE ONLY THE VALUES BELOW ---\nvar tableName = 'cmdb_model'; // ADD: The table you want to check for duplicates.\nvar fieldNames = ['name', 'model_number', 'manufacturer']; // ADD: An array of fields that create the unique combination.\n\n// --- DO NOT EDIT BELOW THIS LINE ---\nfindDuplicateCombinations(tableName, fieldNames);\n\nfunction findDuplicateCombinations(tableName, fieldNames) {\n    /**************************************/\n    /*** Basic Error Handling         ***/\n    /**************************************/\n\n    // Check if the table exists\n    if (!gs.tableExists(tableName)) {\n        gs.print('Error: Table \"' + tableName + '\" does not exist.');\n        return;\n    }\n\n    // Check if fieldNames is a valid, non-empty array\n    if (!Array.isArray(fieldNames) || fieldNames.length === 0) {\n        gs.print('Error: The \"fieldNames\" variable must be an array with at least one field.');\n        return;\n    }\n\n    // Check if all specified fields exist on the table\n    var gr = new GlideRecord(tableName);\n    gr.initialize();\n    for (var i = 0; i < fieldNames.length; i++) {\n        var currentField = fieldNames[i];\n        if (!gr.isValidField(currentField)) {\n            gs.print('Error: The field \"' + currentField + '\" does not exist on the \"' + tableName + '\" table.');\n            return;\n        }\n    }\n\n    /***************************************/\n    /*** Find Duplicate Combinations   ***/\n    /***************************************/\n    var duplicateJson = {}; // Stores the duplicate records and their counts\n    var duplicateGroupCount = 0; // Counts the number of groups of duplicates\n\n    var duplicateAggregate = new GlideAggregate(tableName);\n    duplicateAggregate.addAggregate('COUNT');\n\n    // Loop through the array to group by each field, creating the combination\n    for (var j = 0; j < fieldNames.length; j++) {\n        var field = fieldNames[j];\n        duplicateAggregate.groupBy(field);\n        duplicateAggregate.addNotNullQuery(field); // Ignore records where any part of the combination is empty\n    }\n\n    duplicateAggregate.addHaving('COUNT', '>', 1); // More than 1 means it is a duplicate combination\n    duplicateAggregate.query();\n\n    while (duplicateAggregate.next()) {\n        duplicateGroupCount++;\n        var combinationDisplay = [];\n        var queryParams = [];\n\n        for (var k = 0; k < fieldNames.length; k++) {\n            var fieldName = fieldNames[k];\n            var fieldValue = duplicateAggregate.getDisplayValue(fieldName) || duplicateAggregate.getValue(fieldName);\n            var actualValue = duplicateAggregate.getValue(fieldName);\n            combinationDisplay.push(fieldName + ': \"' + fieldValue + '\"');\n            queryParams.push({\n                field: fieldName,\n                value: actualValue\n            });\n        }\n\n        var displayKey = combinationDisplay.join(', ');\n        var countInGroup = duplicateAggregate.getAggregate('COUNT');\n        duplicateJson[displayKey] = {\n            count: countInGroup,\n            queryParams: queryParams\n        };\n    }\n\n    /***************************************************/\n    /*** Print the Results & Fetch sys_id's          ***/\n    /***************************************************/\n\n    var fieldCombinationString = '\"' + fieldNames.join('\", \"') + '\"';\n\n    if (Object.keys(duplicateJson).length === 0) {\n        gs.print('No duplicates found for the field combination [' + fieldCombinationString + '] on table \"' + tableName + '\".');\n        return;\n    }\n\n    gs.print('');\n    gs.print('======================================================');\n    gs.print('DUPLICATE COMBINATIONS REPORT');\n    gs.print('------------------------------------------------------');\n    gs.print('Table: ' + tableName);\n    gs.print('Found ' + duplicateGroupCount + ' groups of duplicate records.');\n    gs.print('======================================================\\n');\n\n    var groupNumber = 1;\n    for (var key in duplicateJson) {\n        var groupData = duplicateJson[key];\n\n        gs.print('Group ' + groupNumber + ':');\n        gs.print('  - Occurrences: ' + groupData.count);\n        gs.print('  - Combination:');\n\n        var fields = key.split(', ');\n        for (var i = 0; i < fields.length; i++) {\n            gs.print('    - ' + fields[i]);\n        }\n\n        gs.print(\"  - Duplicate sys_id's:\");\n\n        // Perform the second query to get the sys_id's for this specific group\n        var rec = new GlideRecord(tableName);\n        for (var q = 0; q < groupData.queryParams.length; q++) {\n            var query = groupData.queryParams[q];\n            rec.addQuery(query.field, query.value);\n        }\n        rec.query();\n\n        while (rec.next()) {\n            gs.print('    - ' + rec.getUniqueValue());\n        }\n        gs.print('');\n        groupNumber++;\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Duplicate Finder/findDuplicatesBySingleField.js",
    "content": "// Update ONLY below values to find duplicates\nvar tableName = 'incident'; // ADD: Table you want for duplicates\nvar fieldName = 'short_description';\t// ADD: Field that you want to check for duplicates\n\nfindDuplicates(tableName, fieldName);\n\nfunction findDuplicates(tableName, fieldName) {\n    /**************************************/\n    /*** Basic error handling on inputs ***/\n    /**************************************/\n\n    // Check if table exists\n    if (!gs.tableExists(tableName)) {\n        // MODIFIED: Switched to string concatenation\n        gs.info('Table \"' + tableName + '\" does not exist.');\n        return;\n    }\n\n    // Check if field exists\n    var gr = new GlideRecord(tableName);\n    gr.initialize();\n    if (!gr.isValidField(fieldName)) {\n        gs.print('No field called \"' + fieldName + '\" on the \"' + tableName + '\" table.');\n        return;\n    }\n\n    /***************************************/\n    /*********** Find duplicates ***********/\n    /***************************************/\n    var duplicateJson = {}; // Store the duplicate records\n    var duplicateGroupCount = 0; // Counts the number of groups of duplicates\n\n    var duplicateAggregate = new GlideAggregate(tableName);\n    duplicateAggregate.addAggregate('COUNT', fieldName);\n    duplicateAggregate.groupBy(fieldName);\n    duplicateAggregate.addHaving('COUNT', '>', 1); // More than 1 means it is a duplicate\n    duplicateAggregate.addNotNullQuery(fieldName); // Ignore records where the field is empty\n    duplicateAggregate.query();\n\n    while (duplicateAggregate.next()) {\n        duplicateGroupCount++;\n        var fieldValue = duplicateAggregate.getValue(fieldName);\n        var countInGroup = duplicateAggregate.getAggregate('COUNT', fieldName);\n        duplicateJson[fieldValue] = countInGroup;\n    }\n\n    /***************************************/\n    /********** Print the results **********/\n    /***************************************/\n\n    // No duplicates found\n    if (Object.keys(duplicateJson).length === 0) {\n        gs.print('No duplicates found for field \"' + fieldName + '\" on table \"' + tableName + '\".');\n        return;\n    }\n\n    // Duplicates were found\n    gs.print(\"Found \" + duplicateGroupCount + \" groups of duplicates:\");\n    \n    for (var key in duplicateJson) {\n        gs.print('Value \"' + key + '\" has ' + duplicateJson[key] + ' occurrences.');\n    }\n}\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Duplicate Finder/readme.md",
    "content": "# ServiceNow Duplicate Record Finder\nA simple server-side script for ServiceNow that finds and reports on duplicate values for any field on any table. It uses an efficient `GlideAggregate` query.\n## Find Duplicates by Single Field - `findDuplicatesBySingleField.js`\nThis finds duplicates based on a single field which matches\n### How to Use\nThis script is designed to be run in **Scripts - Background** or as a Fix Script.\n1. Navigate: Go to **System Definition > Scripts - Background** (or type sys.scripts.do in the filter navigator).\n2. Copy Script: Copy the entire contents of the `findDuplicatesBySingleField.js` file.\n3. Paste and Configure: Paste the script into the \"Run script\" text box. Add the table to search in `tableName` and the field to search for duplicates in `fieldName`\n  ```js\n  // Update ONLY below values to find duplicates\n  var tableName = '<table_name>'; // ADD: Table you want for duplicates\n  var fieldName = 'field_name';\t// ADD: Field that you want to check for duplicates\n  ```\n  For example, to find duplicate email addresses in the User table:\n  ```js\n  var tableName = 'sys_user';\n  var fieldName = 'email';;\n  ```\n  To find incidents with the same short description:\n  ```js\n  var tableName = 'incident';\n  var fieldName = 'short_description';\n  ```\n\n4. Run Script: Click the \"Run script\" button. The results will be displayed on the screen below the editor.\n\n## Find Duplicates by Field Combination - `findDuplicatesByCombination.js`\nThis is a more powerful script that finds duplicate records based on a unique combination of one or more fields.\n\n### How to use\nThis script is also designed to be run in **Scripts - Background**\n1. Navigate: Go to **System Definition > Scripts - Background**\n2. Copy Script: Copy the entire contents of the `findDuplicatesByCombination.js` file.\n3. Paste and Configure: Paste the script into the \"Run script\" text box. Update the `tableName` field and the `fieldNames` array. The `fieldNames` variable is an array, so even a single field must be enclosed in square brackets `[]`\n\n```js\n// --- UPDATE ONLY THE VALUES BELOW ---\nvar tableName = '<table_name>'; // The table you want to check.\nvar fieldNames = ['field_1', 'field_2']; // An array of fields for the unique combination.\n```\n\nFor example, to find models with the same name, model number and manufacturer\n```js\nvar tableName = 'cmdb_model';\nvar fieldNames = ['name', 'model_number', 'manufacturer'];\n```\n\n### Sample output\n<img width=\"518\" height=\"483\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b5508bd8-4a3d-4478-95bd-fc6f770d4de2\" />\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Encode and Decode URI/README.md",
    "content": "EncodeURI refers to the process of converting a string into that is safe for use in URI(Uniform Resource identifier). \nIt is done by replacing characters that have special meanings in a URI with their percentage encoded equivalents.\n\nThe common use case in ServiceNow is in the Rest Message Endpoint.\nThis endpoint in Rest Message doesn't accept the special characters such as { so by using encodeURI we can encode it and \nthen use it in the Rest message endpoint to get/post responses.\n\nDecodeURI which just do the reverse by decoding the EncodedURI.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Encode and Decode URI/encodeURIdecodeURI.js",
    "content": "var getURI = 'https://api.dynatrace.com/v2/query=min:tls.get_days{*}by{name,script}';\nvar encoded = encodeURI(getURI);\ngs.info(encoded);\n// Expected output: \"https://api.dynatrace.com/v2/query=min:tls.get_days%7B*%7Dby%7Bname,script%7D\"\n\nvar decoded = decodeURI(encoded);\ngs.info(decoded);\n// Expected output: \"https://api.dynatrace.com/v2/query=min:tls.get_days{*}by{name,script}\"\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Encrypt & decrypt payload via base64/README.md",
    "content": "This code is an example to encrypt or decrypt the payload using base64Encode and decode methods of GlideStringUtil API.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Encrypt & decrypt payload via base64/code.js",
    "content": "var obj ={};\nobj.name = 'Mohit Kaushik';\nobj.email ='Mohit.1@abc.com';\nobj.contact = '1234567890';\n\nvar str = JSON.stringify(obj,null,4);\n\nvar encryption = GlideStringUtil.base64Encode(str);\ngs.info(encryption);\n\nvar decrypt = GlideStringUtil.base64Decode(encryption);\ngs.info(JSON.stringify(decrypt,null,2));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Execute Logic on Weekdays/README.md",
    "content": "Background Script that checks today is weekday and then execute further logic inside if block.\nThis script can also be used in 'Condition' part of Scheduled job, with an answer variable.\n\nvar answer = false;\nCode snippet{ inside if condition, set answer = true; }\nanswer;\n\nThis will ensure Scheduled Script only executes on Weekdays.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Execute Logic on Weekdays/doTaskonWeekdays.js",
    "content": "//Code that checks if today is not Saturday or Sunday (i.e. Weekend) and if it turns out that today is not weekend, then execute the further logic written.\n\nvar today = new GlideDateTime();\nvar day = today.getDayOfWeek(); //returns the day of week as number, Monday = 1, Tuesday = 2, ... Saturday = 6, Sunday = 7\nif(day != 6 && day != 7){\n //Do whatever task or update you want to execute on weekdays be it triggering emails or running scheduled script\n gs.print(\"Today is weekday. Good to execute updates.\");\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Extend Code Search Base/README.md",
    "content": "# Description\nOver time, your code spreads across dozens of artifact types and it becomes very difficult to keep track of it all. \n\nThe whole thing is made more difficult by the fact that JavaScript code is not always contained in pure script fields but also, for example, in conditions or even in JSON payloads. \n\nFortunately, there is a code search - for example in the ServiceNow Studio, via the UI page \"CodeSearchExampleUse\" or with the help of the browser extension \"SN Utils\".\n\nHowever in a baseline instance the code search covers about 31 tables and their fields only. If you want to search all available tables and script-related fields, you can execute my script `add_more_tables_to_code_search`. It determines in the dictionary all potential candidates that could hold JavaScript code and add them to the code search base.\n\n# Involved tables\n\n## `sn_codesearch_search_group`\n\nA search group has a certain name and covers all tables and fields which can be searched when using that search group. Utilizing \"SN Utils\" browser extension you can select the preferred search group. Within ServiceNow Studio this is not possible and the search group \"sn_devstudio.Studio\" is leveraged automatically. For this reason my script adds all tables and fields to all search groups the same way.\n\n## `sn_codesearch_table`\n\nEach record in that table represent a table and their fields which are searchable. These records are added or complemented by my script.\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Extend Code Search Base/add_more_tables_to_code_search.js",
    "content": "var grSearchGroup = new GlideRecord('sn_codesearch_search_group');\n\ngrSearchGroup.query();\n\n//iterate all code search groups\nwhile (grSearchGroup.next()) {\n\tvar grDictionary        = new GlideRecord('sys_dictionary');\n\tvar strSearchGroupSysID = grSearchGroup.getValue('sys_id');\n\tvar objArrayUtil        = new ArrayUtil();\n\n  //determine all candidates for a code search from the dictionary \n\tgrDictionary.addEncodedQuery(\n\t\t'internal_type.nameINscript,condition,condition_string,script_plain,XML,script_server' +\n\t\t'^ORelement=reference_qual' +\n\t\t'^ORelement=calculation' +\n\t\t'^NQelementSTARTSWITHscript' +\n\t\t'^ORelementLIKE_script' +\n\t\t'^internal_type.nameSTARTSWITHstring' +\n\t\t'^ORinternal_type.name=json' +\n\t\t'^NQname=sys_variable_value' +\n\t\t'^element=value'\n\t);\n\n\tgrDictionary.query();\n\n\twhile (grDictionary.next()) {\n\t\tvar grCodeSearch = new GlideRecord('sn_codesearch_table');\n\t\tvar strTable     = grDictionary.getValue('name');\n\t\tvar strField     = grDictionary.getValue('element');\n\n    //load everything which is already registered as code search base\n\t\tgrCodeSearch.addQuery('table', strTable);\n\t\tgrCodeSearch.addQuery('search_group', strSearchGroupSysID);\n\t\tgrCodeSearch.setLimit(1);\n\t\tgrCodeSearch.query();\n\n\t\t//for the respective table there is already a record available\n\t\tif (grCodeSearch.next()) {\n\t\t\tvar arrFields = grCodeSearch.getValue('search_fields').split(',');\n\n\t\t\tarrFields.push(strField);\n\t\t\tgrCodeSearch.setValue('search_fields', objArrayUtil.unique(arrFields).join(','));\n\t\t\tgrCodeSearch.update();\n\t\t}\n\t\t// create a new record at table \"sn_codesearch_table\"\n\t\telse {\n\t\t\tgrCodeSearch.initialize();\n\t\t\tgrCodeSearch.setValue('table', strTable);\n\t\t\tgrCodeSearch.setValue('search_group', strSearchGroupSysID);\n\t\t\tgrCodeSearch.setValue('search_fields', strField);\n\t\t\tgrCodeSearch.insert();\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Extract Value from JSON/extractValueFromJSON.js",
    "content": "//JSON of username and corresponding title\n\nvar userData = {\n    \"userDetails\": [{\n            \"user_name\": \"dennis.millar\",\n            \"title\": \"Account Exec Northeast\"\n        },\n        {\n            \"user_name\": \"ashley.parker\",\n            \"title\": \"Director\"\n        },\n        {\n            \"user_name\": \"steve.schorr\",\n            \"title\": \"Investigations Generalist\"\n        },\n        {\n            \"user_name\": \"tammie.schwartzwalde\",\n            \"title\": \"Senior Auditor\"\n        },\n        {\n            \"user_name\": \"tammie.schwartzwalde\",\n            \"title\": \"Payroll Generalist\"\n        },\n\t\t{\n            \"user_name\": \"tommy.tom\",\n            \"title\": \"Tester\"\n        },\n    ]\n};\n\n\nvar userRoles = {};\nfor (var i = 0; i < userData.userDetails.length; i++) {\n    var user = userData.userDetails[i];\n    if (!userRoles[user.user_name]) {\n        userRoles[user.user_name] = [];\n    }\n\n    if (userRoles[user.user_name].indexOf(user.title) === -1) {\n        userRoles[user.user_name].push(user.title);\n    }\n}\n\n\nfor (var userName in userRoles) {\n    gs.info(\"User: \" + userName + \" having Role(s): \" + userRoles[userName].join(\", \"));\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Extract Value from JSON/readme.md",
    "content": "Open 'Scripts - background' in ServiceNow and run the script of [extractValueFromJSON.js] file.\n\nThis also checks for the duplicate user_name and shows the unique ones and the corresponding roles of that particular user.\n\n\nScript:\n\n<img width=\"556\" height=\"636\" alt=\"image\" src=\"https://github.com/user-attachments/assets/8300e272-a712-4b4d-bf91-6e2d8df0aa6b\" />\n\n\nResult:\n\n<img width=\"937\" height=\"167\" alt=\"image\" src=\"https://github.com/user-attachments/assets/51ed18d3-ec65-4812-9298-0fc43bd7bec6\" />\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Fetch Active Groups list without members/README.md",
    "content": "Sample code to clean up the Groups with no members.\nThis code help to get the Active groups list  with no members\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Fetch Active Groups list without members/activeGroupsWithoutMembers.js",
    "content": "var emptyGroupMembers=[];\nvar grSysUserGroup = new GlideRecord('sys_user_group');\ngrSysUserGroup.addEncodedQuery(\"active=true\");\ngrSysUserGroup.orderBy('name');\n//grSysUserGroup.setLimit(1500);\ngrSysUserGroup.query();\nwhile (grSysUserGroup.next()) {\n        var groupid=grSysUserGroup.getValue('sys_id');\n        var groupname=grSysUserGroup.getValue('name');\n    var grSysUserGrmember = new GlideAggregate('sys_user_grmember');\ngrSysUserGrmember.addEncodedQuery(\"group.active=true^group=\"+groupid);\ngrSysUserGrmember.addAggregate('COUNT');\nvar gcount=0;\ngrSysUserGrmember.query();\nwhile (grSysUserGrmember.next()) {\n     gcount=grSysUserGrmember.getAggregate('COUNT');\nif(gcount<1)\n{\n \n    emptyGroupMembers.push(groupname); // push Group name to array\n    //emptyGroupMembers.push(groupid); // pushes groups sys ids\n\n}\n}\n}\n\n\ngs.print(emptyGroupMembers +  \"zero group count leangth\" + \"\\n\");\n\n\n//gs.print(grSysUserGroup.getRowCount() + \" total query group count\" + \"\\n\");\n\n//gs.print(emptyGroupMembers +  \"zero group count leangth\" + \"\\n\");\n//gs.print(grSysUserGroup.getValue('name'));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find All Categories Related to a Knowledge Base/GetAllTheCategoriesRelatedToAKnowledgeBase.js",
    "content": "var categoriesList = [];\n\nfunction GetAllTheCategoriesRelatedToAKnowledgeBase(parentID, categoriesList) {\n\n    if (!parentID)\n        return;\n\n    var catGr = new GlideRecord('kb_category');\n    catGr.addQuery('parent_id', parentID);\n    catGr.query();\n    while (catGr.next()) {\n        categoriesList.push(catGr.getValue('sys_id'));\n        gs.info(catGr.getValue('label'));\n        GetAllTheCategoriesRelatedToAKnowledgeBase(catGr.getValue('sys_id'), categoriesList);\n    }\n    return categoriesList;\n\n}\ngs.info(GetAllTheCategoriesRelatedToAKnowledgeBase('a7e8a78bff0221009b20ffffffffff17' /*replace with your Knowledge Base Sysid*/ , categoriesList));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find All Categories Related to a Knowledge Base/README.md",
    "content": "Use case: there is no field on the Knowledge category table which points back to the Knowledge base it belongs to. \n\nTo find the all the related categories of a knowledge base use this script.\n\nThis script Recursively go through categories and pull the related categories.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find Groups Without Members/Get Groups with no Members.js",
    "content": "var myGroups = [];\nvar grGroup = new GlideRecord(\"sys_user_group\");\ngrGroup.addActiveQuery();\ngrGroup.query();\nwhile (grGroup.next()) {\nvar gaGroupMember = new GlideAggregate(\"sys_user_grmember\");\ngaGroupMember.addQuery(\"group\",grGroup.sys_id.toString());\ngaGroupMember.addAggregate('COUNT');\ngaGroupMember.query();\nvar gm = 0;\nif (gaGroupMember.next()) {\ngm = gaGroupMember.getAggregate('COUNT');\nif (gm == 0) \n      myGroups.push(grGroup.name.toString());\n}\n}\ngs.print(myGroups);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find Groups Without Members/README.md",
    "content": "**Initialize an Array:**\nvar myGroups = [];\n\n**Create a GlideRecord Object for User Groups:**\nvar grGroup = new GlideRecord(\"sys_user_group\");\n\n**Add a Query for Active Groups:**\ngrGroup.addActiveQuery();\n\n**Execute the Query:**\ngrGroup.query();\n\n**Iterate Through Active Groups:**\nwhile (grGroup.next()) {\n\n**Count Group Members:**\nvar gaGroupMember = new GlideAggregate(\"sys_user_grmember\");\ngaGroupMember.addQuery(\"group\", grGroup.sys_id.toString());\ngaGroupMember.addAggregate('COUNT');\ngaGroupMember.query();\n\n**Check Member Count:**\nvar gm = 0;\nif (gaGroupMember.next()) {\n    gm = gaGroupMember.getAggregate('COUNT');\n    if (gm == 0) \n        myGroups.push(grGroup.name.toString());\n}\n\n**Print the Results:**\ngs.print(myGroups);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find Similar Tickets/README.md",
    "content": "This script identifies tickets similar to a given ticket in ServiceNow based on text similarity of short_description and description fields, optionally boosting the score for matching categories or assignment groups. It is intended for background script execution for testing, analysis, or automation purposes.\n\n**Features:**\n1) Compares a source ticket against other tickets in the same table (incident by default).\n2) Computes Jaccard similarity between tokenized text fields.\n3) Applies bonus points for matching category and assignment_group.\n4) Returns a sorted list of similar tickets with score, number, caller_id and short_description.\n5) Supports top N results and a minimum score filter.\n\n\n**Usage:**\n1) Paste the script in scripts-background.\n2) Make changes to the sys_id of your ticket in line no. 3\n3) click run to get an output as below\n\n**Output:**\n *   *** Script: === Similar Tickets to: INC0010005 === \n *   *** Script: INC0010006 | Score: 1.08 | undefined | Sai Test INC0008112\n *   *** Script: INC0010004 | Score: 0.58 | undefined | Sai Test INC0009005\n *   *** Script: INC0009009 | Score: 0.161 | undefined | Unable to access the shared folder.test\n *   *** Script: INC0008001 | Score: 0.08 | undefined | ATF:TEST2\n *   *** Script: INC0000020 | Score: 0.08 | undefined | I need a replacement iPhone, please\n *   *** Script: INC0000031 | Score: 0.08 | undefined | Need help with Remedy. Can we configure UI?\n *   *** Script: INC0000040 | Score: 0.08 | undefined | JavaScript error on hiring page of corporate website\n *   *** Script: INC0010002 | Score: 0.08 | undefined | \n *   *** Script: INC0000057 | Score: 0.08 | undefined | Performance problems with wifi\n *   *** Script: INC0010003 | Score: 0.08 | undefined | \n\n**Explanation of the output:**\n1) First Line contains the ticket which you have provided as a sys_id.\n2) Next lines contains the ticket which contain ticket no. | score | caller_id | short_description.\n3) If you keenly observe there are few tickets that do not have similar short description / description with scores as 0.08 but still in output the reason for this is their category and assignment group still matches with the compared ticket.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find Similar Tickets/findSimilarTickets.js",
    "content": "(function() {\n  var table = 'incident'; //can be used for other tickets as well\n  var sourceSysId = 'f4755b82c3203210348bbd33e40131cb'; // sys_id of the ticket which is used to find similar tickets\n  var limit = 10; // top N results\n  var minScore = 0.05;\n\n  function tokensFromText(text) {\n    if (!text) return [];\n    text = text.toLowerCase().replace(/[^a-z0-9\\s]/g, ' ');\n    var raw = text.split(/\\s+/);\n    var stop = {\n      'the':1,'and':1,'for':1,'with':1,'that':1,'this':1,'from':1,'have':1,'has':1,'was':1,'were':1,\n      'a':1,'an':1,'is':1,'in':1,'on':1,'of':1,'to':1,'it':1,'as':1,'by':1,'be':1,'are':1\n    };\n    var map = {};\n    for (var i=0;i<raw.length;i++) {\n      var t = raw[i].trim();\n      if (!t || t.length<3 || stop[t]) continue; // skip very short tokens\n      t = t.replace(/(ing|ed|s)$/,'');\n      map[t] = (map[t]||0)+1;\n    }\n    return Object.keys(map);\n  }\n\n  function jaccardScore(tokensA, tokensB) {\n    var setA={}, setB={};\n    tokensA.forEach(function(t){setA[t]=1;});\n    tokensB.forEach(function(t){setB[t]=1;});\n    var inter=0, uni=0;\n    for (var t in setA){ if(setB[t]) inter++; uni++; }\n    for (var t2 in setB){ if(!setA[t2]) uni++; }\n    return uni===0 ? 0 : inter/uni;\n  }\n\n  // Get source record\n  var src = new GlideRecord(table);\n  if (!src.get(sourceSysId)) {\n    gs.error(\"Source record not found\");\n    return;\n  }\n\n  var sourceText = [src.short_description, src.description].join(\" \");\n  var sourceTokens = tokensFromText(sourceText);\n  if (sourceTokens.length === 0) {\n    gs.print(\"No meaningful text to compare\");\n    return;\n  }\n\n  // Find candidate incidents\n  var gr = new GlideRecord(table);\n  gr.addActiveQuery();\n  gr.addQuery('sys_id','!=',sourceSysId);\n  gr.setLimit(300);\n  gr.query();\n\n  var results = [];\n  while (gr.next()) {\n    var candidateText = [gr.short_description, gr.description].join(\" \");\n    var candidateTokens = tokensFromText(candidateText);\n    var score = jaccardScore(sourceTokens, candidateTokens);\n\n    if (src.category == gr.category) score += 0.05;\n    if (src.assignment_group == gr.assignment_group) score += 0.03;\n\n    if (score >= minScore) {\n      results.push({\n        number: gr.number.toString(),\n        short_description: gr.short_description.toString(),\n        score: parseFloat(score.toFixed(3))\n      });\n    }\n  }\n\n  results.sort(function(a,b){return b.score - a.score;});\n\n  // Print top results\n  gs.print(\"=== Similar Tickets to: \" + src.number + \" ===\");\n  results.slice(0, limit).forEach(function(r) {\n    gs.print(r.number + \" | Score: \" + r.score + \" | \" + r.caller_id + \" | \" + r.short_description);\n  });\n\n})();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find Top-Level Manager Hierarchy/Readme.md",
    "content": "The script retrieves the top-level manager for the currently logged-in user  by traversing the manager hierarchy in the sys_user table.\n\nIt starts from the current user and moves up through each manager until it reaches a user who does not have a manager.\n\nThe script starts with the current user (e.g., Employee).\n\nIt checks if the user has a manager.\n\nIf yes, it moves up the hierarchy to the manager.\n\nIt repeats this process until it reaches a user who does not have a manager.\n\nThat user is considered the Top-Level Manager.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find Top-Level Manager Hierarchy/script.js",
    "content": "var currentUser = gs.getUser(); // Current logged-in user\nvar userGR = new GlideRecord('sys_user');\nvar maxLevels = 7; \nvar currentLevel = 0;\n\nif (userGR.get(currentUser.getID())) {\n\n    // Loop until we find a user who has no manager or reach max level\n    while (userGR.manager && currentLevel < maxLevels) {\n        var managerID = userGR.getValue('manager');\n        var managerGR = new GlideRecord('sys_user');\n\n        if (managerGR.get(managerID)) {\n            userGR = managerGR; // Move up one level\n            currentLevel++;    \n\t\t\t// gs.print(\" Level \" + currentLevel + \" Manager: \" + userGR.getDisplayValue('name'));\n        } else {\n            break; // Manager record not found\n        }\n    }\n\n    gs.print(\"Top-level (or Level \" + currentLevel + \") Manager: \" + userGR.getDisplayValue('name'));\n} else {\n    gs.print(\"User not found.\");\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find sys_id named records/README.md",
    "content": "## Find sys_id named records\n\nUse this background script to find records that contain a sys_id value in a specified field (other than the actual sys_id field)\n\nThis might for example have happened by accident during migration of data between two ServiceNow instances where referenced records on the target instance were accidentally created due to a Choice action=create setting on a Field Map.\n\nHow to use:\n\nChange the \"table\" and \"field\" variables to the table and field name you want to search for sys_ids in\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Find sys_id named records/findSysIdNamedFiles.js",
    "content": "//\n// Finds values in fields that are pure sys_id's\n//\n\nvar table = \"cmn_location\"; // Table to search in\nvar field = \"name\"; // Table field to search in\n\nvar grTable = new GlideRecord(table);\ngrTable.query();\nvar searchresults = \"\";\nwhile (grTable.next()) {\n    var fieldvalue=grTable.getValue(field);\n    if(/^[a-f0-9]{32}$/.test(fieldvalue)) {\n        searchresults += fieldvalue + \",\";\n    }\n}\nif( searchresults != \"\" ) {\n    gs.info(\"The following sys-ids were found in the \" + table + \".\" + field + \" field:\");\n    gs.info(searchresults);\n} else {\n    gs.info(\"No Sys-ID based values in \" + table + \".\" + field);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Finding groups with inactive managers/Finding groups with inactive managers.js",
    "content": "function Checkgrps() {\n    var arr = []; //empty arr\n    var gr = new GlideRecord(\"sys_user_group\"); //querying group table\n    gr.addActiveQuery(); //filtering only active groups\n    gr.query();\n    while (gr.next()) {\n        if (gr.manager.active == false || gr.manager.nil()) { //dot-walking to check manager's active status and also checking if the group does'nt have a manager assigned or not\n            arr.push(gr.name.toString() + \" \"); //pushing all the group names into an array\n        }\n    }\n    gs.info(\"Groups with inactive or no manager: \" + arr); //\n    return arr; //returns the arr with all the group names\n}\n\nCheckgrps(); //calling the function to execute the script"
  },
  {
    "path": "Server-Side Components/Background Scripts/Finding groups with inactive managers/README.md",
    "content": "This script is designed to identify all active user groups in ServiceNow where:\n\tThe group has no manager assigned, OR\n\tThe assigned manager is inactive.\nThis script helps administrators easily locate and notify the management about this inconsistancy.\n\nAdministrators can also use this script in their fix scripts and add a mailing functionality to the group members by calling an event to trigger the mail.\n\nAll you need to do is use the call the function : \"Checkgrps()\" and it will check for the groups with inactive or no managers and stores the names of the groups in an array \"arr\"."
  },
  {
    "path": "Server-Side Components/Background Scripts/Fix reference to Choice/README.md",
    "content": "\n# How to fix a reference to the Choice [sys_choice] table\n\nThe Choice table is used internally by the platform and depending on the payload of an operation, for performance reasons the platform can decide that it is better to drop the table and insert all the values instead of perform updates on specific records.\n\nWhen we insert a new record in a table, a new sys_id is generated. If we have the Choice table as a reference to provide choices for a particular field in our table, these choices will be lost every time the platform decides to recreate the Choice table.\n\nA simple way to avoid this problem is to create a field of type Choice which indirectly uses the Choice table but it is the right way, or we can create our own table to maintain the Application choices.\n\nRecently I received the mission to do exactly this: create a private choice table for an Application.\n\n## The problem\n\nA long time ago, the platform allowed a developer to choose the Choice table as a reference.\n\nAs a consequence, old custom applications may have fields referencing the Choice table directly.\n\nThis custom application was already in Production, so I needed to fix this on the fly.\n\n## The solution\n\nAfter a meeting with our ServiceNow Architect, we decided to break down the solution in three steps.\n\nAs the Tech Lead I delegated the step 1 to my best ServiceNow Developer, while assigning steps 2 and 3 to myself.\n\nStep 1) Create the new table, grant the proper permission and populate the records. [1]\n\nTable: choices\n\n![App Screenshot](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEjTjEvqeN4bOEMkeA515IR4TZiyB0iLCqn7RzKP_JjuWWeVrEGAirF30OAzVMNJvgOQ9bx-j1YNyJjkqrELlwjmg8A2fGUDPucuKAvaUbCJGws8gngu5BtLAu3zyQMRw-nXet2ImeAxgFqBKG_cpoTGZlDS6Mft0mBn2qkGwHtisyCxirhpSfEhlquZXT_H/s1942/Image%2026-07-2024%20at%2008.44.jpeg)\n\nStep 2) Create a new reference field in your main table so that the referenced table now will be our own choice table.\n\n![App Screenshot](https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEi3ERvXm5QL-s3hhNjovKmHrwXeI0ELKitcyAr50Vbx_bfpLg0tsrs1u1o49vQHdqF1tZEhOARhvwR4ef2NgiB1uY8raW7RhioilfKJViPl7C1aMV7T0s7L-U5vXodMhgAKyaarBm1y8le8NvhEZk4t6-Qnhoq3OpAcYJWU4VhCn7RSXcSUvgwRvJNvTHEe/s1666/Image%2026-07-2024%20at%2008.45.jpeg)\n\nStep 3) Develop a Fix Script to copy existing choices within the records to the new field.\n\n\nThe first idea that came to my mind was to match the Choice and choices tables with one thing I was sure they had in common: the value.\n\nWithin my main table, once I get the values stored in each record that refers the Choice table, I can select the sys_id of the corresponding record from my choices table. The sys_id from my choices table is exactly what I want to store in the new field I created in my main table.\n\nThe detail here was that in my main table these fields were not a simple reference, both were of type List.\n\nSo what I need to do is:\n\n3.1 Search each record in my main table;\n3.2 For each record, generate an array containing a list of 'value' regarding the field that is a reference to the Choice table;\n3.3 With this array of old values, search my table choices to generate a list of sys_Ids (list of records from my choices table);\n3.4 Update my main table record with the list of sys_Ids obtained in step 3.3\n\n## Final thoughts\n\n\nAs we can see in KB0813643 this is a problem of the past. Nowadays your corporate instance has a Business Rule [2] as a security measure.\n\nOne great benefit of maintaining an internal choice table is that we can create a Data Administrator persona, who can use the platform back-end to deactivate/activate choices or create new ones without any deployment effort.\n\nAbout the technical solution, since both field types were a List of records I was positive that I needed to create a list of sys_Ids to update each record at once.\n\nAfter creating your GlideRecord variable regarding the main table, remember to use the method setWorkflow(false). With this your Business Rules will not be triggered during the Fix Script execution.\n\nAnother useful GlideRecord method when updating an entire table is called autoSysFields(false). It is used to disable the update of system fields (Updated, Created, etc).\n\nBe aware that a new Fix Script is executed automatically in the moment a new App version is deployed. When we deploy an Application, the platform creates only the table structure and records are not moved.\n\nIn the deployment to the TEST instance, the Fix Script run and didn't update any record because it didn't find matches between my main table and the new choices table.\n\nAfter the App deployment I asked my Operations team to import an XML file containing all the choices table records. After that we executed the Fix Script manually to populate the main table new field.\n\n[[Watch the video]](https://www.youtube.com/watch?v=KX6S3dcKPKA&t=1s)\n\n\n___\n[1] I instructed my colleague to create the records with the same value existing within the Choice [sys_choice] table.\n\n[2] Business rule \"Prevent Reference to Choice [sys_choice]\""
  },
  {
    "path": "Server-Side Components/Background Scripts/Fix reference to Choice/script_v1.js",
    "content": "/*\n    Carlos Camacho \n    Fix Script name: Fix choices on MainTable\n    -------------------------------------------------\n    Description:\n        Populate the field u_new_field with correct values based on the field \n        core_choice (type = List)\n    -------------------------------------------------\n    Debug or Execute:\n    - camachoDebug: \n                    true: the script will not update any record. Shows sys_ids from \n                    existing choices; \n                    false: Do not show debug information and do update the main table \n                    new field that refers the App choices table.  \n\n    Variables to replace: \n    - x_aaaa_carlos_maintable: table that contains the field ref to Choice and the new \n      field ref to internal choices table;\n    - core_choice: field name (main table) that is a reference (List) to the Choice \n      table.\n    - tbchoices: table to store the App choices.\n    - u_new_field: field name (main table) that is a reference (List) to the App \n      choices table. \n*/\n\nvar camachoDebug = true; \n\nvar grMainTable = new GlideRecord(\"x_aaaa_carlos_maintable\");\ngrMainTable.setWorkflow(false);\ngrMainTable.query();\nvar listArr = [];\n\nwhile (grMainTable.next()) {\n\n    if (camachoDebug)\n       gs.info(grMainTable.number);\n\n    var old_choices = grMainTable.core_choice ? grMainTable.core_choice : \"\";\n    listArr = [];\n\n    if (old_choices) {\n\n        old_choices = old_choices.split(\",\");\n\n        for (var i = 0; i < old_choices.length; i++) {\n            var grOldChoice = new GlideRecord(\"sys_choice\");\n            grOldChoice.addQuery('sys_id', old_choices[i]);\n            grOldChoice.setLimit(1);\n            grOldChoice.query();\n            if (grOldChoice.next()) {\n                listArr.push(grOldChoice.getValue('value'));\n            }\n        }\n\n        if (camachoDebug)\n           gs.info('OldValues: ' + listArr.toString().split(',') );\n\n        var grNewChoice = new GlideRecord(\"tbchoices\");\n        grNewChoice.addEncodedQuery('valueIN' + listArr.toString().split(','));\n        grNewChoice.query();\n        var newListArr = [];\n\n        while (grNewChoice.next()) {\n           newListArr.push(grNewChoice.getValue('sys_id'));\n        }\n\n        if (camachoDebug) {\n\n            gs.info('NewValues: ' + newListArr.toString().split(',') );\n\n        } else {\n\n            grMainTable.setValue('u_new_field', newListArr.join(',') );\n            grMainTable.update();\n\n        }\n\n    }\n\n}"
  },
  {
    "path": "Server-Side Components/Background Scripts/Fix reference to Choice/script_v2.js",
    "content": "/*\n    Carlos Camacho \n    Fix Script name: Fix choices on MainTable\n    -------------------------------------------------\n    Description:\n        Populate the field u_new_field with correct values based on the field \n        core_choice (type = Reference)\n    -------------------------------------------------\n    Debug or Execute:\n    - camachoDebug: \n                    true: the script will not update any record. Shows sys_ids from \n                    existing choices; \n                    false: Do not show debug information and do update the main table \n                    new field that refers the App choices table.  \n    Variables to replace: \n    - x_aaaa_carlos_maintable: table that contains the field ref to Choice and the \n      new field ref to internal choices table;\n    - core_choice: field name (main table) that is a reference (Reference) to the \n      Choice table.\n    - tbchoices: table to store the App choices.\n    - u_new_field: field name (main table) that is a reference (Reference) to the \n      App choices table. \n*/\nvar camachoDebug = true; \nvar grMainTable = new GlideRecord(\"x_aaaa_carlos_maintable\");\ngrMainTable.setWorkflow(false);\ngrMainTable.query();\nvar listArr = [];\nwhile (grMainTable.next()) {\n    if (camachoDebug)\n       gs.info(grMainTable.number);\n    var old_choice = grMainTable.core_choice ? grMainTable.core_choice : \"\";\n    if (old_choice) {\n        var grOldChoice = new GlideRecord(\"sys_choice\");\n        grOldChoice.addQuery('sys_id', old_choice);\n        grOldChoice.setLimit(1);\n        grOldChoice.query();\n        if (grOldChoice.next()) {\n            var oldValue = grOldChoice.getValue('value');\n        }\n        if (camachoDebug)\n           gs.info('OldValue: ' + oldValue);\n        var grNewChoice = new GlideRecord(\"tbchoices\");\n        grNewChoice.addQuery('value', oldValue);\n        grNewChoice.query();\n        if (grNewChoice.next()) {\n           var newValue = grNewChoice.getValue('sys_id');\n        }\n        if (camachoDebug) {\n            gs.info('NewValues: ' + newValue );\n        } else {\n            grMainTable.setValue('u_new_field', newValue);\n            grMainTable.update();\n        }\n    }\n}"
  },
  {
    "path": "Server-Side Components/Background Scripts/FlushOutbox/README.md",
    "content": "# Flush Outbox\n\nDelete or Ignore all email sitting in the outbox. Useful when enabling email in sub-prd and not wanting to get spammed with unsent email\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/FlushOutbox/script.js",
    "content": "var deleteAll = false;\n/**\n * Delete or Ignore all email sitting in the outbox\n * Useful when enabling email in sub-prd and\n * not wanting to get spammed with unsent email\n */\nvar gr = new GlideRecord(\"sys_email\");\ngr.addQuery(\"mailbox\", \"outbox\");\ngr.addQuery(\"type\", \"send-ready\");\nif (deleteAll) gr.deleteMultiple();\nelse {\n  gr.query();\n  gs.print(\"Found \" + gr.getRowCount() + \" emails in outbox.\");\n  while (gr.next()) {\n    gr.type = \"send-ignored\";\n    gr.update();\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Force new value to read only or protected field/README.md",
    "content": "**Background Script**\n\nBackground Script, to force update on read only or protected fields. It can be used when it is a need of fixing a value, of field which can not be done from list / form edit. It can be used to any type of table, record and field, need just correct configuration.\n\n**How to use**\n\nYou need to fill all four variables which are placed on the begging of the script with values:\n\n- ticketSysId - sys_id value of record which you would like to update\n- table - table where this record exists\n- field - field which should be forced with new value on record\n- value - new value which should be set on record\n\n**Example execution**\n\nValues choosed in this example: \n\n![Coniguration](ScreenShot_1.PNG)\n\nExecution log:\n\n![Coniguration](ScreenShot_2.PNG)\n\nExecution effect on incident record\n\n![Coniguration](ScreenShot_3.PNG)\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Force new value to read only or protected field/script.js",
    "content": "//Background script to force new value on read only fields\n\n//ticketSysId - sys_id value of record which you would like to update\nvar ticketSysId = '';\n\n//table - table where this record exists\nvar table = '';\n\n//field - field which should be forced with new value on record\nvar field = '';\n\n//value - new value which should be set on record\nvar value = '';\n\n//GlideRecord to retrieve chose record\nvar gr = new GlideRecord(table);\nvar result = gr.get(ticketSysId);\n\nif (result) {\n    //In case record was correctly found, log information is executed and then update is made on record\n    gs.info('Record found, updating field: ' + field + ' with value: ' + value);\n    gr.setWorkflow(false);\n    gr.setValue(field, value);\n    gr.update();\n} else {\n    //In case record was not found, error log information is displayed\n    gs.error('Not found record with sys_id: ' + ticketSysId + ' in table: ' + table);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Form Field Count/Form Field Count.js",
    "content": "var count = 0;\nvar maxFields = 30; //change to match your threshold/allowance\nvar frmGr = new GlideRecord('sys_ui_form');\nfrmGr.query();\nwhile (frmGr.next()){\n\tcount = 0;\n\tvar frmsecGr = new GlideRecord('sys_ui_form_section');\n\tfrmsecGr.addQuery('sys_ui_form', frmGr.sys_id);\n\tfrmsecGr.query();\n\twhile (frmsecGr.next()){\n\t\tvar secGr = new GlideRecord('sys_ui_section');\n\t\tsecGr.addQuery('sys_id', frmsecGr.sys_ui_section);\n\t\tsecGr.query();\n\t\twhile (secGr.next()) {\n\t\t\tvar eleGr = new GlideRecord('sys_ui_element');\n \t\t\teleGr.addQuery('sys_ui_section', secGr.sys_id);\n\t\t\teleGr.addNullQuery('type');\n \t\t\teleGr.query();\n \t\t\tif (eleGr.next()) {\n\t \t\t\tcount += eleGr.getRowCount();\n\t\t\t}\n\t\t}\n\t}\n\tif (count > maxFields) {\n\t\tgs.info('FORM FIELD SCRIPT: Fields on form ' + frmGr.name + ' = ' + count);\n\t}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Form Field Count/README.md",
    "content": "# Form Field Count\n\nA background script that identifies forms with excessive field counts that may impact performance or trigger Health Scan warnings.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content\n3. (Optional) Modify `maxFields` variable to set your threshold (default: 30)\n4. Click \"Run script\"\n\n## What It Does\n\nThe script:\n1. Queries all forms in the instance (`sys_ui_form`)\n2. Iterates through each form's sections (`sys_ui_form_section`)\n3. Counts fields in each section, excluding container elements (splits, section starts)\n4. Reports only forms exceeding the configured threshold\n5. Outputs form name and total field count to system logs\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Generate JWT Token/README.md",
    "content": "\n# Generate JWT Token\nThis is used to generate the JWT token from SN background script by using jwtAPI and the corresponding method:generateJWT()\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Generate JWT Token/generateJWTToken.js",
    "content": "var jwtAPI = new sn_auth.GlideJWTAPI(); // Instantiate the object\nvar headerJSON = {  \"kid\": \"<key id value>\"  }; //Add the header\nvar header = JSON.stringify(headerJSON);\nvar payloadJSON = { \"iss\": \"<client_id value>\", \"sub\": \"ravikumar@gmail.com\", \"aud\":\"<client_id value>\", \"exp\":\"36500\" }; //Prepare the payload\nvar payload = JSON.stringify(payloadJSON);\nvar jwtProviderSysId = \"<JWT provider sys id value>\"; //Mention the JWT provider instance id\nvar jwt = jwtAPI.generateJWT(jwtProviderSysId, header, payload); //Now generate the JWT token\ngs.info(\"******JWT Token:\" + jwt);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Generate Random Incident Records/README.md",
    "content": "**Generate Random Incident Records**\nThis script can be used to generate random Incident records for testing purposes.\n\n**Usage**\nCan be used to run on demand in Scheduled Script Execution.\n\n**Scenario**\nUsed to generate records in PDI for testing reports and graphs.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Generate Random Incident Records/generate_random_incident.js",
    "content": "// Create a specified number of random incident records\nvar numberOfIncidents = 50;\nvar incidentTable = 'incident';\nvar incidentGR;\n\n// Sample values for Category, Subcategory, Impact, and Urgency\nvar categories = ['Software', 'Hardware', 'Network'];\nvar subcategories = ['CPU', 'Disk','Memory', 'Monitor','Mouse', 'Email', 'Application', 'Desktop', 'Server', 'Router', 'VPN', 'DNS', 'DB2', 'MS SQL Server','Oracle'];\nvar impacts = [1, 2, 3]; // Low, Moderate, High\nvar urgencies = [1, 2, 3]; // Low, Moderate, High\n\n// Sample array for requestedFor\nvar requestedForArray = [{\n    \"name\": \"Fred Luddy\",\n    \"email\": \"fred.luddy@example.com\"\n}, {\n    \"name\": \"Beth Anglin\",\n    \"email\": \"beth.anglin@example.com\"\n}, {\n    \"name\": \"System Administrator\",\n    \"email\": \"admin@example.com\"\n}, {\n    \"name\": \"Joe Employee\",\n    \"email\": \"employee@example.com\"\n}];\n\nfor (var i = 0; i < numberOfIncidents; i++) {\n    // Create a new incident record\n    incidentGR = new GlideRecord(incidentTable);\n    incidentGR.initialize();\n\n    // Set random values for incident fields\n    incidentGR.description = 'This is a random incident generated for testing purposes.';\n    incidentGR.category = categories[Math.floor(Math.random() * categories.length)];\n    incidentGR.subcategory = subcategories[Math.floor(Math.random() * subcategories.length)];\n    incidentGR.impact = impacts[Math.floor(Math.random() * impacts.length)];\n    incidentGR.urgency = urgencies[Math.floor(Math.random() * urgencies.length)];\n    incidentGR.short_description = 'Random Incident ' + (i + 1) + ' ' + incidentGR.category +\n        ' ' + incidentGR.subcategory + incidentGR.impact + ' ' + incidentGR.urgency;\n\t\n    // Set random values for AssignedTo, IssueDate\n    var randomRequester = requestedForArray[Math.floor(Math.random() * requestedForArray.length)];\n    incidentGR.assigned_to = randomRequester.name;\n    incidentGR.requested_for = randomRequester.name;\n    incidentGR.caller_id = \"System Administrator\"; // You can use your user name for easy identification.\n\n    // Randomly set the state: New, In Progress, Resolved, Closed, or Canceled\n    var stateRandom = Math.random();\n    if (stateRandom < 0.2) {\n        incidentGR.state = 3; // Resolved\n        incidentGR.close_code = 'Solution provided'; // Set the resolution code\n        incidentGR.resolved_date = gs.daysAgo(Math.floor(Math.random() * 30)); // Resolved within the last 30 days\n        incidentGR.u_resolved_date = gs.daysAgo(Math.floor(Math.random() * 30)); // User created Resolved date within the last 30 days\t\t\n        incidentGR.close_notes = 'Randomly Resolved';\n    } else if (stateRandom < 0.4) {\n        incidentGR.state = 7; // Closed\n        incidentGR.close_code = 'Solution provided'; // Set the resolution code\n        incidentGR.resolved_date = gs.daysAgo(Math.floor(Math.random() * 30)); // Resolved within the last 30 days\n        incidentGR.u_resolved_date = gs.daysAgo(Math.floor(Math.random() * 30)); // User created Resolved Date within the last 30 days\t\t\n        incidentGR.close_notes = 'Randomly Closed';\n    } else if (stateRandom < 0.6) {\n        incidentGR.state = 8; // Canceled\n    } else if (stateRandom < 0.8) {\n        incidentGR.state = 1; // New\n        incidentGR.assigned_to = '';\n    } else {\n        incidentGR.state = 2; // In Progress\n    }\n\n\n    // Generate random issue_date within the last month\n    var rightNow = new GlideDateTime();\n    pickDate = (Math.floor(Math.random() * 60) - 30);\n    rightNow.addDaysUTC(pickDate);\n    incidentGR.setValue(\"issue_date\", rightNow.getDate());\n    incidentGR.setValue(\"u_issue_date\", rightNow.getDate());\n\n    // Insert the record\n    incidentGR.insert();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Generate statistics about events created today/README.md",
    "content": "**Background Script** \n\nBackground Script, to easily generate statistics about today's created events in system. You can call getStats() with different parameters to get information about current situation in sysevent table. You can generate statistic about different aggregation functions, different aggregation fields and selected group by fields. It can be helpful for example to detect which type of events have the longest average processing duration or which type of events was created the most today.\n\n**How to use** \n\nYou need to call getStats() function with three parameters:\n\n- field - name of field which should be used in aggregate function\n- aggFunction - name of aggregate function which should be used\n- orderByField - name of field which should be used to orderBy\n\n**Example execution**\n\nValues choosed in this example:\n\n ![Coniguration](ScreenShot_0.PNG)\n \nExecution log:\n\n ![Execution](ScreenShot_1.PNG)\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Generate statistics about events created today/script.js",
    "content": "//Script to quickly generate needed statistic about events generated today in system \n\n//Function to generate statistics based on parameters\n//field - name of field which should be used in aggregate function\n//aggFunction - name of aggregate function which should be used\n//orderByField - name of field which should be used to orderBy\nfunction getStats(field, aggFunction, orderByField) {\n\n    gs.info('[Events stats] --- Statistics for field: ' + field + ' and with aggreagte function: ' + aggFunction);\n\n    //GlideAggregate query to get data from sysevent table\n    var agEvent = new GlideAggregate('sysevent');\n    agEvent.addAggregate(aggFunction, field);\n    agEvent.orderBy(orderByField);\n    agEvent.addEncodedQuery('sys_created_onONToday@javascript:gs.daysAgoStart(0)@javascript:gs.daysAgoEnd(0)');\n\n    //Order results descending by aggregate results\n    agEvent.orderByAggregate(aggFunction, field);\n    agEvent.query();\n\n    //Display statistics for all groups\n    while (agEvent.next()) {\n        var eventNumber = agEvent.getAggregate(aggFunction, field);\n        var label = agEvent.getValue(orderByField);\n        if (field == orderByField) {\n            gs.info('[Events stats] ------ Event in ' + orderByField + ': ' + label + ': ' + eventNumber);\n        } else {\n            gs.info('[Events stats] ------ Event in ' + orderByField + ': ' + label + ': ' + eventNumber + ' ' + aggFunction + ' ' + field);\n        }\n\n    }\n}\n\ngs.info('[Events stats] - Generating events statistics created on: ' + gs.nowDateTime());\n\n//Calling getStats function which different parameters\n//Get events count group by state\ngetStats('state', 'count', 'state');\n\n//Get events count group by name\ngetStats('name', 'count', 'name');\n\n//Get events average processing duration grouped by table name\ngetStats('processing_duration', 'avg', 'table');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Active MID Servers/GetActiveMidServer.js",
    "content": "/**\n * Retrieves the name of an active MID Server that is both up and validated.\n * @returns {string|null} The name of the active MID Server, or null if none is found.\n */\nfunction getActiveMidServer() {\n    // Create a GlideRecord object for the 'ecc_agent' table\n    var grServer = new GlideRecord('ecc_agent');\n\n    // Add an encoded query to filter for MID Servers that are up and validated\n    grServer.addEncodedQuery('status=Up^validated=true');\n\n    // Execute the query\n    grServer.query();\n\n    // Check if a record matching the query criteria was found\n    if (grServer.next()) {\n        // Return the name of the active MID Server\n        return grServer.name;\n    } else {\n        // No active MID Server found, return null\n        return null;\n    }\n}\n\ngetActiveMidServer();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Active MID Servers/README.md",
    "content": "# ServiceNow Script: getActiveMidServer\n\n## Description\nThis ServiceNow script defines a function, `getActiveMidServer`, that retrieves the name of an active MID Server that is both up and validated. MID Servers are essential components in the ServiceNow platform for various integration and automation tasks.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Agent log from Mid Server/README.md",
    "content": "# ServiceNow MID Server Log Retrieval\n\nThis script is used to grab a agent log file from a specified **MID Server** on demand.\n\n## What Does It Do?\n\nIt sends a command through the **ECC Queue** to the MID Server, asking it to locate a file (like `agent0.log.0`) and send its contents back to the ServiceNow instance.\n\n---\n\n## How to Use the Function\n\nThe function is named `getMidServerAgentLog`.\n\n| Parameter | What it is | Example Value |\n| :--- | :--- | :--- |\n| **`midServerName`** | The name of your MID Server. | `\"DevMidServer01\"` |\n| **`logFileName`** | The name of the file you want. | `\"agent0.log.0\"` |\n\n### Quick Example:\n\n```javascript\n// Change \"YOUR_MID_SERVER\" to the actual name!\nvar mid = \"YOUR_MID_SERVER\"; \nvar log = \"agent0.log.0\";\n\ngetMidServerAgentLog(mid, log); \n// This creates the request in the ECC Queue.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Agent log from Mid Server/getMidServerAgentLog.js",
    "content": "/**\n * Script to retrieve a specific log file (e.g., agent0.log.0)\n * from a given ServiceNow MID Server.\n *\n * It creates an ECC Queue output record with the 'grabLog' source\n * and 'SystemCommand' topic, which instructs the MID Server to\n * return the content of the specified file.\n *\n * @param {string} midServerName - The name of the target MID Server (e.g., 'My_MID_Server_A').\n * @param {string} logFileName - The name of the log file to retrieve (e.g., 'agent0.log.0').\n * @returns {GlideRecord | null} The ECC Queue record inserted, or null if insertion failed.\n */\nfunction getMidServerAgentLog(midServerName, logFileName) {\n    if (!midServerName || !logFileName) {\n        gs.error(\"MID Server name and log file name are required.\");\n        return null;\n    }\n\n    var gr = new GlideRecord(\"ecc_queue\");\n    gr.initialize();\n    gr.source = \"grabLog\";              // Specific source for file retrieval\n    gr.topic = \"SystemCommand\";         // Required topic for this type of command\n    gr.name = logFileName;              // The name of the file to grab (this becomes the command's parameter)\n    gr.queue = \"output\";                // Send from ServiceNow to the MID Server\n    gr.state = \"ready\";                 // Set to ready to be processed by the MID Server\n    gr.agent = \"mid.server.\" + midServerName; // Full agent string\n    // Set a high priority to ensure it's processed quickly (optional, but good practice)\n    gr.priority = 100;\n\n    var sysId = gr.insert();\n\n    if (sysId) {\n        gs.info(\"Request to retrieve log '\" + logFileName + \"' on MID Server '\" + midServerName + \"' successfully placed.\");\n        gs.info(\"ECC Queue Record: https://\" + gs.getProperty(\"instance_name\") + \".service-now.com/ecc_queue.do?sys_id=\" + sysId);\n        return gr; // Returns the initialized GlideRecord object\n    } else {\n        gs.error(\"Failed to insert ECC Queue record.\");\n        return null;\n    }\n}\n\n// --- EXAMPLE USAGE ---\n\n// 1. Define your target MID Server name (the name *only*, not the 'mid.server.' prefix)\nvar targetMidServer = \"MyMidServerName\"; // <== **UPDATE THIS** to your actual MID Server name\n\n// 2. Define the log file you want to retrieve\nvar targetLogFile = \"agent0.log.0\";\n\n// 3. Call the function\nvar eccRecord = getMidServerAgentLog(targetMidServer, targetLogFile);\n\nif (eccRecord) {\n    gs.print(\"Check the ECC Queue Input for a response with the log content.\");\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get All Child Roles/GetAllChildRolesRecursive.js",
    "content": "// You can set this to a role name or the Sys ID of the \"sys_user_role\" record\nvar roleNameOrSysId = \"itil\";\nvar result = getFullRoleHierarchyList(roleNameOrSysId);\n\n// This is just to print out the results. Remove this when you want to use it\nfor (var i = 0; i < result.length; i++) {\n\tgs.info(result[i]);\n}\n\nfunction getFullRoleHierarchyList(roleNameOrSysID) {\n\n\tvar roleSysID = \"\";\n\tvar grRole = new GlideRecord(\"sys_user_role\");\n\tgrRole.addEncodedQuery(\"name=\" + roleNameOrSysID + \"^ORsys_id=\" + roleNameOrSysID);\n\tgrRole.query();\n\twhile(grRole.next()) {\n\t\troleSysID = grRole.getUniqueValue();\n\t}\n\n\tif (!roleSysID) {\n\t\tgs.warn(\"Role entered does not exist.\")\n\t\treturn;\n\t}\n\n\tvar obj = getChildRoles({}, roleSysID, \"\", \"role\", \"contains\");\n\tvar arr = [];\n\tfor (var key in obj) {\n\t\tarr.push(obj[key].display);\n\t}\n\treturn arr;\n}\n\nfunction getChildRoles(found, queryId, display, queryField, getField) {\n\tif (!queryId || found.hasOwnProperty(queryId))\n\t\treturn;\n\n\tif (display) {\n\t\tfound[queryId] = {display: display};\n\t\tdirect = false;\n\t}\n\tvar gr = new GlideRecord(\"sys_user_role_contains\");\n\tgr.addEncodedQuery(queryField + \"=\" + queryId);\n\tgr.query();\n\twhile(gr.next()) {\n\t\tgetChildRoles(found, gr.getValue(getField), gr.getDisplayValue(getField), queryField, getField);\n\t}\n\n\treturn found;\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get All Child Roles/README.md",
    "content": "Use this script to get a flattened array of all Roles contained by a specificed role.\nThis will recursively drill down into other contained roles so you have the full list of roles involved in the hierarchy.\n\nTo use, you can set the \"roleNameOrSysId\" variable to either a role name or the Sys ID from the \"sys_user_role\" table\nOr call the \"getFullRoleHierarchyList\" function directly and pass in a role name or Sys ID\n\nThe following lines exist just to print out the results for your testing. Remove this if needed:\n\nfor (var i = 0; i < result.length; i++) {\n\tgs.info(result[i]);\n}\n\n\n\n- Example input: \"it_project_manager\"\n\n- List of chiold roles found in the Related List for \"it_project_manager\":\n\nresource_user\nidea_manager\nit_demand_manager\npa_viewer\nit_project_user\nproject_manager\ntimeline_user\n\n- Example output and full list of nested child roles for \"it_project_manager\":\n\nresource_user\nreport_group\nreport_user\nviz_creator\nskill_user\nidea_manager\nit_demand_manager\nit_project_user\nit_project_portfolio_user\nproject_portfolio_user\nsn_gf.goal_user\nsn_gf.goal_user_read\nsn_gf.strategy_planner_read\nit_demand_user\ndemand_user\nbaseline_user\npps_resource\nproject_user\ntimecard_user\nsn_test_management.tester\nplanning_console_user\ntask_editor\nsn_gf.strategy_planner\ntimeline_user\ndemand_manager\nscrum_user\ncmdb_read\nscrum_admin\nrm_scrum_task_admin\nrm_doc_admin\nrm_task_admin\nrm_test_admin\nrm_story_admin\nrm_epic_admin\nrm_release_scrum_admin\nrm_sprint_admin\nview_changer\nfinancial_mgmt_user\nfiscal_calendar_user\nsn_invst_pln.std_user\nrate_model_user\nsn_invst_pln_v2.investment_user\nsn_invst_pln_investment_user\ncurrency_instance_report_admin\npa_viewer\nproject_manager\ntimecard_approver\nsn_test_management.test_manager\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get All the CI classes/README.md",
    "content": "# Get All CI Classes\n\nA background script that lists all Configuration Item (CI) classes in your ServiceNow instance by using the TableUtils API to find all tables that extend `cmdb_ci`.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content\n3. Click \"Run fix script\"\n\n## What It Does\n\nThe script:\n1. Creates a TableUtils object for the base CI table (`cmdb_ci`)\n2. Gets all tables that extend this base class using `getAllExtensions()`\n3. Converts the Java object to JavaScript array using `j2js()`\n4. Prints each CI class table name\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get All the CI classes/getAllCiClasses.js",
    "content": "// Find all CI Classes\nvar table = new TableUtils(\"cmdb_ci\");\nvar ciTableList = j2js(table.getAllExtensions());\nfor (var i = 0; i < ciTableList.length; i++) \ngs.print(ciTableList[i]);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Array of Records with Attachments/incHavingAttachments.js",
    "content": "//GlideRecord the [incident] table and fetching all the active incident records having attachments\n\nvar incWithAttachment = [];\nvar checkAttachments = new GlideRecord('incident');\ncheckAttachments.addActiveQuery();\ncheckAttachments.query();\nwhile (checkAttachments.next()) {\n    if (checkAttachments.hasAttachments()) {\n        incWithAttachment.push(checkAttachments.number.toString());\n    }\n}\ngs.info(\"Incident Records having attachment: \" + incWithAttachment);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Array of Records with Attachments/readme.md",
    "content": "GlideRecord in the [incident] table and fetching all the active incident records which are having attachments \nand storing it in an array. This script will help to filter out incidents with attachments and based on that we can perform other actions as per the need.\n\n\nBackground Script:\n\n\n<img width=\"866\" height=\"368\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ad9b5652-cb2f-48ac-813d-c250d334b617\" />\n\n\nOutput:\n\n\n<img width=\"638\" height=\"132\" alt=\"image\" src=\"https://github.com/user-attachments/assets/16f1f34c-14d6-456c-8063-5d01d70c4ca4\" />\n\n\n\n\nNote: The output may vary based on the records present on the target audience's instance.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Duplicate/README.md",
    "content": "Using GlideAggregate function to find out tickets (tasks) with same number. OOB there happens to be a Unique checkbox at dictionary level\nand if in case not set to True it might create duplicate numbered tickets.\nScript will help find, ticekts if any.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Duplicate/script.js",
    "content": "var dpchk = new GlideAggregate('task');               \ndpchk.groupBy('number');                             \ndpchk.addHaving('COUNT', '>', 1);\ndpchk.query();\nwhile(dpchk.next())\t\n{\n\t\tgs.print(dpchk.number);                               \n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get GlideRecord Reference Field/README.md",
    "content": "# Get GlideRecord to a reference field using getRefRecord() method\n\n**Use case** : Background script that retrieves the complete record of a reference field"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get GlideRecord Reference Field/get_glide_record_reference_field.js",
    "content": "var grApproval = new GlideRecord('sysapproval_approver');\ngrApproval.get('007a44badba52200a6a2b31be0b8f525');\n\nif(grApproval.sysapproval.getRefRecord().isValidRecord()) { \n   return grApproval.sysapproval.getRefRecord();\n}\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Installed Plugins details/README.md",
    "content": "Background Script provides the list of installed plugins, version installed and version available for the upgrade in the instance \n\nNote: We need to set the basic auth credential in order for the script to work on the instance where we are running it. \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Installed Plugins details/getInstalledPluginInfo.js",
    "content": "//Get the instance\nvar instance_name = gs.getProperty(\"instance_name\");\n\n//build the endpoint\nvar endPoint = 'https://'+instance_name+'.service-now.com/api/sn_appclient/appmanager/apps?tab_context=installed';\n\n//initialize the RestMessageV2 API.\nvar request = new sn_ws.RESTMessageV2();\nrequest.setEndpoint(endPoint);\nrequest.setHttpMethod('POST');\n\n//Eg. UserName=\"admin\", Password=\"admin\" for this code sample.\nvar user = 'admin';\nvar password = '****'; \n\n//set the authentication\nrequest.setBasicAuth(user,password);\n\n//set the request header\nrequest.setRequestHeader(\"Accept\",\"application/json\");\n\n//invoke the API\nvar response = request.execute();\n\n//Parse the response\nvar jsonResponse = JSON.parse(response.getBody());\n\nvar appsList = jsonResponse.result.apps;\n\n//Print the Header for the response\ngs.info(\"Application name\"+\" | \"+ \"Assigned version\"+\" | \" + \"Latest version | \"  + \"Hasupdate\");\nappsList.forEach(function(app){\n    //Print the plugin details\n   var hasUpdate = app.update_available == 1 ? \"true\" : \"false\";\ngs.info(app.name+\" | \"+ app.assigned_version+\" | \" + app.latest_version+\" | \" + hasUpdate);\n});\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Instance DB Size/README.md",
    "content": "This is to get the size of the DB from the SN instance using SNC.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Instance DB Size/getInstDBSize.js",
    "content": "gs.info(\n  SNC.UsageAnalyticsScriptUtils.getCount(\n    'Primary DB size (MB)',\n    'Primary DB size of this instance (in MB)'\n  )\n); //Run this code in the Background Scripts module to get the size of the DB\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Instance Info/README.md",
    "content": "A background script that retrieves and displays essential ServiceNow instance information including IP address, node ID, and instance name.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content from `getInstanceInfo.js`\n3. Click \"Run script\"\n4. Check the system logs and info messages for the instance details\n\n## What It Does\n\nThe script:\n1. Retrieves the remote IP address of the current transaction using `GlideTransaction.get().getRemoteAddr()`\n2. Gets the system/node ID using `GlideServlet.getSystemID()`\n3. Fetches the instance name from system properties using `gs.getProperty(\"instance_name\")`\n4. Displays IP address and Node ID as info messages in the UI\n5. Logs the instance name to the system logs\n\n\n## Sample Output\n\n**Info Messages:**\n```\nIP Address: 192.168.1.100\nNode ID: node1abc123def456\n```\n\n**System Log:**\n```\n*** Script: mycompany-dev\n```\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Instance Info/getInstanceInfo.js",
    "content": "var gl= GlideTransaction.get().getRemoteAddr();\ngs.addInfoMessage('IP Address:'+gl);\nvar gl1=GlideServlet.getSystemID();\ngs.addInfoMessage('Node ID:'+gl1);\nvar env = gs.getProperty(\"instance_name\");\ngs.info(env);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Journal Entry as HTML Without Header/GetJournalEntryAsHTMLWithoutHeader.js",
    "content": "var journalFieldName = 'comments';\nvar journalText = current[journalFieldName]\n\t.getJournalEntry(1)\n\t.trim()\n\t.split('\\n')\n\t.slice(1)\n\t.join('<br />\\n');"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Journal Entry as HTML Without Header/README.md",
    "content": "# Get Journal Entry as HTML\n\nThis script can be used in a Business Rule or background script, or any other server-side script. It assumes the presence of the `current` object, but can be used with any positioned GlideRecord. \n\n## Use\n\nSimply change `current` to whatever GlideRecord variable you're using, and change the `journalFieldName` variable so it contains the name of the journal field you want to get the value for.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get My Groups/README.md",
    "content": "The GlideSystem User object has a useful function, which is called getMyGroups. This gives back the sys_ids of current user's group. But the functionality behaves differently depending where it is called from.\n\nIf the function is called from Global scope a Java object (com.glide.collections.StringList) is returned:\n\n``` Javascript\nvar currentUserGroups = gs.getUser().getMyGroups();\ngs.info(currentUserGroups);\ngs.info('Object type: ' + Object.prototype.toString.call(currentUserGroups));\n```\n\nResult:\n``` Text\n*** Script: [723aa84f5ba02200502f6ede91f91aea, cfcbad03d711110050f5edcb9e61038f]\n*** Script: Object type: [object JavaObject]\n```\nWhen the function is called from Application scope, the type will be a Javascript Array object:\n``` Text\nx_149822_va_code_p: 723aa84f5ba02200502f6ede91f91aea,cfcbad03d711110050f5edcb9e61038f\nx_149822_va_code_p: Object type: [object Array]\n```\nThe main problem here is that the StringList class behaves differently like a generic JS Array. For example you cant get an element from the collection based on its index (currentUserGroups[0]).\n\n``` Text\nJavascript compiler exception: Java class \"com.glide.collections.StringList\" has no public instance field or method named \"0\". (null.null.script; line 8) in:\nvar currentUserGroups = gs.getUser().getMyGroups();\n```\nThe solution below gives a generic way, how this function can be called from both type of Applications:\n\n``` Javascript\nvar currentUserGroups = gs.getUser().getMyGroups();\n\nif (Object.prototype.toString.call(currentUserGroups).match(/^\\[object\\s(.*)\\]$/)[1] == \"JavaObject\") {\n    var arrayUtil = new global.ArrayUtil();\n    currentUserGroups = arrayUtil.convertArray(currentUserGroups);\n}\n\ngs.info(currentUserGroups);\ngs.info('Object type: ' + Object.prototype.toString.call(currentUserGroups));\n```\n\nGlobal:\n``` Text\n*** Script: 723aa84f5ba02200502f6ede91f91aea,cfcbad03d711110050f5edcb9e61038f\n*** Script: Object type: [object Array]\n```\n\nScoped app:\n``` Text\nx_149822_va_code_p: 723aa84f5ba02200502f6ede91f91aea,cfcbad03d711110050f5edcb9e61038f\nx_149822_va_code_p: Object type: [object Array]\n```\nSo with this simple solution the collection of groups can be handled as a JS Array in both cases.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get My Groups/getMyGroups.js",
    "content": "// Get current user's group\nvar currentUserGroups = gs.getUser().getMyGroups();\n\n// Check that the collection object type is JavaObject or not\nif (Object.prototype.toString.call(currentUserGroups).match(/^\\[object\\s(.*)\\]$/)[1] == \"JavaObject\") {\n    // ArrayUtil can be used to create a JS Array from Java collection object\n    var arrayUtil = new global.ArrayUtil();\n    currentUserGroups = arrayUtil.convertArray(currentUserGroups);\n}\n\ngs.info(currentUserGroups);\ngs.info('Object type: ' + Object.prototype.toString.call(currentUserGroups));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Outstanding Incidents/README.md",
    "content": "# ServiceNow Background Script — Outstanding Incidents Before Current Month (JSON Output)\n\nThis background script retrieves all \"active\" incidents created before the start of the current month, and outputs the results in JSON format.\n\nIt is useful for reporting involving aging incident records.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Outstanding Incidents/get-outstanding-incidents.js",
    "content": "var result = [];\n    var gr = new GlideRecord('incident');\n    gr.addQuery('active', true);\n    var startOfMonth = new GlideDateTime();\n    startOfMonth.setDisplayValue(gs.beginningOfThisMonth());\n    gr.addQuery('sys_created_on', '<', startOfMonth);\n\n    gr.query();\n\n    while (gr.next()) {\n        result.push({\n            number: gr.getValue('number'),\n            short_description: gr.getValue('short_description'),\n            assigned_to: gr.getDisplayValue('assigned_to'),\n            sys_created_on: gr.getDisplayValue('sys_created_on'),\n            state: gr.getDisplayValue('state')\n        });\n    }\n\n    var output = {\n        total_count: result.length,\n        generated_on: gs.nowDateTime(),\n        outstanding_incidents: result\n    };\n\n    gs.print(JSON.stringify(output,null,2));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Risk and Controls in Project/README.md",
    "content": "In ServiceNow Risk Assessment Project, we can assess multiple risks on a single page or UI.\nBut for each individual risks, we need different Controls to be associated that can mitigate the risk.\nSo, there is direct relationship between Risk and Control in ServiceNow along with Risk and Risk Assessment Project.\nTwo m2m tables are - 'sn_risk_m2m_risk_control' and 'sn_risk_advanced_m2m_risk_assessment_project_risk' respectively.\n\nNow organisations need to check how many Controls involved in a Risk Assessment Project for each risk.\nTo achieve that, we can utilize Risk Assessment Instance Response records where Control Assessment data is stored.\nThis Control Assessment data is updated whenever we associate Controls with Risks during Risk Assessment and fill out the factors.\n\nIn the Background script, if we pass one sys_id of Risk Assessment Project record, that should print Risks and Controls for each risk like this-\n<img width=\"767\" height=\"356\" alt=\"image\" src=\"https://github.com/user-attachments/assets/56e2a719-b767-456d-959d-e84457d31c44\" />\n\nActual Risk Assessment Project on workspace looks like this-\n\n<img width=\"922\" height=\"412\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c102d444-922b-44ec-9cb4-4a4ae13db4c7\" />\n\n<img width=\"920\" height=\"416\" alt=\"image\" src=\"https://github.com/user-attachments/assets/48f8d6b1-bb25-4d5c-875c-ca49bd6660a8\" />\n\nHere, at the left we can select individual risks and inside of Control Assessment for that risk, controls can be added and assessed.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get Risk and Controls in Project/risksandcontrolsinProject.js",
    "content": "var asmtGr = new GlideRecord('sn_risk_advanced_risk_assessment_instance');\nasmtGr.addQuery('risk_assessment_project','d4eed504c3787210b533bb02b4013121');//Risk Assessment Project Sys Id\nasmtGr.query();\nwhile(asmtGr.next()){\n    gs.print(\"Risk: \"+asmtGr.risk.name+\"\\n\");//Printing Individual Risk Name\n    var asmtResp = new GlideAggregate('sn_risk_advanced_risk_assessment_instance_response');\n    asmtResp.addEncodedQuery('assessment_instance_id='+asmtGr.sys_id+'^assessment_type=2^parent_instance_response=NULL');\n    asmtResp.query();\n    var countCont = asmtResp.getRowCount().toString();\n    gs.print(\"This risk has \"+countCont+\" control(s) associated as mitigating action. Those are -\\n\");\n    var i=1;\n    while(asmtResp.next()){\n        gs.print('Control '+i+' : '+asmtResp.control.name+\"\\n\");//Printing Control names for each risk\n        i++;\n    }\n    gs.print('\\n');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get The Last Journal Comment Date/README.md",
    "content": "# Timestamp Extraction from Comment\n\nThis code snippet demonstrates how to extract a timestamp from a comment text and create a 'GlideDateTime' object from it.\nIt may be useful if you don't want to drill down to the 'sys_journal_field' table.\n\n## How it Works\n\n1. It retrieves the last comment from the journal using `record.comments.getJournalEntry(1)`.\n2. It then uses regular expressions to search for a timestamp in the format `DD-MM-YYYY hh:mm:ss`.\n3. If a timestamp is found, it creates a new `GlideDateTime` object from the matched timestamp.\n\n```javascript\n/* If your date has a different format you can replace the regex pattern with a new one */\nvar timestampMatch = commentText.match(/YOUR_REGEX_PATTERN/);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get The Last Journal Comment Date/script.js",
    "content": "/* Get required record by sys_id */\nvar record = new GlideRecord('<table>');\nrecord.get('<SYSID>');\n\n/* Get the latest comment, replace comments with your journal field if required */\nvar commentText = record.comments.getJournalEntry(1);\n\n/* Catch the timestamp out of it with regex */\nvar timestampMatch = commentText.match(/(\\d{2}-\\d{2}-\\d{4} \\d{2}:\\d{2}:\\d{2}) -/);\n\n/* If catched, created new DateTime out of it */\nif (timestampMatch) {\n    var parsedTimestamp = new GlideDateTime(timestampMatch[1]);\n    gs.info(\"Timestamp: \" + parsedTimestamp);\n} else {\n    gs.error(\"No timestamp found: \" + commentText);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get User's Favorite Hierarchy/GetUsersFavoriteHierarchy.js",
    "content": "// Example call with print out. You can replace gs.getUserID() with a User Name instead\nvar favorites = getFavoritesHierarchyArray(gs.getUserID());\ngs.info(JSON.stringify(favorites));\n\nfunction getFavoritesHierarchyArray(userId) {\n\tvar hierarchy = [];\n\taddGroups(\"NULL\", hierarchy, userId);\n\taddItems(\"NULL\", hierarchy, userId);\n\treturn hierarchy;\n}\n\nfunction addGroups(parentId, array, name) {\n  var grBookmarkGroup = new GlideRecord(\"sys_ui_bookmark_group\");\n  grBookmarkGroup.addEncodedQuery(\"user=\" + name + \"^parent_group=\" + parentId);\n  grBookmarkGroup.query();\n  while (grBookmarkGroup.next()) {\n    var groupObj = {\n      \"type\": \"group\",\n      \"color\": grBookmarkGroup.getValue(\"color\"),\n      \"order\": grBookmarkGroup.getValue(\"order\"),\n      \"title\": grBookmarkGroup.getValue(\"title\"),\n      \"items\": [],\n    };\n    array.push(groupObj);\n    addGroups(grBookmarkGroup.getUniqueValue(), groupObj.items, name);\n    addItems(grBookmarkGroup.getUniqueValue(), groupObj.items, name);\n  }\n}\n\nfunction addItems(parentGroup, array, name) {\n  var grBookmark = new GlideRecord(\"sys_ui_bookmark\");\n  grBookmark.addEncodedQuery(\"user=\" + name + \"^group=\" + parentGroup);\n  grBookmark.query();\n  while (grBookmark.next()) {\n    var grBookmarkObj = {\n      \"type\": \"bookmark\",\n      \"color\": grBookmark.getValue(\"color\"),\n      \"order\": grBookmark.getValue(\"order\"),\n      \"icon\": grBookmark.getValue(\"icon\"),\n      \"open_in_form\": grBookmark.getValue(\"open_in_form\"),\n      \"pinned\": grBookmark.getValue(\"pinned\"),\n      \"separator\": grBookmark.getValue(\"separator\"),\n      \"title\": grBookmark.getValue(\"title\"),\n      \"ui_type\": grBookmark.getValue(\"ui_type\"),\n      \"uncancelable\": grBookmark.getValue(\"uncancelable\"),\n      \"url\": grBookmark.getValue(\"url\"),\n      \"flyout\": grBookmark.getValue(\"flyout\"),\n      \"flyout_sizing\": grBookmark.getValue(\"flyout_sizing\"),\n      \"flyout_width\": grBookmark.getValue(\"flyout_width\"),\n    };\n    array.push(grBookmarkObj);\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get User's Favorite Hierarchy/README.md",
    "content": "This script will allow you to get Favorites Hierarchy of a specific user.\nThis means all nested groups and links.\n\nHere is an example call with print out. You can replace gs.getUserID() with a User Name instead:\n\nvar favorites = getFavoritesHierarchyArray(gs.getUserID());\ngs.info(JSON.stringify(favorites));\n\n\nThis will return an array of objects.\nEach item in the array will have a \"type\" property which will be \"group\" for nested groups and \"bookmark\" for bookmarks/links\n\nOther properties are named the same as the standard fields on the \"sys_ui_bookmark_group\" and \"sys_ui_bookmark\" tables for Groups and Bookmark types respectively\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get all users where manager is empty/README.md",
    "content": "# Get all users without manager\n\nUse the script in script.js file to get the list of all users in sys_user table who do not have an manager.\nThis GlideRecord script can be used in multiple places. For example in background scripts.\n\n### Did some optimization in the code\n1. Used different variable name instead of gr to reference a GlideRecord object.\n2. Used addActiveQuery() method to filter out just the active records.\n3. Used getDisplayValue() method to push string values in the array instead of using dot notation.\n4. Used self executing function to wrap the code in a function for reducing variable scoping issues.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get all users where manager is empty/script.js",
    "content": "(function () {\n    var users = [];\n    var userGr = new GlideRecord('sys_user'); // used different variable name instead of gr.\n    userGr.addActiveQuery(); // used to filter more on the records fetched.\n    userGr.addNullQuery('manager');\n    userGr.query();\n    while (userGr.next()) {\n        users.push(userGr.getDisplayValue()); // used getDisplayValue() method to get the name as a string instead of using gr.name\n    }\n    gs.info(\"Users without manager are : \" + users);\n\n})(); // Used a self executing function to wrap the code with a function for reducing variable scoping issues\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get current logged in user count in all nodes of instance/README.md",
    "content": "\n# Get current logged in user count in all nodes of instance\n\n**Use case** : Background script that will print the count of all currently logged in users in the instance\n\n*info* : This method is to achieve the above use-case just with one time run of background script\n\n**Solution** : Open `Scripts - Background` from the application navigator and run the script present in [script.js](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/Background%20Scripts/Get%20current%20logged%20in%20user%20count%20in%20all%20nodes%20of%20instance/script.js)\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get current logged in user count in all nodes of instance/script.js",
    "content": "var total_users = 0;\nvar diag = new Diagnostics();\n\nwhile (diag.nextNode()) {\n    var diagNode = diag.getNode();\n    var summary = diagNode.stats.sessionsummary;\n\n    if (summary) {\n        total_users += parseInt(diagNode.stats.sessionsummary[\"@logged_in\"]);\n    }\n}\n\ngs.info(total_users);\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get incident count based on priority/IncidentCount.js",
    "content": "(function() {\n    var priorities = {};\n    var agg = new GlideAggregate('incident');\n    agg.addAggregate('COUNT');\n    agg.groupBy('priority');\n    agg.query();\n    while (agg.next()) {\n        var priority = agg.getDisplayValue('priority') || 'No Priority Set';\n        var count = agg.getAggregate('COUNT');\n        priorities[priority] = parseInt(count, 10);\n    }\n    for (var priority in priorities) {\n        gs.info('Priority: ' + priority + ' | Count: ' + priorities[priority]);\n    }\n})();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get incident count based on priority/README.md",
    "content": "//To get the incident count based on priority.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get list of Update Set types/README.md",
    "content": "This snippet outputs a list of module types included in an update set.\nBy checking the included module types before committing an update set, you can be sure you aren't including any unintended updates.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get list of Update Set types/script.js",
    "content": "function getListOfUpdateSetTypes(update_set){\n    var result = []\n    var gr = new GlideAggregate('sys_update_xml');\n    gr.addQuery('update_set', update_set);\n    gr.groupBy('type');\n    gr.query();\n    while(gr.next()){\n        result.push(gr.type.getDisplayValue())\n    }\n    return result;\n}\n\ngetListOfUpdateSetTypes('update_set_sys_id');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get the current version of an application/README.md",
    "content": "- Set the appName variable to the exact name of the application you’re checking (in this case as an example, Project Workspace).\n- This script queries the Application [sys_app] table for a record with the specified name.\n- If the application is found, it retrieves and prints the version. If not, it prints a message stating the application wasn’t found.\n- This script will find the version of a specific application and output the version in the Scripts - Background logs.\n\n\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Get the current version of an application/currentversionscript.js",
    "content": "// Replace 'Project Workspace' with the name of the application you want to check\nvar appName = 'Project Workspace';\n\nvar appRecord = new GlideRecord('sys_app');\nappRecord.addQuery('name', appName);\nappRecord.query();\n\nif (appRecord.next()) {\n    gs.print(\"Application Name: \" + appName);\n    gs.print(\"Current Version: \" + appRecord.getValue('version'));\n} else {\n    gs.print(\"The Application '\" + appName + \"' is not found.\");\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/GetFlowNames/README.md",
    "content": "\n\n\nThis Background script check and provides all the flow names running on any given specific table.\n\nYou need to put table name on below line from code on which you want to see the all the running flows. \n\nvar strTableName = 'change_request'; //replace with your table name\n\nThe output will be all the flow names on the given table.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/GetFlowNames/getFlowNames.js",
    "content": "var grActionInstance = new GlideAggregate('sys_hub_action_instance');\nvar strTableName = 'change_request'; //replace with your table name\n\ngrActionInstance.addJoinQuery('sys_variable_value', 'sys_id', 'document_key').addCondition('value', strTableName);\ngrActionInstance.addAggregate('GROUP_CONCAT_DISTINCT', 'flow'); \ngrActionInstance.groupBy('flow');\ngrActionInstance.addQuery('flow.sys_class_name', 'sys_hub_flow')\ngrActionInstance.query();\n\nwhile(grActionInstance.next()) {\n    gs.info( '[{0}]\\t{1}',  grActionInstance.flow.active ? 'active' : 'inactive', grActionInstance.flow.name);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/GetRecordsFromMultipleTables/README.md",
    "content": "A background script that retrieves record counts from multiple ServiceNow tables with date filtering, providing a comprehensive data audit report.\n\n## What It Does\n\nThe script:\n1. Defines an array of table names to query (60+ tables by default)\n2. Iterates through each table using `forEach()` to process them systematically\n3. Uses `GlideAggregate` with COUNT aggregate for efficient record counting\n4. Applies a date filter to count records updated before a specific date\n5. Handles errors gracefully with try-catch blocks for invalid table names\n6. Outputs results in a formatted table structure with pipe separators\n\n\n## Sample Output\n\n```\n| Table |    Records \n| customer_account |    1,245 records\n| cmn_location |    87 records\n| customer_contact |    3,456 records\n| cmdb_ci |    12,789 records\nWe've got an error for table: invalid_table_name\n```\n\n## Configuration Options\n\n- **Date filtering**: Modify `sys_updated_on<=javascript:gs.dateGenerate('YYYY-MM-DD','HH:mm:ss')` to change the cutoff date\n- **Custom table list**: Replace the `tablesList` array with your specific tables of interest\n- **Additional filters**: Add more encoded query conditions like `active=true` or specific field criteria"
  },
  {
    "path": "Server-Side Components/Background Scripts/GetRecordsFromMultipleTables/script.js",
    "content": "var tablesList = ['customer_account','cmn_location','customer_contact','sn_install_base_sold_product','cmdb_model','sn_install_base_item','sn_install_base_m2m_contract_sold_product','sn_install_base_sold_product_related_party','business_unit','contract_rel_contact','cmdb_contract_product_model','cmdb_contract_model_lifecycle','cmn_department','cmdb_service_product_model','sn_install_base_m2m_installed_product','cmdb_ci_service_discovered','cmdb_ci','cmdb_rel_ci','cmdb_rel_team','cmdb_ci_service','service_offering','cab_agenda_item','cab_attendee','cab_meeting','kb_article_template','kb_article_template_definition','kb_category','kb_feedback','kb_feedback_task','kb_knowledge','kb_knowledge_base','kb_knowledge_summary','kb_template_m2m_knowledge_base','kb_uc_cannot_contribute_mtom','kb_uc_cannot_read_mtom','kb_uc_can_contribute_mtom','kb_uc_can_read_mtom','kb_use','kb_version','m2m_kb_feedback_likes','m2m_kb_task','ts_query_kb','u_kb_template_askim_article_template','u_kb_template_askit_article_template','u_kb_template_msc_known_error_databases_template','u_msc_known_error_data_migration','v_st_kb_category','v_st_kb_most_viewed','user_criteria','live_message','m2m_sp_portal_knowledge_base','sys_data_source','sys_import_set','sys_import_set_run','sysevent_register','sys_user_group_type','sys_user_group','core_company','sn_customerservice_contact_relationship','pc_product_cat_item','sc_cat_item_content','sc_cat_item_guide','sc_cat_item_producer','sc_cat_item_service','sc_cat_item_wizard','sc_category_list','sc_cat_item_list','kb_quality_checklist','dl_u_priority','sys_user_group','sys_group_has_role','sys_user','sys_user_role','sys_user_grmember','sys_user_group_type','sys_user_group','sys_group_has_role','sysapproval_group','sysapproval_approver','sc_cat_item_content','sc_cat_item_guide','sc_cat_item_producer','sc_cat_item_service','sc_cat_item_wizard','chg_model','sn_customerservice_catalog_item_per_change_category_per_offer','std_change_record_producer'];\ngs.print('| Table | \\t Records ');\ntablesList.forEach(function(table){\n\tgetTotalRecords(table);\n});\n//Print total count of table Records\nfunction getTotalRecords(table){\n\ttry { \n\t\tvar records = new GlideAggregate(table);\n\t\trecords.addAggregate('COUNT');\n\t\trecords.addEncodedQuery(\"sys_updated_on<=javascript:gs.dateGenerate('2024-09-30','23:59:59')\")\n\t\trecords.query();\n\t\n\t\tif (records.next()){\n\t\t  gs.print( '| ' +table + ' | \\t ' + records.getAggregate('COUNT') + ' records');\n\n\t\t}\n\t  } catch (err) {\n\t\t  gs.print(\"We've got an error for table: \" + table);\n\t  }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/GreenHouse ServiceNow Integration Snippet/GreenHouse_SN_Snippet.js",
    "content": "  var instanceName = gs.getProperty('instance_name');\n  var hr = new GlideRecord('sn_hr_core_profile');\n  hr.addEncodedQuery('user.active=true^user.u_worker_idISNOTEMPTY^u_worker_type=EE^u_greenhouse_idISNOTEMPTY');\n  hr.query();\n  while (hr.next()) {\n\n      var workerID = hr.user.u_worker_id + '';\n      var greenhouseID = hr.getValue('u_greenhouse_id');\n      var userSys = hr.user.sys_id.toString();\n\n      var key = 'Basic ' + gs.getProperty('sn_hr_core.greenhouseApiKey');\n\n      var r = new sn_ws.RESTMessageV2('Greenhouse', 'Retrieve Candidate');\n      r.setStringParameterNoEscape('id', greenhouseID);\n      r.setStringParameterNoEscape('Authorization', key);\n\n      var response = r.execute();\n      var responseBody = response.getBody();\n      var httpStatus = response.getStatusCode();\n\n      var rBody = JSON.parse(responseBody);\n\n      var attachments = rBody.attachments;\n\n      for (var i = 0; i < attachments.length; i++) {\n\n          var type = attachments[i].type + '';\n          var fileName = attachments[i].filename + '';\n          var docType = '';\n          var encodedUri = attachments[i].url;\n          //var decodedUrl = decodeURI(encodedUri);\n          //var url = decodedUrl.replace(/ /g, '%20');\n\n          if (type == 'offer_packet' || type == 'signed_offer_letter' || type == 'offer_letter') {\n\n              docType = '3a4a029adb3bac9444c5ebd8489619d1';\n\n          } else if (type == 'resume') {\n\n              docType = '9af9ca5adb3bac9444c5ebd8489619a3';\n\n          } else if (type == 'form_attachment' || type == 'cover_letter' || type == 'other') {\n\n              docType = '696549a3dbbfe41044c5ebd8489619c5';\n\n          }\n\n          var newDoc = new GlideRecord('sn_hr_ef_employee_document');\n          newDoc.newRecord();\n          newDoc.employee = userSys;\n          newDoc.document_type = docType;\n          newDoc.u_title = fileName;\n          newDoc.insert();\n\n          var request = new sn_ws.RESTMessageV2();\n          request.setHttpMethod('get');\n          request.setEndpoint(encodedUri);\n          request.saveResponseBodyAsAttachment('sn_hr_ef_employee_document', newDoc.sys_id, fileName);\n          var response1 = request.execute();\n\n      }\n  }\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/GreenHouse ServiceNow Integration Snippet/README.md",
    "content": "This utility contains sample code to integrate ServiceNow with GreenHouse and pull employee files from GreenHouse in ServiceNow Employee Document Management OOB Table records.\n\nSample code queries HR profile which a filtered query of active users which has a valid greenhouse ID present in ServiceNow HR profile records.\n\nREST message which calls Greenhouse REST API is using below REST endpoint.\nhttps://developers.greenhouse.io/harvest.html#get-retrieve-candidate\n\nDocument Types sys_id can be mapped to relevant document types on your instance.\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/IRE Simulation/readme.md",
    "content": "Background script to test IRE simulation for specific CI .\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/IRE Simulation/script.js",
    "content": "/*\nAdd class name and provide name and short description.\n\n*/\n\nvar payload = {\n    \"items\": [\n        {\n            \"className\": \"cmdb_ci_linux_server\",\n            \"values\": {\n                \"name\": \"Test_Linux_001\",\n                \"short_description\": \"My New Description\"\n            }\n        }\n    ]\n};\nvar jsonUtil = new JSON();\nvar input = jsonUtil.encode(payload);\nvar output = SNC.IdentificationEngineScriptableApi.createOrUpdateCI('ServiceNow', input);\ngs.print(output);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Identification and Reconciliation/README.md",
    "content": "Identification and Reconciliation rule is used to identify the true source of data inserted/updated\ninto a table from different sources.\n\nHere we will see how we can use the given script in business rule, scheduled job, etc to run the IRE on any payload \nwhenever required.\nOne of the use case is: Suppose we have a payload from Rest message and it is creating/updating records in Computer table.\nSo, we will use the payload and pass it into the IRE to run the identification and reconciliation rule.\n\n\nclassName - It will be name of the table on which the rule needs to be implemented\nUnder the values, we need to pass the backend name of the fields and field value\nNote: name, serial_number, u_glide_date and asset_tag are the field's backend name in the \nComputer[cmdb_ci_computer] table.\n\nServiceNow is the 'Data Source' which we have defined in the reconciliation rules for the table\n[cmdb_ci_computer]. It will follow the priority, field that have been\nselected as part of the reconciliation rule. Then it will automatically insert/update based on the defined \nidentification/reconciliation rule on the table[cmdb_ci_computer].\n\nThe table and their fields can be changed as per the end user's requirement and this piece will help to\nmaintain the integrity of CMDB and avoid duplicate records and maintain the true source of data.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Identification and Reconciliation/identificationReconciliationOnPayload.js",
    "content": "identificationReconciliationRule();\n\nfunction identificationReconciliationRule() {\n\n   var gdtToday = new GlideDate();\n   var todayDate = gdtToday.getDisplayValue();\n\n   var payload = {\n  items: [{\n    className:'cmdb_ci_computer',\n    values: {\n      name: '382735F5AD9E493',\n      serial_number:'28398596-3000301',\n      u_glide_date: todayDate,\n      asset_tag: 'P1000148'\n    }\n  }]\n};\n\n//'ServiceNow' is the 'Data Source' which we have defined in the reconciliation rules for the table [cmdb_ci_computer]\n\n   var input = new JSON().encode(payload);\n   var output = SNC.IdentificationEngineScriptableApi.createOrUpdateCI('ServiceNow', input);\n   gs.info(output);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Identify Inactive users with tickets/README.md",
    "content": "# ServiceNow Background Script – Identify Inactive Users with Open Incidents\n\nThis background script helps admin to identify **inactive users** who still have **open incidents** assigned to them.  \nIt’s particularly useful during **user cleanup, offboarding audits, or reassignment activities** to ensure no tickets remain unaddressed.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Identify Inactive users with tickets/identify_inactive_users_with_open_tickets.js",
    "content": "var user = new GlideRecord('sys_user');\nuser.addQuery('active', false);\nuser.query();\nwhile (user.next()) {\n    var inc = new GlideRecord('incident');\n    inc.addQuery('assigned_to', user.sys_id);\n    inc.addQuery('state', '!=', 7); // not Closed\n    inc.query();\n    while (inc.next()) {\n        gs.info('Inactive user with open ticket: ' + user.name + ' → ' + inc.number);\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Incident Auto-Categorization Based on Keywords/Incident Auto-Categorization Based on Keywords.js",
    "content": "var grIncident = new GlideRecord('incident');\ngrIncident.addQuery('category', ''); // Only process uncategorized incidents\ngrIncident.query();\n\nwhile (grIncident.next()) {\n    try {\n        var description = grIncident.getValue('short_description');\n        var category = categorizeIncident(description); // Call helper function to categorize\n\n        if (category) {\n            grIncident.category = category;\n            grIncident.update();\n            gs.info('Incident ' + grIncident.number + ' categorized as ' + category);\n        } else {\n            gs.warn('No category found for incident ' + grIncident.number);\n        }\n    } catch (e) {\n        gs.error('Failed to categorize incident ' + grIncident.number + ': ' + e.message);\n    }\n}\n\n// Helper function to categorize incidents based on keywords\nfunction categorizeIncident(description) {\n    if (!description) return null;\n\n    description = description.toLowerCase();\n    if (description.includes('email')) return 'Email';\n    if (description.includes('network')) return 'Network';\n    if (description.includes('password')) return 'Password Reset';\n    if (description.includes('hardware')) return 'Hardware';\n    return null;\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Incident Auto-Categorization Based on Keywords/README.md",
    "content": "# Incident Auto-Categorization Based on Keywords\n\nThis background script auto-categorizes uncategorized incidents based on keywords found in the short_description field, updating the incident with relevant categories (e.g., \"Email\", \"Network\"). \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Limit String and Add Elipses/README.md",
    "content": "This function can be used to show the string with  limited number of characters and add the ellipses to it. This is mostly used on the portal side."
  },
  {
    "path": "Server-Side Components/Background Scripts/Limit String and Add Elipses/script.js",
    "content": "function addElipses(string, maxLength) {\n    var maxLength = Number(maxLength);\n    \n        if (string.length > maxLength) {\n            return string.substr(0, maxLength) + '...';\n        }\n    \n    return string;\n};\n\nvar StringName = 'hafsa asif razzak';\ngs.info(addElipses(StringName, 5));"
  },
  {
    "path": "Server-Side Components/Background Scripts/List Stories and Tasks by User and Date Range/README.md",
    "content": "# List Stories and Tasks by User / Date Range\n\nThis script is nice for those who love playing with background scripts.  The script can be configured with a user ID and a start/end date range, and will list all stories or tasks assigned or closed during that period for the given user.\n\n## Running the Script\n\n1. Update the variables `userName`, `startDate`, and `endDate` to meet your requirements.\n2. Navigate to `Scripts - Background` and copy/paste the contents of `script.js` into the editor, and click **Run script**\n\n## Example of Results\n\nYou will be presented with output such as:\n\n![Results](./results.png)"
  },
  {
    "path": "Server-Side Components/Background Scripts/List Stories and Tasks by User and Date Range/script.js",
    "content": "/**\n * Stories / Tasks Report\n * Stories, Changes, Problems, Tasks\n * Run from Scripts - Background\n * @author: Kevin Custer\n */\n\n// User Variables - Change These ////////////\nvar userName  = \"aileen.mottern\";\nvar startDate = \"2021-10-01 00:00:00\";\nvar endDate   = \"2021-10-31 23:59:59\";\n/////////////////////////////////////////////\n\n// Script Variables\nvar storyCount = 0;\nvar taskCount = 0;\n\n// Print Stories\nvar grStory = new GlideRecord(\"rm_story\");\nvar query =  'assigned_to.user_name=' + userName + '^';\n    query += \"state!=4\" + '^';\n    query += 'sys_created_onBETWEEN' + startDate + '@' + endDate + '^OR';\n    query += 'closed_atBETWEEN' + startDate + '@' + endDate;\n\ngrStory.addEncodedQuery(query);\ngrStory.orderBy(\"closed_at\");\ngrStory.query();\n\ngs.print('           Stories / Tasks Report for: ' + userName);\ngs.print('');\ngs.print('              Report Start: ' + startDate);\ngs.print('                Report End: ' + endDate);\ngs.print('');\ngs.print('');\ngs.print('Stories Assigned or Closed:');\ngs.print('');\n\n// Print Header Row\ngs.print(this.padEnd(\"Number\", 15) +\n         this.padEnd(\"Assigned\", 12) +\n         this.padEnd(\"Closed (*)\", 12) +\n         this.padEnd(\"Product\", 40) +\n         this.padEnd(\"CI\", 70) +\n         this.padEnd(\"State\", 25) +\n         \"Description\");\ngs.print(Array(225).join('-'));\n\nwhile (grStory.next()) {\n  gs.print(this.padEnd(grStory.number.toString(), 15) +\n           this.padEnd(grStory.sys_created_on.getByFormat('MM-dd-yyyy'), 12) + \n           this.padEnd(grStory.closed_at.getByFormat('MM-dd-yyyy') || '', 12) +\n           this.padEnd(grStory.product.getDisplayValue().substring(0,38), 40) +\n           this.padEnd(grStory.cmdb_ci.getDisplayValue().substring(0,68), 70) +\n           this.padEnd(grStory.state.getDisplayValue(), 25) +\n           grStory.short_description);\n  storyCount++;\n}\n\ngs.print('');\ngs.print('Total Stories: ' + storyCount);\ngs.print('');\ngs.print('');\n\n\n// Print Tasks\nvar grTask = new GlideRecord(\"task\");\nvar query = 'assigned_to.user_name=' + userName + '^';\nquery += \"state!=4\" + '^';\nquery += 'sys_class_name!=rm_story^sys_class_name!=rm_scrum_task^';\nquery += 'sys_created_onBETWEEN' + startDate + '@' + endDate + '^OR';\nquery += 'closed_atBETWEEN' + startDate + '@' + endDate;\n\ngrTask.addEncodedQuery(query);\ngrTask.orderBy(\"closed_at\");\ngrTask.query();\n\ngs.print('Non-Story Tasks Assigned or Closed:');\ngs.print('');\n\n// Print Header Row\ngs.print(this.padEnd(\"Number\", 15) +\n         this.padEnd(\"Assigned\", 12) +\n         this.padEnd(\"Closed (*)\", 12) +\n         this.padEnd(\"CMDB CI\", 40) +\n         this.padEnd(\"State\", 25) +\n         \"Description\");\ngs.print(Array(155).join('-'));\n\nwhile (grTask.next()) {\n  gs.print(this.padEnd(grTask.number.toString(), 15) +\n           this.padEnd(grTask.sys_created_on.getByFormat('MM-dd-yyyy'), 12) + \n           this.padEnd(grTask.closed_at.getByFormat('MM-dd-yyyy') || '', 12) +\n           this.padEnd(grTask.cmdb_ci.getDisplayValue().substring(0,38), 40) +\n           this.padEnd(grTask.state.getDisplayValue(), 25) +\n           grTask.short_description);\n  taskCount++;\n}\n\ngs.print('');\ngs.print('Total Tasks: ' + taskCount);\ngs.print('');\ngs.print('');\ngs.print('* - Sorted by ascending values');\n\nfunction padEnd(str, padAmount) {\n  if (typeof str === 'undefined') \n    return '';\n\n  var padding = Array(padAmount + 1).join(' ');\n  return (str + padding).substring(0, padding.length);\n}"
  },
  {
    "path": "Server-Side Components/Background Scripts/List fields in table/README.md",
    "content": "# Get Fields in Table\n\nQuick script to print out the fields on one or multiple tables. Especialy helpful for CMBD tables when a stakeholder inevitably asks \"Can you send me a list of all the tables and fields?\n\n## Use\n\nSimply add the table names from you wish to display the fields in the **table** array.\n\n## Current Configuration\n\nA demo list of CMDB tables is included. The script will list the fields numbered, in alphabetical order, and includes actual field name and label name.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/List fields in table/listFieldsInTable.js",
    "content": "var table = [\n  \"cmdb_ci_netgear\",\n  \"cmdb_ci_firewall_network\",\n  \"cmdb_ci_ip_firewall\",\n  \"cmdb_ci_switch\",\n  \"cmdb_ci_ip_router\",\n  \"cmdb_ci_computer\",\n  \"cmdb_ci_hardware\",\n  \"cmdb_ci_lb\",\n  \"cmdb_ci_lb_interface\",\n  \"cmdb_ci_lb_pool\",\n  \"cmdb_ci_lb_pool_member\",\n  \"cmdb_ci_lb_service\",\n  \"cmdb_ci_vlan\",\n  \"cmdb_ci_server\",\n  \"cmdb_ci_linux_server\",\n  \"cmdb_ci_win_server\",\n  \"cmdb_ci_ups\",\n];\n\nfor (var i = 0; i < table.length; i++) {\n  gs.print(\"<<<<<-------START TABLE------->>>>>\");\n  gs.print(\"TABLE \" + table[i]);\n  getFields(\"alm_asset\");\n  gs.print(\"<<<<<-------END TABLE------->>>>>\\n\\n\");\n}\n\nfunction getFields(table) {\n  var i = 0;\n\n  var gr = new GlideRecord(\"sys_dictionary\");\n\n  gr.addQuery(\"name\", table);\n\n  gr.query();\n  gs.print(\"Row, Field name, Display name\");\n  while (gr.next()) {\n    i = i + 1;\n    gs.print(\"Field \" + i + \": \" + gr.element + \" (\" + gr.column_label + \")\");\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Logout User/README.md",
    "content": "# Logout Users\nUsed to log out all users within the platform.\n\n## Usage\nRun script in **logoutUser.js** in 'Scripts - Background'\n\n* **Parameters:** \n    - **ignoreUser:** Username of a sys_user that is ignored (ex. 'admin')\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Logout User/logoutUser.js",
    "content": "logOutAllUsers('admin'); // Admin user is ignored.\n\nfunction logOutAllUsers(ignoreUser) {\n    var logoutCounter = 0;\n    var grSession = new GlideRecord(\"v_user_session\");\n    if (ignoreUser && ignoreUser != '') {\n        grSession.addQuery(\"user\", \"!=\", ignoreUser);\n    }\n    grSession.query();\n\n    while (grSession.next()) {\n        var username = grSession.user;\n\n        // Try to find the user record, based on their username.\n        var grUser = new GlideRecord(\"sys_user\");\n        grUser.addQuery(\"user_name\", username);\n        grUser.setLimit(1);\n        grUser.query();\n\n        if (grUser.next()) {\n            // Logout the user\n            gs.print(\"Logging out session for user \" + username + \".\");\n            logoutCounter += 1;\n            grSession.locked = true;\n            grSession.update();\n        }\n    }\n    gs.print(\"Completed logout of \" + logoutCounter + \" users.\");\n}"
  },
  {
    "path": "Server-Side Components/Background Scripts/Merge Duplicate User Records Automatically/Readme.md",
    "content": "Merge Duplicate User Records Automatically\n\n\nAutomatically detects and merges duplicate User records in ServiceNow based on the email address.\n\nEnsures data integrity and prevents duplicate entries.\n\nReassigns related records (e.g., incidents) to a master record.\n\nDeactivates duplicate users.\n\nIn this UseCase\nMultiple users exist with the same email address.\n\nThe first record found is treated as the master user.\n\nAll duplicates are: Reassigned in related records (e.g., tasks, incidents).\n\nMarked inactive.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Merge Duplicate User Records Automatically/script.js",
    "content": "(function() {\n\n    var userEmailMap = {};     \n    var userCreatedMap = {};   \n    var duplicatesFound = 0;\n\n    var userGR = new GlideRecord('sys_user');\n    userGR.addNotNullQuery('email');\n    userGR.addQuery('active', true);\n    userGR.orderBy('email'); // Group by email \n    userGR.query();\n\n    while (userGR.next()) {\n        var email = userGR.email.toLowerCase();\n        var createdOn = new GlideDateTime(userGR.sys_created_on).getNumericValue(); // Convert to timestamp\n\n        if (!userEmailMap[email]) {\n            // First user found for this email temporarily mark as master\n            userEmailMap[email] = userGR.sys_id.toString();\n            userCreatedMap[email] = createdOn;\n        } else {\n            // Another user with same email found \n            var masterSysId = userEmailMap[email];\n            var masterCreatedOn = userCreatedMap[email];\n\n            var masterIsOlder = createdOn > masterCreatedOn ? true : false;\n\n            if (masterIsOlder) {\n                // If this user was created later, we keep the existing master\n                // earliest created record will be accepted\n                mergeDuplicateUser(userGR, masterSysId);\n            } else {\n                // If this user was created earlier, this becomes the new master\n                var oldMasterGR = new GlideRecord('sys_user');\n                if (oldMasterGR.get(masterSysId)) {\n                    mergeDuplicateUser(oldMasterGR, userGR.sys_id); // merge old master into new master\n                }\n                // Update maps\n                userEmailMap[email] = userGR.sys_id.toString();\n                userCreatedMap[email] = createdOn;\n            }\n\n            duplicatesFound++;\n        }\n    }\n\n    gs.info(\"Total duplicates merged: \" + duplicatesFound);\n\n\n   \n    function mergeDuplicateUser(duplicateGR, masterSysId) {\n        // Reassign related incidents\n        var incGR = new GlideRecord('incident');\n        incGR.addQuery('caller_id', duplicateGR.sys_id);\n        incGR.query();\n        var count = 0;\n        while (incGR.next()) {\n            incGR.caller_id = masterSysId;\n            incGR.update();\n            count++;\n        }\n\n        // Deactivate  user\n        duplicateGR.active = false;\n        duplicateGR.u_merged_to = masterSysId; // optional custom field\n        duplicateGR.update();\n\n        gs.info(\"Merged duplicate user '\" + duplicateGR.name + \"' → master: \" + masterSysId + \". Reassigned \" + count + \" incidents.\");\n    }\n\n})();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Move Customer Updates/README.md",
    "content": "# Move Customer Updates\nI've developed a script to facilitate the transfer of customer updates between two update sets within the same application scope. The script needs to run at the global scope for execution.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Move Customer Updates/moveCustomerUpdates.js",
    "content": "//A background script to move customer updates from source update set to target update set within the same application scope.\nfunction moveCustomerUpdates(sourceUpdateSetSysId, targetUpdateSetSysId) {\n    var customerUpdateGR = new GlideRecord('sys_update_xml');\n    customerUpdateGR.addQuery('update_set', sourceUpdateSetSysId);\n    customerUpdateGR.setValue('update_set', targetUpdateSetSysId);\n    customerUpdateGR.updateMultiple();\n}\n\nmoveCustomerUpdates('sourceUpdateSetSysId', 'targetUpdateSetSysId');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Notify User of Password Expiry/README.md",
    "content": "This script will work to notify the users if their password is going to expire in less than 7 days. \nPrerequisite : You will need to create an event first.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Notify User of Password Expiry/script.js",
    "content": "var userGR = new GlideRecord('sys_user');\nuserGR.query();\nwhile (userGR.next()) {\n    var expiryDate = new GlideDateTime(userGR.password_needs_reset_by);\n    var today = new GlideDateTime();\n    var diff = GlideDateTime.subtract(expiryDate, today);\n\n    // Check the difference in days\n    if (diff.getDays() < 7) {\n        gs.eventQueue('password_expiry', userGR, 'Your password will expire in ' + diff.getDays() + ' days.', '');\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Orphaned Users/README.md",
    "content": "This script identifies active users in ServiceNow who have no group memberships and no roles assigned.\nIt queries the sys_user table for all active users, then checks each user against the sys_user_grmember table (groups) and the sys_user_has_role table (roles).\nIf a user has no associated groups and no assigned roles, their username is added to a list called orphanedUsers.\nFinally, the script prints the list, which can be used for user cleanup, security audits, or compliance purposes to ensure proper user management.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Orphaned Users/Users with no groups and roles.js",
    "content": "var userRecord = new GlideRecord('sys_user');\nuserRecord.addQuery('active', true);\nuserRecord.query();\n\nvar orphanedUsers = [];\n\nwhile(userRecord.next()) {\n    var userSysId = userRecord.getValue('sys_id'); \n\n    var userGroups = new GlideRecord('sys_user_grmember');\n    userGroups.addQuery('user', userSysId); \n    userGroups.query();\n    \n    var userRoles = new GlideRecord('sys_user_has_role');\n    userRoles.addQuery('user', userSysId); \n    userRoles.query();\n    \n    if(!userGroups.hasNext() && !userRoles.hasNext()) {\n        orphanedUsers.push(userRecord.getValue('user_name')); \n    }\n}\n\ngs.print('Orphaned Users: ' + orphanedUsers.join(', '));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Parse ISO8601 Date/README.md",
    "content": "# ISO8601 Date Parser Script\n\nSome APIs commonly return dates in ISO8601 format. This script can help you parse the date and get `'GlideDateTime'` object in return.\n\n## Usage\n\nYou can use this script to parse ISO8601 date strings in a Business Rule, Background Script, Script Include, etc.\n\n### Function: `parseISO8601DateTime(isoDate)`\n\nThis function takes an ISO8601 date string as input and returns a GlideDateTime object.\n\n#### Parameters\n\n- `isoDate` (string): The ISO8601 date string to be parsed. It should be\nin the following format: `'YYYY-MM-DDTHH:mm:ssZ'` (e.g., `'2022-05-01T14:30:00Z'`).\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Parse ISO8601 Date/script.js",
    "content": "/* ISO8601 Date. 30 Hours were put for demonstration purposes */\nvar date = '2022-05-01T30:00:00Z';\n\ngs.info('Parsed date: ' + parseISO8601DateTime(date));\n\n/* Function to parse ISO8601 date format */\nfunction parseISO8601DateTime(isoDate) {\n    try {\n        /* Check if the input date is valid and not the default value */\n        if (isoDate && isoDate !== '1970-01-01T00:00:00Z') {\n            /* Split the date and time components */\n            var dateAndTime = isoDate.split('T');  \n\n            /* Ensure there are both date and time components */\n            if (dateAndTime.length !== 2) {\n                /* Handle invalid input format */\n                gs.error('Invalid ISO8601 date format: ' + isoDate);\n                return null;\n            }\n\n            var date = dateAndTime[0];\n            var time = dateAndTime[1].substring(0, 8);\n\n            /* Construct a valid GlideDateTime object */\n            var parsedDateTime = new GlideDateTime(date + ' ' + time);\n            return parsedDateTime;\n        }\n    } catch (e) {\n        gs.error('Error parsing date: ' + e.message);\n    }\n    /* Return false in case of any errors */\n    return null;\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Prevent unnecessary notifications from being sent out/Prevent_unnecessary_notifications_from_being_sent_out.js",
    "content": "var emailGR = new GlideRecord('sys_email');\n\n// Query for the emails you want to ignore\nemailGR.addQuery('state', 'ready'); // Only emails that are ready to send\nemailGR.addEncodedQuery(\"sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()\"); // Optional timeline filter\n\n// Set the fields to ignore and update all matching records at once\nemailGR.setValue('state', 'ignored'); // Set state to \"ignored\"\nemailGR.setValue('type', 'send-ignored'); // Set type to 'send-ignored'\nemailGR.updateMultiple(); // Bulk update all matching records\n\ngs.info('All relevant emails have been marked as ignored.');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Prevent unnecessary notifications from being sent out/README.md",
    "content": "Created a background script to prevent unnecessary notifications from being sent out. \nIt helps in managing the volume of emails being sent so that we do not send the notifications even by mistake. \nThis script is mostly used in dev or uat to avoid any notifications being sent from lower instances.\n\nWe are querying the sys_email table to find all the emails with below queries:\n--> emails with state as \"ready\"\n--> emails that were created on today (optional query, if not added all the mails with state as \"ready\" will be considered for getting ignored.) \n\nPost query we are setting as below:\n--> state of the email to \"ignored\"\n--> type of the email to \"send-ignored\"\n\nAfter setting the fields we are updating the records.\n\nPlease be cautious while using the script in Production environment.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/QuickCurrent/README.md",
    "content": "# QuickCurrent\n\nI use this all the time to test out workflow or business rule script logic without having to go through end-to-end testing of a catalog item workflow or fill out mandatory fields or approvals multiple times just to make sure a complex script has the desired output.\n\nBy having a quick snippet to create a \"current\" variable, there is no need to modify the script that may be retrieving or setting values on \"current\" in the workflow or business rule.\n\n## Use\n\n1. Create the desired record and ensure all needed values are filled in.\n2. Enter the table name in the **table** variable (sc_req_item is default)\n3. Copy and paste the sys_id from the test record into the **sid** variable\n4. Paste script below the comment and run to see if it works as expected\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/QuickCurrent/quickCurrent.js",
    "content": "var table = \"sc_req_item\"; //Requested item\nvar sid = \"[Insert record sys_id]\";\nvar current = new GlideRecord(table);\ncurrent.get(sid);\nvar vars = current.variables; //Short-hand access to variables to check values before/after running the script\n//ex: vars.requested_for, vars.color, vars.desired_delivery_date\ngs.info(current.number + \"\\n\" + current.short_description); //display record number and short description\n\n//##### Enter script using 'current' below this line #####\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Read Encoded Query/README.md",
    "content": "This background script code to get the any encoded query in redable format.\nYou need to paste the encoded query in the quotes of the function API which you want to read in simple layman format \n\nInput Required as below\n\n1. Table name on which the query is.\n2. Any encoded query which you want to read\n\n   Input Eg.\n\n   Table Name- incident\n   Encoded query  - 'active=true^short_descriptionLIKEtest'\n\n   Output- Readable Query is Active = true .and. Short description contains test\n\n\n   Please note that this API is allowed to worked in global application. It is not applicable in scoped application.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Read Encoded Query/readQuery.js",
    "content": "// This code get the encoded query in readable format.\n// Pass the table name and encoded query in the function API whoch you want to read.\n\nvar grQ= new GlideQueryBreadcrumbs().getReadableQuery('table name', 'Pass the encoded query');\ngs.info(\"Readable Query is \"+grQ);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Reassignment of Manager from Group and User Table/README.md",
    "content": "## Reassignment of Manager from Group and User Tables to New Manager for Outgoing/Retiring Manager\n\nAutomatically reassigns all groups and users managed by a retiring manager to a new manager and deactivates the outgoing manager’s **sys_user** record.\n\n- Ensures transition by updating manager references in both user and group tables before disabling the old manager’s access.  \n- Uses a Background Script to perform the following actions:\n  - Updates all groups where the old manager is assigned   \n  - Updates all users reporting to the old manager by new manager \n  - Make old manager’s user record inactive in **sys_user** record\n\n### Prerequisites :\n- Keep old manager's and new manager's sys_ids ready\n- Navigate to System Definition → Scripts - Background\n- Click New and paste the script. In the script Replace with your requirement: \n  - var oldManagerSysId = `<Include sys id of old Manager >`\n  - var newManagerSysId =  `<Include sys id of New Manager >`\n- Run Script\n\n---\n\n### Example Of Group Table Record Before Script Execution \n\n![Manager Reassignment](BackGroundScript_UpdateManager_Replace_2.png)\n\n---\n\n### Background Script Execution \n\n![Manager Reassignment](BackGroundScript_UpdateManager_Replace_3.png)\n\n---\n\n### Example Of Group Table Record After Script Execution\n\n![Manager Reassignment](BackGroundScript_UpdateManager_Replace_4.png)\n\n---\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Reassignment of Manager from Group and User Table/backgroundScriptManagerReassign.js",
    "content": "var oldManagerSysId = '506c0f9cd7011200f2d224837e61030f'; // <Include sys id of old Manager >\nvar newManagerSysId = '46c6f9efa9fe198101ddf5eed9adf6e7'; // <Include sys id of New Manager >\n\ngs.print('========== MANAGER REASSIGNMENT FROM GROUP, USERS AND DEACTIVATE RETIRING MANAGER USER ID ==========');\n\n// --- STEP 1: Update Groups managed by old manager ---\nvar grpCount = 0;\nvar grpGR = new GlideRecord('sys_user_group');\ngrpGR.addQuery('manager', oldManagerSysId);\ngrpGR.query();\n\ngs.print('Checking groups managed by retiring manager...');\nwhile (grpGR.next()) {\n    gs.print('➡️ Group: ' + grpGR.name + ' | Old Manager: ' + grpGR.manager.getDisplayValue());\n\n    grpGR.manager = newManagerSysId;\n    grpGR.update();\n    gs.print('✅ Group manager updated ');\n\t\n\tgrpCount++;\n}\ngs.print('Total groups updated: ' + grpCount);\n\n// --- STEP 2: Update Users reporting to old manager ---\nvar userCount = 0;\nvar userGR = new GlideRecord('sys_user');\nuserGR.addQuery('manager', oldManagerSysId);\nuserGR.query();\n\ngs.print('\\ Checking users reporting to retiring manager...');\nwhile (userGR.next()) {\n    gs.print('👤 User: ' + userGR.getDisplayValue('name') + ' | Current Manager: ' + userGR.manager.getDisplayValue());\n    userGR.manager = newManagerSysId;\n    userGR.update();\n    gs.print('✅ User manager updated ');\n    userCount++;\n}\ngs.print('Total users updated: ' + userCount);\n\n// --- STEP 3: Deactivate old manager ---\nvar mgrGR = new GlideRecord('sys_user');\nif (mgrGR.get(oldManagerSysId)) {\n    gs.print('\\n Retiring Manager: ' + mgrGR.getDisplayValue('name'));\n    mgrGR.active = false;\n    mgrGR.locked_out = true; // optional – prevents login\n    mgrGR.update();\n    gs.print('✅ Old manager deactivated and locked out.');\n} else {\n    gs.print(' Could not find old manager record.');\n}\n\ngs.print('\\n========== PROCESS COMPLETE ==========');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Remove Inactive User/README.md",
    "content": "This code snippet helps to remove the inactive users from active groups. It won't delete the user record from \"sys_user\" table it will just delete the entry from the \"sys_user_grmember\" table.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Remove Inactive User/Remove Inactive user from active group.js",
    "content": "var user = new GlideRecord(\"sys_user\");\nuser.addInactiveQuery();  // Filter the InActive user from the sys_user table.\nuser.query();\nwhile(user.next()){\nvar group = new GlideRecord(\"sys_user_group\");\ngroup.addQuery(\"user\",user.getUniqueValue());  // Compare the group in which Inactive user would find.\ngroup.query();\nif(group.next()){\n\t// gs.print(\"Group sys_id is \"+ group.getUniqueValue());\n\tvar groupMember= new GlideRecord(\"sys_user_grmember\");\n\tgroupMember.addQuery(\"group\",group.getUniqueValue());  // Compare the group member in which Inactive user would find.\n\tgroupMember.query();\n\tif(groupMember.next()){\n\t\tgs.print(\"Group sys_id is \"+ group.getUniqueValue());  // Get the sys_id of the group;\n\t\tgs.print(\"Member sys_id is \"+ groupMember.getUniqueValue());  // Get the sys_id of the member which is inactive.\n\t\tgs.print(\"Deleted record is \"+ groupMember.sys_id);  // Get the sys_id of the record which is going to delete;\n\t\tgroupMember.deleteRecord();  // Delete the record\n\t\t\n\t}\n}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Remove element from list field/README.md",
    "content": "This script can be used to remove a specific element from a list field across multiple records in a table that match a query condition.\n\nIt requires 3 variables to be set:\n- table {string}: the name of the table to run on, e.g. 'kb_knowledge_block'\n- listField {string}: the name of the list field on the above table to remove an element from, e.g. 'can_read_user_criteria'\n- whatToRemove {sysId}: the sys_id of the element to remove from the list field, e.g. sys_id of a specific user critria\n\nThe script contains additional inline comments about what it does while runnning.\n\nFurther context annd use case can be read in the [related community post](https://www.servicenow.com/community/developer-forum/glide-list-type-field-need-to-remove-one-value-in-bulk/m-p/2431257#M947276) where this code was used as the solution by yours truly.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Remove element from list field/removeFromList.js",
    "content": "var table = ''; // Name of the table, e.g. kb_knowledge_block\nvar listField = ''; // Name of the list field to remove an element from, e.g. can_read_user_criteria\nvar whatToRemove = ''; // Sys_id of the element to remove from the list field, e.g. sys_id of user criteria\nvar encQ = listField + 'LIKE' + whatToRemove; // encoded query to limit queried record to those that contain the element we want to remove\nvar listArray = []; // initial array variable declaration\nvar elementIndex = -1; // initial index variable declaration\n\n\n/* Run a GlideRecord query to find all records that contain the element to be deleted from the specifid list field */\nvar listGr = new GlideRecord(table);\nlistGr.addEncodedQuery(encQ);\nlistGr.query();\nwhile(listGr.next()) {\n  listArray = listGr[listField].toString().split(','); // set the array variable based on the List field of the found record\n  elementIndex = listArray.indexOf(whatToRemove); // search for the element to remove from this particular record\n  /* Only try to remove the element and update the record if it was found, i.e. not -1 */\n  if(elementIndex > -1) {\n    listArray.splice(elementIndex,1);\n    listGr.setValue(listField,listArray);\n    listGr.update();\n  }\n  /* Reset the initial array related variables */\n  listArray = [];\n  elementIndex = -1;\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Remove roles from inactive user/README.md",
    "content": "\n# Remove all roles from inactive user\n\nCode Snippet : Remove all roles from an inactive user \nWhen a user in an instance is inactive, it's a good practice to remove all roles assigned to that user. Following piece of code helps to remove all the roles from the inactive user.\n~~~ \nvar gr = new GlideRecord('sys_user_has_role');\ngr.addEncodedQuery('user.active=false');\ngr.query();\ngr.deleteMultiple();\n~~~\nThis piece of code can be used in scheduled jobs under scheduled script execution tab. This can be run weekly once to check the last one week inactive users and remove them from all assigned roles (if exist).\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Remove roles from inactive user/script.js",
    "content": "var gr = new GlideRecord('sys_user_has_role');\ngr.addEncodedQuery('user.active=false');\ngr.query();\ngr.deleteMultiple();"
  },
  {
    "path": "Server-Side Components/Background Scripts/Rename reports - Avoid duplicate names/readme.md",
    "content": "How often we come across cases where reports are created with same name by different users. Well to maintain some uniquenss across why not have some controls to get it unified. Below script can be used to suffix 'Created by' to the report to help uniquely identify report.\nExecute it as a background script to update it in bulk.\nFor example Report named ABC will becom ABC - [PQR]v1 and ABC - [XYZ]v2 where PQR and XYZ are created by users \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Rename reports - Avoid duplicate names/script.js",
    "content": "var ar = [];\nvar dupCheck = new GlideAggregate('sys_report');\ndupCheck.addEncodedQuery('titleISNOTEMPTY');\ndupCheck.addNotNullQuery('title');\ndupCheck.groupBy('title');\ndupCheck.addAggregate('COUNT', 'title');\ndupCheck.addHaving('COUNT', '>', 1);\ndupCheck.query();\nwhile (dupCheck.next()) {\nar.push(dupCheck.getValue(\"title\"));\n}\n\nfor(var i = 0 ; i< ar.length; i++){\n\tvar report = new GlideRecord(\"sys_report\");\n\treport.addQuery(\"title\",ar[i]);\n\treport.query();\nvar c= 0;\n\twhile(report.next()){\n\t\tc++;\n\t\tvar user = new GlideRecord(\"sys_user\");\n\t\tuser.addQuery(\"email\",report.sys_created_by.toString());\n\t\tuser.query();\n\t\tif(user.next()){\n\t\t\tvar name = user.name;\n\t\t}\n\t\t\n\t\tif(name){\n\t\treport.title = report.title+\" \"+' - [' + name + ']'+\" \"+\"v\"+c; // Report named ABC will now be ABC - [PQR]v1 and ABC - [XYZ]v2 where PQR and XYZ are created by users \n\t\treport.setWorkflow(false);\n\t\treport.autoSysFields(false);\n\t\treport.update();\n\t\t}\n\t\telse {\n\t\t\treport.title = report.title+\" \"+\"v\"+c;\n\t\t\treport.setWorkflow(false);\n\t\t\treport.autoSysFields(false);\n\t\t\treport.update();\n\t\t\t\n\t\t}\n\t}\n\t\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Replace Text/README.md",
    "content": "Purpose of the script is to help replace text IT_SAP with IT_ERP for all the Groups in Group table containing IT_SAP as keyword.\nIt uses replace method which follows format replace(/'ABCD'/g,'PQR')\nwhere ABCD is to be replaced with PQR\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Replace Text/script.js",
    "content": "var replacemyname;\nvar replaceis=new GlideRecord('sys_user_group');\nreplaceis.addEncodedQuery('nameLIKEIT_SAP');\nreplaceis.query();\nwhile(replaceis.next())\n{\nreplaceis.name=replaceis.name.replace(/IT_SAP/g, 'IT_ERP'); // /IT_SAP/g is oldvalue 'IT_ERP' is new value\nreplaceis.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Restart RITM Flow/README.md",
    "content": "# Restart RITM Flow\n\nRestarts a Flow Designer flow for an individual RITM.\n\n## Description\n\nRestart a Flow Designer flow by running restart-ritm-flow.js as a background or fix script. Use the ritmSysId variable to store the sys_id of the RITM for which you want the flow to be restarted. Output is logged to the sys_log table.\n\n## Getting Started\n\n### Dependencies\n\n* Must be in the Global scope.\n\n### Execution\n\n* Copy the script from restart-ritm-flow.js to either a background script or a fix script.\n* Set the sys_id of the target RITM in the ritmSysId variable.\n* Run the script and the flow will be restarted.\n\n## Authors\n\nBrad Warman \n\nhttps://www.servicenow.com/community/user/viewprofilepage/user-id/80167\n\n## Version History\n\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Restart RITM Flow/restart-ritm-flow.js",
    "content": "var ritmSysId = '443c2b1f1b95f15036d94268b04bcbe9'; // Enter the RITM sys_id\n\nvar grScReqItem = new GlideRecord('sc_req_item');\ngrScReqItem.addEncodedQuery(\"sys_id=\" + ritmSysId);// sys_id of the RITM\ngrScReqItem.setLimit(1);\ngrScReqItem.query();\n\nif (grScReqItem.next()) {\ntry {\n\tgs.info(\"Restarting flow for \" + grScReqItem.number);\n\tvar flow = grScReqItem.cat_item.flow_designer_flow;\n\tvar flowName = flow.sys_scope.scope + \".\" + flow.internal_name;\n    \tvar inputs = {};\n    \tinputs['request_item'] = grScReqItem; // GlideRecord of table: sc_req_item\n    \tinputs['table_name'] = 'sc_req_item';\n\n    \tvar contextId = sn_fd.FlowAPI.startFlow(flowName, inputs);\t\n\t\n  } catch (ex) {\n    var message = ex.getMessage();\n    gs.error(\"Error restarting flow for \" + grScReqItem.number + \"\\n\\n\" + message);  \n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retiring KB articles in bulk/Readme.md",
    "content": "# Retire IT Knowledge Base Articles - Background Script\n\n## 📘 Overview\nThis ServiceNow **Background Script** is designed to automatically retire all Knowledge Articles associated with the **IT Knowledge Base** (`kb_knowledge_base` = \"IT\").  \n\nIt helps administrators clean up outdated or inactive articles in bulk, ensuring that end-users can only access relevant and up-to-date IT knowledge content.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retiring KB articles in bulk/Retire Articles.js",
    "content": "var article = new GlideRecord(\"kb_knowledge\");\narticle.addQuery(\"workflow_state\",\"published\");\narticle.addQuery(\"kb_knowledge_base\",\"a7e8a78bff0221009b20ffffffffff17\")  // Sys ID of the IT Knowledge base. You can provide the sys Id of any knowledge base.\narticle.query();\nwhile(article.next()){\n  article.workflow_state = \"retired\";\n  article.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retrieve Impersonation Insights/impersonationInsights.js",
    "content": "var impersonatorUserID = 'zane.sulikowski'; //Replace it with the user ID of user for whom  we need to check impersonation details\n\nvar isUserPresent = new GlideRecord('sys_user');\nif (isUserPresent.get('user_name', impersonatorUserID)) {\n\n    var queryEvents = new GlideRecord('sysevent');\n    queryEvents.addEncodedQuery(\"name=impersonation.start^ORname=impersonation.end^parm1=\" + impersonatorUserID + \"^sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()\");\n    queryEvents.orderBy('sys_created_on');\n    queryEvents.query();\n\n    //This object will hold all events grouped by impersonated user which is in parm2\n    var userEvents = {};\n\n    while (queryEvents.next()) {\n        var impersonatedId = queryEvents.getValue('parm2');\n        if (!userEvents[impersonatedId])\n            userEvents[impersonatedId] = [];\n        userEvents[impersonatedId].push({\n            name: queryEvents.getValue('name'),\n            time: queryEvents.getValue('sys_created_on')\n        });\n    }\n\n} else {\n    gs.info('Invalid User');\n}\n\n\nfunction getUserName(sysId) {\n    var getUser = new GlideRecord('sys_user');\n    if (getUser.get(sysId)) {\n        return getUser.getDisplayValue('name');\n    }\n    return sysId;\n}\n\n\nfor (var userId in userEvents) {\n    var events = userEvents[userId];\n    var totalSeconds = 0;\n    var startTime = null;\n\n    events.forEach(function(evt) {\n        if (evt.name === 'impersonation.start') {\n            startTime = new GlideDateTime(evt.time);\n        } else if (evt.name === 'impersonation.end' && startTime) {\n            var endTime = new GlideDateTime(evt.time);\n            totalSeconds += (endTime.getNumericValue() - startTime.getNumericValue()) / 1000;\n            startTime = null;\n        }\n    });\n\n\n    var hours = Math.floor(totalSeconds / 3600);\n    var minutes = Math.floor((totalSeconds % 3600) / 60);\n    var seconds = Math.floor(totalSeconds % 60);\n\n    gs.info(impersonatorUserID + \" impersonated User: \" + getUserName(userId) +\n        \" - Total Duration of impersonation is : \" + hours + \"hrs \" + minutes + \"min \" + seconds + \"sec (\" + totalSeconds + \"sec)\");\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retrieve Impersonation Insights/readme.md",
    "content": "This script helps to get the impersonator and impersonated user details and duration of impersonation.\n\n\nDetails of Events:\n\n(impersonation.start) which shows that the impersonation has started, \n(impersonation.end) shows that the impersonation has ended.\n\nParm1 contains the userid of user who started the impersonation.\nParm2 contains the userid of user whom we have impersonated.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retrieve MRVS Name and Value/RetrieveMRVSNameAndValue.js",
    "content": "//GlideRecord to the RITM table to access the name and corresponding value of MRVS\nvar getVariables = new GlideRecord('sc_req_item');\n\n//Pass the sysId of RITM record\nif (getVariables.get(\"<pass_ritm_sys_id_here>\")) {\n\n  //<mobile_devices_set> is the internal name of MRVS\n    var mrvsInternalName = getVariables.variables.mobile_devices_set;\n    var rowsCount = mrvsInternalName.getRowCount();\n\n    if (rowsCount > 0) {\n        for (var i = 0; i < rowsCount; i++) {\n            var getRowVal = mrvsInternalName.getRow(i);\n            var getCellVal = getRowVal.getCells();\n\n            for (var j = 0, len = getCellVal.length; j < len; j++) {\n                var mrvsFieldName = getCellVal[j].getName();\n                var mrvsFieldValue = getCellVal[j].getCellDisplayValue();\n                gs.info(mrvsFieldName + ' : ' + mrvsFieldValue);\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retrieve MRVS Name and Value/readme.md",
    "content": "This code comes handy to retrieve the name and value of Multi Row Variable Sets(MRVS).\n\nSample:\nRITM is raised with below MRVS details:\n\n<img width=\"1819\" height=\"290\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b03063c1-a8a5-4128-b19f-d451e80b7388\" />\n\n\nBackground Script:\n\n<img width=\"886\" height=\"515\" alt=\"image\" src=\"https://github.com/user-attachments/assets/2d368b99-88d9-4e2f-9fc9-985b1d68efad\" />\n\nOutput:\n\n<img width=\"536\" height=\"194\" alt=\"image\" src=\"https://github.com/user-attachments/assets/43aa8ac8-0799-43cc-a7d6-a80dc28d36a3\" />\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retrieve age of Incident/README.md",
    "content": "This script is designed to calculate the age of incidents in the ServiceNow by comparing the creation date of each incident with the current date. It retrieves all the records from the incident table and iterates through them. For each incident, it calculates the time difference between its creation date (sys_created_on) and the current date in milliseconds, then converts that value into days. This approach is useful for monitoring how long incidents have been open, helping teams prioritize older issues and ensuring timely resolution. Whether you're tracking overall service performance or looking for bottlenecks in your incident management processes, this script can provide valuable insights into the duration of open incidents, aiding in improving service delivery.\n\nvar grIncidentAge = new GlideRecord('incident'); //This line creates a new GlideRecord object for the incident table, allowing the script to query and manipulate incident records.\ngrIncidentAge.query(); //This line executes a query to retrieve all records from the incident table.\nwhile (grIncidentAge.next()) { //The while loop iterates over each record returned by the query. The next() method moves to the next record in the result set.\nvar incCreated = new GlideDateTime(grIncidentAge.sys_created_on); //A new GlideDateTime object is created using the sys_created_on field of the current incident record. This field stores the date and time when the incident was created.\nvar nowDT = new GlideDateTime(); //Another GlideDateTime object is created that contains the current date and time.\nvar ageInMilliseconds = nowDT.getNumericValue() - incCreated.getNumericValue(); //The numeric values of the current and created dates are obtained, and the difference is calculated to find the age of the incident in milliseconds.\nvar ageInDays = Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24)); //The age in milliseconds is converted to days by dividing by the number of milliseconds in a day (1,000 milliseconds/second * 60 seconds/minute * 60 minutes/hour * 24 hours/day). The result is rounded down using Math.floor().\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Retrieve age of Incident/ageOfIncidents.js",
    "content": "var grIncidentAge = new GlideRecord('incident');\ngrIncidentAge.query();\nwhile (grIncidentAge.next()) {\n    var incCreated = new GlideDateTime(grIncidentAge.sys_created_on);\n    var nowDT = new GlideDateTime();\n    var ageInMilliseconds = nowDT.getNumericValue() - incCreated.getNumericValue(); //The numeric values of the current and created dates are obtained, and the difference is calculated to find the age of the incident in milliseconds.\n    var ageInDays = Math.floor(ageInMilliseconds / (1000 * 60 * 60 * 24)); //The age in milliseconds is converted to days by dividing by the number of milliseconds in a day (1,000 milliseconds/second * 60 seconds/minute * 60 minutes/hour * 24 hours/day). The result is rounded down using Math.floor().\n    gs.info('Incident ' + grIncidentAge.number + ' is ' + ageInDays + ' days old.');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/RetrieveAPIKey/Readme.md",
    "content": "This BG script will be helpful to fetch the subscription key for any API to view key value if forget, store and update if required for doc purpose\n\nThis will use GlideEncrypter and Decrypter API. \n"
  },
  {
    "path": "Server-Side Components/Background Scripts/RetrieveAPIKey/getSubscriptionKey.js",
    "content": "/*\nThis BG script will be helpful to fetch the subscription key for any API to view key value if forget, store and update if required for doc purpose\n\nThis will use GlideEncrypter and Decrypter API\n*/\nvar gr = new GlideRecord('api_key_credentials');\n        gr.addQuery('name', ' <ENter your Key Name for ex MSFT subscription Key>'); // Replace with your credential name\n        gr.query();\n        if (gr.next()) {\n            var encryptedApiKey = gr.api_key; // Assuming 'api_key' is the field name\n            var decryptedApiKey = new GlideEncrypter().decrypt(encryptedApiKey);\n            gs.info(\"Decrypted API Key: \" + decryptedApiKey);\n        }\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Run a Scheduled Job/README.md",
    "content": "A script to run an existing Scheduled Job programmatically. Can be used as Background script, in a Script Include or anywhere a server side script is available.\nReplace `sys_id` with the Sys ID of the Scheduled Job to be run.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Run a Scheduled Job/run-scheduled-job.js",
    "content": " // Generic/parent scheduled job table = sysauto\n var grScheduledJob = new GlideRecord(\"sysauto\");\n if (grScheduledJob.get(sys_id)) {\n\t// In order to run the job successfully, we need the more specific\n\t// child table name of the scheduled job (eg. sysauto_script)\n     var classname = grScheduledJob.getValue('sys_class_name');\n     var scheduledJob = new GlideRecord(classname);\n     if (scheduledJob.get(sys_id))\n         gs.executeNow(scheduledJob);\n }\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/SQL Checker/README.md",
    "content": "There is a simple way, how the generated SQL query can be checked, without activating the SQL Debugger feature in the navigator.\n\nHere you can see a small code snippet:\n```JS\ntry {\n\tgs.trace(true);\n\tvar incGr = new GlideRecord(\"incident\");\n\tincGr.setLimit(10);\n\tincGr.orderByDesc(\"sys_created_on\");\n\tincGr.query();\n}\nfinally {\n\tgs.trace(false);\n}\n```\nBy enabling the trace feature, the generated SQL query will be visible in the output:\n```SQL\nSELECT task0.`sys_id`\nFROM task task0\nIGNORE index(sys_created_on)\nWHERE task0.`sys_class_name` = 'incident'\nORDER BY task0.`sys_created_on` DESC\nLIMIT 0, 10\n```\n\nAnother example:\n```JS\ntry {\n\tgs.trace(true);\n\tvar incGr = new GlideRecord(\"incident\");\n\tincGr.setLimit(10);\n\tincGr.orderByDesc(\"sys_created_on\");\n\tincGr.query();\n\twhile (incGr.next()) {\n\t\tgs.info(\"Number: \" + incGr.getValue(\"number\") + \" Created: \" + incGr.getValue(\"sys_created_on\"));\n\t}\n}\nfinally {\n\tgs.trace(false);\n}\n```\n\n```SQL\nSELECT task0.`sys_id`\nFROM task task0\nIGNORE index(sys_created_on)\nWHERE task0.`sys_class_name` = 'incident'\nORDER BY task0.`sys_created_on` DESC\nLIMIT 0, 10\n```\n```SQL\nSELECT task0.`parent`,\n       task0.`a_ref_3` AS `caused_by`,\n       task0.`watch_list`,\n       task0.`upon_reject`,\n       task0.`sys_updated_on`,\n       task0.`a_str_5` AS `origin_table`,\n       task0.`approval_history`,\n       task0.`skills`,\n       task0.`number`,\n       task0.`state`,\n       task0.`sys_created_by`,\n       task0.`knowledge`,\n       task0.`order`,\n       task0.`cmdb_ci`,\n       task0.`delivery_plan`,\n       task0.`impact`,\n       task0.`contract`,\n       task0.`active`,\n       task0.`work_notes_list`,\n       task0.`priority`,\n       task0.`sys_domain_path`,\n       task0.`rejection_goto`,\n       task0.`business_duration`,\n       task0.`group_list`,\n       task0.`approval_set`,\n       task0.`wf_activity`,\n       task0.`universal_request`,\n       task0.`short_description`,\n       task0.`correlation_display`,\n       task0.`work_start`,\n       task0.`delivery_task`,\n       task0.`additional_assignee_list`,\n       task0.`a_int_1` AS `notify`,\n       task0.`sys_class_name`,\n       task0.`service_offering`,\n       task0.`closed_by`,\n       task0.`follow_up`,\n       task0.`a_ref_7` AS `parent_incident`,\n       task0.`a_ref_5` AS `reopened_by`,\n       task0.`reassignment_count`,\n       task0.`assigned_to`,\n       task0.`variables`,\n       task0.`sla_due`,\n       task0.`comments_and_work_notes`,\n       task0.`agile_story`,\n       task0.`escalation`,\n       task0.`upon_approval`,\n       task0.`correlation_id`,\n       task0.`made_sla`,\n       task0.`a_int_6` AS `child_incidents`,\n       task0.`a_int_8` AS `hold_reason`,\n       task0.`task_effective_number`,\n       task0.`a_ref_6` AS `resolved_by`,\n       task0.`sys_updated_by`,\n       task0.`opened_by`,\n       task0.`user_input`,\n       task0.`sys_created_on`,\n       task0.`sys_domain`,\n       task0.`route_reason`,\n       task0.`a_int_4` AS `calendar_stc`,\n       task0.`closed_at`,\n       task0.`business_service`,\n       task0.`a_str_11` AS `business_impact`,\n       task0.`a_ref_2` AS `rfc`,\n       task0.`time_worked`,\n       task0.`expected_start`,\n       task0.`opened_at`,\n       task0.`work_end`,\n       task0.`a_dtm_1` AS `reopened_time`,\n       task0.`a_dtm_2` AS `resolved_at`,\n       task0.`a_ref_4` AS `caller_id`,\n       task0.`a_str_3` AS `subcategory`,\n       task0.`work_notes`,\n       task0.`a_str_7` AS `close_code`,\n       task0.`assignment_group`,\n       task0.`a_int_5` AS `business_stc`,\n       task0.`a_str_10` AS `cause`,\n       task0.`description`,\n       task0.`a_str_2` AS `origin_id`,\n       task0.`calendar_duration`,\n       task0.`close_notes`,\n       task0.`sys_id`,\n       task0.`contact_type`,\n       task0.`a_int_2` AS `incident_state`,\n       task0.`urgency`,\n       task0.`a_ref_1` AS `problem_id`,\n       task0.`company`,\n       task0.`activity_due`,\n       task0.`a_int_3` AS `severity`,\n       task0.`comments`,\n       task0.`approval`,\n       task0.`due_date`,\n       task0.`sys_mod_count`,\n       task0.`a_int_7` AS `reopen_count`,\n       task0.`location`,\n       task0.`a_str_1` AS `category`\nFROM task task0\nWHERE task0.`sys_class_name` = 'incident'\n  AND task0.`sys_id` IN ('9060975f472d4210a53cdbe4116d4311',\n                         '9e7f9864532023004247ddeeff7b121f',\n                         'd71f7935c0a8016700802b64c67c11c6',\n                         'a9a16740c61122760004fe9095b7ddca',\n                         'd71b3b41c0a8016700a8ef040791e72a',\n                         'd7195138c0a8016700fd68449cfcd484',\n                         'd7158da0c0a8016700eef46c8d1f3661',\n                         'ef43c6d40a0a0b5700c77f9bf387afe3',\n                         'ef4225a40a0a0b5700d0b8a790747812',\n                         'a9e30c7dc61122760116894de7bcc7bd')\n```\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/SQL Checker/SQLChecker.js",
    "content": "// This example demonstrates how it is possible to see the generated SQL query without enabling the SQL debug feture in the navigator\ntry {\n\tgs.trace(true);\n\tvar incGr = new GlideRecord(\"incident\");\n\tincGr.setLimit(10);\n\tincGr.orderByDesc(\"sys_created_on\");\n\tincGr.query();\n  // TODO any other logic comes here...\n}\nfinally {\n\tgs.trace(false);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Safe Bulk Update with Logging/README.md",
    "content": "# Safe Bulk Record Update with Logging\n\n## Overview\nEfficiently update multiple records in batch with error handling, progress tracking, and logging to prevent timeouts and data loss.\n\n## What It Does\n- Updates records in configurable batch sizes\n- Logs progress for monitoring\n- Handles individual record errors without stopping batch\n- Prevents script timeout with batch processing\n- Tracks success/failure counts\n- Logs detailed error information\n\n## Use Cases\n- Bulk data migrations\n- Mass field updates after deployment\n- Scheduled bulk corrections\n- Data cleanup operations\n- Batch status updates across records\n\n## Files\n- `bulk_update_with_progress.js` - Background Script for safe bulk updates\n\n## How to Use\n\n### Option 1: Run as Background Script\n1. Go to **System Diagnostics > Script Background**\n2. Copy code from `bulk_update_with_progress.js`\n3. Modify the table name and query filter\n4. Execute and monitor logs\n\n### Option 2: Create as Scheduled Job\n1. Go to **System Scheduler > Scheduled Jobs**\n2. Create new job with the script code\n3. Schedule for off-peak hours\n4. Logs will be available in System Logs\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Safe Bulk Update with Logging/bulk_update_with_progress.js",
    "content": "var TABLE = 'incident'; // Change to your table\nvar FILTER = \"priority=1\"; // Add your filter conditions\nvar FIELD_TO_UPDATE = 'state'; // Field to update\nvar NEW_VALUE = '1'; // Value to set\n\nvar successCount = 0;\n//using gs.info for best logging\ngs.info('[Bulk Update Started] Table: ' + TABLE + ' | Filter: ' + FILTER, 'BulkUpdate');\n\ntry {\n    var gr = new GlideRecord(TABLE);\n    gr.addEncodedQuery(FILTER);\n    gr.query();\n    \n    // Collect all record IDs first (single query)\n    var recordIds = [];\n    while (gr.next()) {\n        recordIds.push(gr.getUniqueValue());\n    }\n    \n    //using updateMultiple will update all records at once\n    if (recordIds.length > 0) {\n        var updateGr = new GlideRecord(TABLE);\n        updateGr.addEncodedQuery(FILTER);\n        updateGr.setValue(FIELD_TO_UPDATE, NEW_VALUE);\n        updateGr.updateMultiple();//this is more efficient\n        successCount = recordIds.length;\n        gs.info('[Bulk Update Complete] Total Updated: ' + successCount, 'BulkUpdate');\n    } else {\n        gs.info('[Bulk Update] No records matched the filter criteria', 'BulkUpdate');\n    }\n} catch (e) {\n    gs.error('[Bulk Update Error] ' + e.toString(), 'BulkUpdate');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Set the status to Retired on Ec2 Instance/README.md",
    "content": "This background script is used to set the Install_status to Retired if the status is terminated for the Ec2 Instances(Ci's)\nWe are quering the table against the table cmdb_ci_ec2_instance\nThen we have encoded query to search if there is any record with the status as terminated and install_status is not retired\nWe are sorting based on name and set the limit to 10K records\nWe are searching if there are any records, If yes, we will set the install_Status to Retired and we are disabling the workflows to false.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Set the status to Retired on Ec2 Instance/script.js",
    "content": "//This Script is used to check if the state is terminated then we will set the Install status to retired of Ec2 Instances(CI's)\n\nvar gr = new GlideRecord(\"cmdb_ci_ec2_instance\");\ngr.addEncodedQuery('state=terminated^install_status!=7');\ngr.orderByDesc('name');\ngr.setLimit(10000);\ngr.query();\nwhile (gr.next()){\ngr.install_status = \"7\";\n  //install_status attribute will be set to \"7\" which of retired\ngr.autoSysField(false);\ngr.setWorkflow(false);\ngr.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Set update sets to Complete/README.md",
    "content": "Executing this script would help the administrators to set all the inprogress update sets to complete state.\n\nMost of the times this script comes in handy before setting up the instance to patching or upgradation as during that time updatesets need to be set complete and a backup has to be taken.\n\nBe cautious while using this script as this sets all the update sets whose state is inprogress and name does not start with \"default\" to complete.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Set update sets to Complete/set_update_sets_to_complete.js",
    "content": "//note : this script is going to mark all the update sets that are in progress to complete so make sure the query meets your requirements.\n\nvar gr = new GlideRecord(\"sys_update_set\"); //querying the update sets table to check update sets which are in progress  \ngr.addEncodedQuery(\"state=in progress^nameNOT LIKEdefault\");\n\ngr.setValue(\"state\",\"complete\"); //marking them to complete and updating multiple records using updateMultiple()\ngr.updateMultiple();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Silent update on GlideRecord/README.md",
    "content": "Add the gr.autoSysFields(false) to prevent system updates when modifying GlideRecord in background script execution. This is to preserve audit containing the last known user modifications from ticket form.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Silent update on GlideRecord/slientUpdateOnGlideRecord.js",
    "content": "//Update GlideRecord without modification to system fields\nvar grInc = new GlideRecord('incident');\nif (grInc.get('62826bf03710200044e0bfc8bcbe5df9')) {\n  grInc.active='false';\n  grInc.autoSysFields(false); //Do not update sys_updated_by, sys_updated_on, sys_mod_count, sys_created_by, and sys_created_on\n  grInc.setWorkflow(false); //Disables the running of business rules\n  grInc.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Stale Tasks Auto-Close/README.md",
    "content": "The script identifies tasks that haven’t been updated for a set period, sends reminder notifications to assigned users, and, if still inactive after additional time, automatically closes them. This helps keep task lists current and reduces manual follow-ups."
  },
  {
    "path": "Server-Side Components/Background Scripts/Stale Tasks Auto-Close/Stale Tasks Auto-Close.js",
    "content": "var staleDays = 7;\nvar closeDays = 14;\nvar reminderGR = new GlideRecord('task');\nreminderGR.addActiveQuery();\nreminderGR.addEncodedQuery('sys_updated_onRELATIVELE@dayofweek@ago@' + staleDays);\nreminderGR.query();\nwhile (reminderGR.next()) {\n    gs.eventQueue('task.reminder', reminderGR, reminderGR.assigned_to, staleDays + ' days without update.');\n    }\n\nvar closeGR = new GlideRecord('task');\ncloseGR.addActiveQuery();\ncloseGR.addEncodedQuery('sys_updated_onRELATIVELE@dayofweek@ago@' + closeDays);\ncloseGR.query();\nwhile (closeGR.next()) {\n    closeGR.state = 3; // Closed\n    closeGR.update();\n    }\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Table Growth Analysis/README.md",
    "content": "# Table Size Analysis Script\r\n\r\nThis script checks the number of records in selected ServiceNow tables and shows how many were created in the last 30 days.\r\n\r\n## Tables Checked\r\n- `task`\r\n- `cmdb_ci`\r\n- `sc_cat_item`\r\n\r\n## What It Does\r\n- Logs the start of the analysis.\r\n- Counts total records in each table.\r\n- Counts records created in the last 30 days.\r\n- Logs both counts to the system log.\r\n\r\n## How to Use\r\n1. Add or remove table names in the `tablesToCheck` list.\r\n2. Run the script in a background script or scheduled job.\r\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Table Growth Analysis/tableGrowthAnalysis.js",
    "content": "// === TABLE SIZE ANALYSIS SCRIPT ===\n// Log the start of the analysis\ngs.info('=== TABLE SIZE ANALYSIS ===');\n\n// Define the list of tables to analyze\nvar tablesToCheck = ['task', 'cmdb_ci', 'sc_cat_item'];\n\n// Loop through each table in the list\nfor (var i = 0; i < tablesToCheck.length; i++) {\n    var tableName = tablesToCheck[i]; // Get current table name\n\n    // Create a GlideAggregate object to count total records in the table\n    var grCount = new GlideAggregate(tableName);\n    grCount.addAggregate('COUNT'); // Add COUNT aggregate\n    grCount.query(); // Execute the query\n\n    // If the query returns a result\n    if (grCount.next()) {\n        var recordCount = grCount.getAggregate('COUNT'); // Get total record count\n        gs.info('Table: ' + tableName + ' | Record count: ' + recordCount); // Log the count\n\n        // Optional: Analyze growth by checking records created in the last 30 days\n        var grRecent = new GlideAggregate(tableName);\n        grRecent.addAggregate('COUNT'); // Add COUNT aggregate\n        grRecent.addQuery('sys_created_on', '>=', gs.daysAgo(30)); // Filter records created in last 30 days\n        grRecent.query(); // Execute the query\n\n        // If the query returns a result\n        if (grRecent.next()) {\n            var recentCount = grRecent.getAggregate('COUNT'); // Get count of recent records\n            gs.info(' - Records created last 30 days: ' + recentCount); // Log the recent count\n        }\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Tag Incident Outliers/README.md",
    "content": "# Tag incident resolution outliers by z score\n\n## What this solves\nAverage resolution time hides long-tail outliers. This script calculates mean and standard deviation of resolution minutes and tags incidents whose z score exceeds a threshold, helping teams investigate anomalies.\n\n## Where to use\nRun as a Background Script or convert into a Scheduled Job for periodic tagging.\n\n## How it works\n- Uses `GlideAggregate` to compute count, mean, and approximate variance\n- Calculates z score per resolved incident\n- Sets a flag field or work note on outliers above a configurable z threshold\n\n## Configure\n- `DAYS`: look-back window\n- `Z_THRESHOLD`: default 2.5\n- `FLAG_FIELD`: field to set, for example a custom boolean `u_outlier`\n\n## References\n- GlideAggregate API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideAggregate/concept/c_GlideAggregateAPI.html\n- GlideRecord API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideRecord/concept/c_GlideRecordAPI.html\n- GlideDateTime API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Tag Incident Outliers/tag_resolution_outliers.js",
    "content": "// Background Script: Tag incident resolution outliers by z score\n(function() {\n  var TABLE = 'incident';\n  var DAYS = 30;\n  var Z_THRESHOLD = 2.5;\n  var FLAG_FIELD = 'u_outlier'; // create this boolean field or change action to add work_notes - potential to change to tag as well if one exists\n\n  // Build look-back cutoff\n  var cutoff = new GlideDateTime();\n  cutoff.addDaysUTC(-DAYS);\n\n  // First pass: mean and std dev of resolution minutes\n  // Compute duration per record as closed_at - opened_at in minutes\n  var minutes = [];\n  var gr = new GlideRecord(TABLE);\n  gr.addQuery('closed_at', '>=', cutoff);\n  gr.addQuery('state', '>=', 6); // resolved or closed\n  gr.addNotNullQuery('opened_at');\n  gr.addNotNullQuery('closed_at');\n  gr.query();\n  while (gr.next()) {\n    var opened = String(gr.getValue('opened_at'));\n    var closed = String(gr.getValue('closed_at'));\n    var mins = gs.dateDiff(opened, closed, true) / 60;\n    minutes.push({ id: gr.getUniqueValue(), mins: mins });\n  }\n  if (!minutes.length) {\n    gs.info('No records in window. Exiting.');\n    return;\n  }\n\n  var sum = minutes.reduce(function(a, x) { return a + x.mins; }, 0);\n  var mean = sum / minutes.length;\n\n  var variance = minutes.reduce(function(a, x) {\n    var d = x.mins - mean; return a + d * d;\n  }, 0) / minutes.length;\n  var std = Math.sqrt(variance);\n\n  // Second pass: tag outliers\n  var tagged = 0;\n  minutes.forEach(function(row) {\n    var z = std > 0 ? (row.mins - mean) / std : 0;\n    if (z >= Z_THRESHOLD) {\n      var r = new GlideRecord(TABLE);\n      if (r.get(row.id)) {\n        if (r.isValidField(FLAG_FIELD)) {\n          r[FLAG_FIELD] = true;\n          r.update();\n        } else {\n          r.work_notes = 'Marked outlier by automation. z=' + z.toFixed(2) + ', mean=' + Math.round(mean) + 'm, std=' + Math.round(std) + 'm';\n          r.update();\n        }\n        tagged++;\n      }\n    }\n  });\n\n  gs.info('Outlier tagging complete. Window=' + DAYS + 'd, N=' + minutes.length + ', mean=' + Math.round(mean) + 'm, std=' + Math.round(std) + 'm, tagged=' + tagged);\n})();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/To check incidents having a VIP caller/VIP-caller-incidents.js",
    "content": "/**\n * Name: VIP Caller Incidents\n * Type: Background Script\n * Purpose: Prints all incidents where the caller is a VIP user\n * Author: Shashank Jain \n */\n\nvar inc = new GlideRecord('incident');\ninc.addQuery('caller_id.vip', true); // Only VIP callers\ninc.query();\ngs.print(\"Incidents with VIP Callers:\");\nwhile (inc.next()) {\n    gs.print(\"Number: \" + inc.number + \" | Short Description: \" + inc.short_description);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/To check incidents having a VIP caller/readme.md",
    "content": "# VIP Caller Incidents Background Script\n\n## Description\nThis background script fetches all incidents where the caller is marked as a VIP user\nand prints the incident number and short description in the logs.\n\n## Usage\n1. Go to **System Definition > Scripts - Background** in ServiceNow.\n2. Paste the script into the editor.\n3. Click **Run Script**.\n4. Check the output in the logs.\n\n## Prerequisites\n- The User table must have a **VIP checkbox** (`vip` field).\n- The Incident table must have a `caller_id` reference field.\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Typed Array Elements/README.md",
    "content": "# Type Elements Of An Array\n\nThis background script may be useful if you come \nacross a situation where it would be necessary to convert \nelements to a specific type such as string or number.\n\n# Use Case: \nexample: several string numbers to a number type like: ['1', '2', '3'] => [1, 2, 3] \nthus being able to avoid errors in situations involving calculations\n\n# How Map Function Works Example:\nconst array1 = [1, 4, 9, 16];\n\n// Pass a function to map\nconst map1 = array1.map((x) => x * 2);\n\nconsole.log(map1);\n// Expected output: Array [2, 8, 18, 32]\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Typed Array Elements/typed_array_elements.js",
    "content": "function arrayElementsToNumber(array) {\n    return array.map(Number);\n}\n\nfunction arrayElementsToString(array) {\n    return array.map(String);\n}\n\narrayElementsToNumber(['1', '2', '3'])\narrayElementsToString([1, 2, 3])\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Update All Store Apps/README.md",
    "content": "# Update All Store Apps\n\n## Introduction\n\nWould you like to upgrade all of your installed plugins and store apps at the same time?  This script include can be used as a background script to allow you to do just that. \n\nIt uses a feature called Batch Plans[^1] included with the out of the box CICD API[^2] to allow you to batch up multiple applications for installations at once. \n\nBatch Plans are only accessible by sending REST commands with a JSON payload to the CICD API. The examples below use the CICD API on the same instance where the script is running, but theoretically you could call a remote instance with the correct credentials and upgrade all plugins and/or store apps on it. \n\nThe script can utilize hard-coded credentials (*less secure, but easier*) or a Connection & Credential Alias[^3] (*very secure, but a few more steps to set up*)\n\n# Instructions\n\n# Step 1: Get ready to run the script\n\n***Go to System Definition > Scripts - Background***\n\nAt the bottom of the page, switch your scope to **Global**.\n\n![](2023-10-13-08-42-48.png)\n\n# Step 2: Copy and paste the script\n\nCopy the entire script and paste into the background script window.\n\n# Step 3: Run the script\n\nScroll to the bottom of the background script window. \n\nSelect one of the options below to run.\n\n1. [Find out how many apps are available to upgrade](#option-1-find-out-how-many-apps-are-available-to-upgrade)\n\n2. [Upgrade all apps by hard-coding a username and password](#option-2-upgrade-all-apps-by-hard-coding-a-username-and-password) (*Less secure*)\n\n3. [Upgrade all apps using a Connection & Credential Alias](#option-3-upgrade-all-apps-using-a-connection--credential-alias) (*More secure*)\n\n    1. [Setup Connection & Credential Alias](#section-a-setup-connection--credential-alias)\n    2. [Regular usage after initial setup of Connection & Credential Alias](#section-b-regular-usage-after-initial-setup-of-connection--credential-alias)\n\n## OPTION 1: Find out how many apps are available to upgrade\n\n1. ***Replace the last line by pasting this line at the bottom of the script.***\n\n    ```javascript\n    new upgradeUtil().countUpgrades();\n    ```\n\n    ![](2023-10-13-08-47-01.png)\n\n2. ***Click the \"Run script\" button.***\n\n    ### \n    ![](2023-10-13-08-55-37.png)\n\n    *All apps that can be upgraded are displayed along with their version number.*\n\n    *In the example below ...*\n\n    * *679 apps total are installed*\n\n    * *The app **@devsnc/sn-help-setup** can be upgraded to the version **23.0.2**.*\n\n3. ***Scroll to the bottom of the page to see the total number of apps to be upgraded.***\n\n    ![](2023-10-13-09-02-32.png)\n\n\n## OPTION 2: Upgrade all apps by hard-coding a username and password\n\n> [!WARNING]\n> <strong>Option 2</strong> will expose the credentials in the System Log on your instance. Do not use this method if you do not want to expose these in the System Log. Any user with the *admin* role will be able to view these credentials in the log.\n> <strong>YOU HAVE BEEN WARNED.</strong>\n\n\n<table>\n  <tr>\n    <td><b>Difficulty:</b></td>\n    <td>Easy</td>\n  </tr>\n  <tr>\n    <td><b>Security:</b></td>\n    <td>Low</td>\n  </tr>\n</table>\n\n1. ***Paste this line at the bottom of the script.***\n\n    ```javascript\n    upgradeUtil.upgradeAllAvailable('admin', 'password');\n    ```\n\n    Update **password** to the password of the user **admin**.\n\n    You may also use a different user instead of **admin** but the user must have the *admin* role. \n\n\n2. ***Click the \"Run script\" button.***\n\n\n## OPTION 3: Upgrade all apps using a Connection & Credential Alias \n\n<table>\n  <tr>\n    <td><b>Difficulty:</b></td>\n    <td>Medium</td>\n  </tr>\n  <tr>\n    <td><b>Security:</b></td>\n    <td>High</td>\n  </tr>\n</table>\n\n### SECTION A: Setup Connection & Credential Alias\n\n> [!IMPORTANT]\n> You only need to complete SECTION A the first time you use the script on your instance.\n> After that, the Credential is configured and does not need to be defined again so you can skip straight to [SECTION B](#option-3-section-b) for any other time you run the script after the first time.\n\n1. ***Change your Application Scope to \"Continuous Integration and Continuous Delivery (CICD) Spoke\"***\n\n    ![](2023-10-13-10-19-54.png)\n\n1. ***Go to All >> Connections & Credentials >> Connection & Credential Aliases***\n\n    ![](2023-10-13-10-22-40.png)\n\n1. ***Open the record where Name is \"CICD\".***\n\n1. ***Change the \"Type\" to \"Connection & Credential\".***\n\n    ![](2023-10-13-10-25-04.png)\n\n1. ***Click the context menu hamburger button and Save the record.***\n\n    ![](2023-10-13-10-27-05.png)\n\n1. ***Click the **New** button in the Credentials related list.***\n\n    ![](2023-10-13-10-26-37.png)\n\n1. ***Select Basic Auth Credentials.***\n\n    ![](2023-10-13-09-49-57.png)\n\n1. ***Complete the form with the following values, then click Submit.***\n\n    | Field | Value | Description |\n    |--|--|--|\n    | **Name**| admin@myInstanceName | This field is a *description* of the HTTP Connection. A good syntax to use is the name of the account '@' the name of the instance.  \n    | **Connection URL** | `http://example.service-now.com` | The URL of your ServiceNow instance. Please do not put the value literally as `http://example.service-now.com`.\n\n1. ***Click the context menu hamburger button and Save the record.***\n\n1. ***Click the reference field icon next to the field Credential.***\n\n    ![](2023-10-13-10-31-04.png)\n\n1. ***Click New.***\n\n    ![](2023-10-13-10-31-29.png)\n\n1. ***Select 'Basic Auth Credentials'.***\n\n1. ***Complete the form with the following values, then click Submit.***\n\n    | Field | Value | Description |\n    |--|--|--|\n    | **Name**| admin@myInstanceName | This field is a *description* of the credential. A good syntax to use is the name of the account '@' the name of the instance.  \n    | **User name** | `admin` | This is the User that the script will log in to the instance with. \n    | **Password** | `password` | This is the password for the User specified above. After you save the record, the dots in the field will be shorter than the actual password. This is normal to prevent users from knowing the actual length of the password.\n\n1. ***Click the context menu hamburger button and Save the record.***\n\n    ![](2023-10-13-10-33-17.png)\n\n### SECTION B: Regular usage after initial setup of Connection & Credential Alias\n\n1. ***Go to System Definition > Scripts - Background***\n\n1. ***Paste this line at the bottom of the script.***\n\n    ```javascript\n    upgradeUtil.upgradeAllAvailable('alias','752a91887740001038e286a2681061fb');\n    ```\n\n   *In this scenario, the script will use a Connection Alias with the sys_id `752a91887740001038e286a2681061fb`.*\n\n1. ***Click the \"Run script\" button.***\n\n\n## Review the Batch Plan\n\nOnce you have ran the script with either Option 2 or Option 3, you will get output about the Batch Plan that was generated. \n\nBatch Plans do not have a Navigation Module on the All menu so these URLs are provided to help you get to the Batch Plan quicker. \n\n![](2023-10-13-12-17-09.png)\n\n**This Batch Plan (Example)**: \n\n`https://myInstanceName.service-now.com/nav_to.do?uri=sys_batch_install_plan.do?sys_id=e4f90b6e93f93910d008b7518bba1050`\n\nCopy and paste the URL from the output of the script into a browser tab to view the Batch Plan you just submitted. You may need to be patient and refresh the page occasionally to see the changes made as the Plan is built and executed. \n\n**Batch Plan List (Example):**\n\n`https://myInstanceName.service-now.com/now/nav/ui/classic/params/target/sys_batch_install_plan_list`\n\nCopy and paste this URL from the output of the script into a browser tab to view the list of all Batch Plans.\n\n\n[^1]: **Product Docs: Install multiple applications in a batch:** [https://docs.servicenow.com/csh?topicname=cicd-spoke-batch-install.html&version=latest](https://docs.servicenow.com/csh?topicname=cicd-spoke-batch-install.html&version=latest)\n\n[^2]:**Product Docs: Continuous Integration/Continuous Delivery (CICD) API:** [https://docs.servicenow.com/csh?topicname=cicd-api.html&version=latest](https://docs.servicenow.com/csh?topicname=cicd-api.html&version=latest)\n\n[^3]:**Product Docs: Create a Connection & Credential alias:** [https://docs.servicenow.com/csh?topicname=connection-alias.html&version=latest](https://docs.servicenow.com/csh?topicname=connection-alias.html&version=latest)\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Update All Store Apps/update_all_apps.js",
    "content": "var upgradeUtil = Class.create();\nupgradeUtil.prototype = {\n\tinitialize: function () {\n        this.debug = true;  // Set to true to get more verbose output\n        //this.debug = false;  // Set to true to get more verbose output\n    },\n\n\t///////////////////////////////////////////////////////////////////////////////\n\tcountUpgrades: function () {\n\n        if (this.debug === true) gs.info(\"BEGIN: countUpgrades()\");\n\n\t\tvar log = [];\n\n\t\t///////////////////////////////////////////////////////////////////////////\n\t\t// In sys_store_app records, compare version to latest_version to see if we can upgrade\t\t\n\t\t///////////////////////////////////////////////////////////////////////////\n\t\tvar appsGr = new GlideRecord('sys_store_app');\n\t\tappsGr.orderBy('name');\n\t\tappsGr.addQuery('active', true);\n\t\tappsGr.query();\n\n\t\tvar applicationsToUpgradeArr = []; // Create an empty JSON array\n\t\tvar upgrades = 0;\n\t\tvar total = appsGr.getRowCount();\n\t\tlog.push(\"\\n --> \" + total + \" apps found on [sys_store_app]\");\n\n\t\twhile (appsGr.next()) {\n\t\t\tvar notes = appsGr.getValue('name');\n\t\t\tvar upgrade = 0;\n\t\t\tvar upgrade_version = '';\n\t\t\tvar versionStr = appsGr.getValue('version');\n\t\t\tvar assignedVersionStr = appsGr.getValue('assigned_version');\n\t\t\tvar latestVersionStr = appsGr.getValue('latest_version');\n\t\t\t// Check if Latest Version field is populated, break the current loop if so as update cannot be checked\n\t\t\tif (!latestVersionStr) break;\n\t\t\t// Convert the strings to arrays of integers\n\t\t\tvar versionArr = versionStr.split('.').map(Number);\n\t\t\tvar assignedVersionArr = assignedVersionStr.split('.').map(Number);\n\t\t\tvar latestVersionArr = latestVersionStr.split('.').map(Number);\n\n\t\t\t// Compare the arrays element-wise\n\t\t\tfor (var i = 0; i < versionArr.length; i++) {\n\t\t\t\tif (versionArr[i] > assignedVersionArr[i] && versionArr[i] > latestVersionArr[i]) {\n                    if (this.debug === true) log.push(\"\\n --> \" + notes + \" version is highest value \" + versionStr);\n\t\t\t\t\tupgrade_version = versionStr;\n\t\t\t\t\tupgrade = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (assignedVersionArr[i] > versionArr[i] && assignedVersionArr[i] > latestVersionArr[i]) {\n                    if (this.debug === true) log.push(\"\\n --> \" + notes + \" assigned_version is highest value \" + assignedVersionStr);\n\t\t\t\t\tupgrade_version = assignedVersionStr;\n\t\t\t\t\tupgrade = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t} else if (latestVersionArr[i] > versionArr[i] && latestVersionArr[i] > assignedVersionArr[i]) {\n                    if (this.debug === true) log.push(\"\\n --> \" + notes + \" latest_version is highest value \" + latestVersionStr);\n\t\t\t\t\tupgrade_version = latestVersionStr;\n\t\t\t\t\tupgrade = 1;\n\t\t\t\t\tbreak;\n\t\t\t\t} else {\n\t\t\t\t\tcontinue; // version values are all the same. no upgrade\t\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (upgrade == 1) { // If upgrade found, add packageDetails to JSON to send to API later\n\t\t\t\tvar packageDetails = {\n\t\t\t\t\tnotes: notes,\n\t\t\t\t\tid: appsGr.getValue('sys_id'),\n\t\t\t\t\trequested_version: upgrade_version,\n\t\t\t\t\tload_demo_data: true,\n\t\t\t\t\ttype: \"application\"\n\n\t\t\t\t};\n\t\t\t\tapplicationsToUpgradeArr.push(packageDetails);\n\t\t\t\tupgrades++;\n\t\t\t}\n\t\t}\n\n\t\tlog.push(\"\\n --> \" + upgrades + \" can be upgraded\");\n\t\tgs.info(log);\n\n        if (this.debug === true) gs.info(\"END: countUpgrades()\");\n\n\t\t//var value1 = 10;\n\t\t//var value2 = 20;\n\t\treturn {\n\t\t\tprop1: applicationsToUpgradeArr,\n\t\t\tprop2: upgrades\n\t\t}\n\n\t},\n\n////////////////////////////////////////////////////////////////////////////////\n\tupgradeAllAvailable: function (loginType, loginKey) {\n\n        if (this.debug === true) gs.info(\"BEGIN: upgradeAllAvailable() \");\n\n\t\t// Check if any inputs are missing\n\t\tif (!loginType || !loginKey) {\n\t\t\tgs.info(\"No inputs were provided. Please provide the required inputs.\");\n\t\t\treturn;\n\t\t}\n\n\t\tvar result = this.countUpgrades();\n\n        if (this.debug === true) gs.info(\"upgradeAllAvailable(): CONTINUE\");\n\n\t\tvar log = [];\n\n\n\t\tvar applicationsToUpgradeArr = result.prop1;\n\t\tvar upgrades = result.prop2;\n\n\t\tif (upgrades == \"0\") {\n\t\t\tlog.push(\"\\n --> No apps found to upgrade\");\n\t\t\t//gs.info(log);\n\t\t\t//return;\n\t\t}\n\n\n\t\t////////////////////////////////////////////////////////////////////////////////\n\t\t// Build the payload \n\t\t////////////////////////////////////////////////////////////////////////////////\n\t\t//var dateString = new GlideDateTime();\n\n\t\tnotes = \"Submitted \" + upgrades + \" apps to upgrade\"; // This will go in the Batch Install Plan Notes\n\n        if (this.debug === true) gs.info(notes);\n\n\t\t// Create a JSON object containing the packages array\n\t\tvar payload = {\n\t\t\t\"name\": \"upgradeUtil Script on \" + new GlideDateTime(),\n\t\t\t\"notes\": notes,\n\t\t\t\"packages\": applicationsToUpgradeArr\n\t\t};\n\n\t\t////////////////////////////////////////////////////////////////////////////////\n\t\t// check login type: username or Connection & Credential Alias\n\t\t////////////////////////////////////////////////////////////////////////////////\n\t\tvar basicUserName = '';\n\t\tvar basicPassword = '';\n\n\t\tif (loginType === \"\") {\n\t\t\tlog.push(\"\\n ERROR: loginType is blank\");\n\t\t\tgs.info(log);\n\t\t\texplainHowToMakeCreds();\n\t\t\treturn;\n\t\t}\n\n\t\tif (loginType == \"alias\") {\n\t\t\tif (this.debug === true) log.push(\"\\n\\n --> AUTHTYPE: Connection & Credential Alias\")\n\t\t\tvar aliasId = loginKey;\n\n\t\t\t// Set basic authentication using a username and password\n\t\t\t// https://developer.servicenow.com/dev.do#!/reference/api/tokyo/server/sn_cc-namespace/connectioninfo-api\n\t\t\tvar provider = new sn_cc.ConnectionInfoProvider();\n\t\t\tvar connectionInfo = provider.getConnectionInfo(aliasId);\n\t\t\tif (connectionInfo != null) {\n\t\t\t\tbasicUserName = connectionInfo.getCredentialAttribute(\"user_name\");\n\t\t\t\tbasicPassword = connectionInfo.getCredentialAttribute(\"password\");\n\n\t\t\t\tif (basicUserName == null) {\n\t\t\t\t\tlog.push(\"\\n ERROR: Connection Alias username issue\");\n\t\t\t\t\tgs.info(log);\n\t\t\t\t\texplainHowToMakeCreds();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (basicPassword == null) {\n\t\t\t\t\tlog.push(\"\\n ERROR: Connection Alias password issue\");\n\t\t\t\t\tgs.info(log);\n\t\t\t\t\texplainHowToMakeCreds();\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tlog.push(\"\\n --> Connection Alias unknown issue - ABORTING!!!\");\n\t\t\t\tgs.info(log);\n\t\t\t\texplainHowToMakeCreds();\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else {\n\t\t\tlog.push(\"\\n\\n --> Authentication will be with an account\")\n\t\t\tvar basicUserName = loginType.toString();\n\t\t\tvar basicPassword = loginKey.toString();\n\t\t\tlog.push(\"\\n --> User will be => \" + basicUserName);\n\t\t\t//log.push(\"\\n --> Key will be => \" + basicPassword);\n\t\t}\n\n\t\tif (basicUserName == '' || basicPassword == '') {\n\t\t\tlog.push(\"\\n --> credentials not found.\");\n\t\t\tgs.info(log);\n\t\t\treturn;\n\t\t}\n\n\t\t////////////////////////////////////////////////////////////////////////////////\n\t\t// Call the API\n\t\t////////////////////////////////////////////////////////////////////////////////\n\n\t\t// Create a RESTMessageV2 object and set the endpoint URL and HTTP method\n\t\tvar instanceName = gs.getProperty('instance_name');\n\t\tif (instanceName.indexOf(\"nowlearning\") !== -1) { // need to add .lab to URL\n\t\t\tinstanceName = instanceName + \".lab\";\n\t\t}\n\n\t\tvar request = new sn_ws.RESTMessageV2();\n\t\trequest.setEndpoint('https://' + instanceName + '.service-now.com/api/sn_cicd/app/batch/install');\n\t\trequest.setHttpMethod('POST');\n\n\t\t// Set basic authentication using a username and password\n\t\trequest.setBasicAuth(basicUserName, basicPassword);\n\n\t\t// Set the request headers to accept JSON\n\t\trequest.setRequestHeader(\"Accept\", \"application/json\");\n\n\t\t// Set the request body to the JSON payload\n\t\trequest.setRequestBody(JSON.stringify(payload));\n\n\t\t// Execute the REST API call and log the response body\n\t\tvar response = request.execute();\n\t\tvar responseBody = response.getBody();\n\t\tvar statusCode = response.getStatusCode();\n\n        switch (statusCode) {\n            case 200:\n                if (this.debug === true) gs.info(\"HTTP 200: Auth Successful\");\n                break;\n            case 401:\n                gs.info('HTTP 401: Unauthorized');\n                gs.info('The credentials you provided did not work.');\n                return;\n                //break;\n            default:\n                gs.info(\"\\n\\nERROR: Request failed with status code \" + statusCode);\n                //gs.info(log);\n                return;\n                //break;\n        }\n\n\t\tvar responseBodyJSONObj = JSON.parse(responseBody);\n\n\t\tvar myObject = responseBodyJSONObj.result.links.results;\n\t\tfor (var property in myObject) {\n\t\t\tif (myObject.hasOwnProperty(property)) {\n\t\t\t\tvar value = myObject[property];\n\t\t\t\tif (property == \"id\") {\n\t\t\t\t\tvar batchUrl = 'https://' + instanceName + '.service-now.com/nav_to.do?uri=sys_batch_install_plan.do?sys_id=' + value;\n\t\t\t\t\tvar allBatchPlans = 'https://' + instanceName + '.service-now.com/now/nav/ui/classic/params/target/sys_batch_install_plan_list';\n\n\t\t\t\t\tlog.push(\"\\n\\n --> This Batch Plan: \\n\" + batchUrl + \"\\n\");\n\t\t\t\t\tlog.push(\"\\n\\n --> Batch Plan List: \\n\" + allBatchPlans + \"\\n\");\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tgs.info(log);\n\n\n        if (this.debug === true) gs.info(\"BEGIN: upgradeAllAvailable() \");\n\n\t},\n\n\texplainHowToMakeCreds: function(log) {\n\t\tvar instanceName = gs.getProperty('instance_name');\n\t\tvar connection_url = 'https://' + instanceName + '.service-now.com/';\n\t\tvar alias_url = 'https://' + instanceName + '.service-now.com/nav_to.do?uri=sys_alias.do?sys_id=752a91887740001038e286a2681061fb';\n\t\tvar log = [];\n\t\tlog.push(\"\\n\\n --> NEED TO RECONFIGURE CICD CONNECTION ALIAS FOR SCRIPT\");\n\t\tlog.push(\"\\n\\n --> Go to this URL:\\n\" + alias_url + \"\\n\");\n\t\tlog.push(\"\\n\\n --> Change 'Type' to 'Connection and Credential and Save Record\");\n\t\tlog.push(\"\\n     (Stay on page)\");\n\t\tlog.push(\"\\n\\n --> Create NEW Connection\");\n\t\tlog.push(\"\\n     NAME: (enter name)\");\n\t\tlog.push(\"\\n     Credential: (Create new record)\");\n\t\tlog.push(\"\\n     Connection URL: \" + connection_url);\n\t\tgs.info(log);\n\t},\n\n\ttype: 'upgradeUtil'\n};\n\nvar upgradeUtil = new upgradeUtil();\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Update reference field from CI relationship/README.md",
    "content": "In this piece of code, we are querying a table for some records and then updating a particular reference field's value of those records to the value of the specific parent class to which it has a cmdb_rel_ci relationship.\nWe are also printing the sys_ids pf the records which would be updated and the ones that would be skipped.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Update reference field from CI relationship/Reference field value update from CI relationship connection.js",
    "content": "var updatedSysIds = [];\nvar notUpdatedSysIds = [];\n\nvar gr = new GlideRecord('< table_name >');\ngr.addQuery('< query condition >');\ngr.query();\n\nwhile (gr.next()) {\n\n    var relCi = new GlideRecord('cmdb_rel_ci');\n    relCi.addQuery('child', gr.sys_id);\n    relCi.addQuery('parent.sys_class_name', '< backend name of the table to which the reference field is referred to >');\n    relCi.query();\n\n    if (relCi.next()) {\n        // Update the reference field with the referenced table's sys_id\n        gr.< reference field backend name > = relCi.parent.sys_id;\n        gr.setWorkflow(false); \n        gr.update();\n        updatedSysIds.push(gr.sys_id.toString()); // Add to updated list\n    } \n\telse {\n        notUpdatedSysIds.push(gr.sys_id.toString()); // Add to not updated list\n    }\n}\n\n// Print the sys_ids of the records updated and not updated\ngs.print(\"Updated records sys_ids: \" + updatedSysIds.join(', '));\ngs.print(\"Not updated records sys_ids: \" + notUpdatedSysIds.join(', '));\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Updating a record in the sys_user table/README.md",
    "content": "# update a record in sys_user table\n\nIf we use the command below to update a record, it can lead to a problem.\n\ngrUser.get('grUser.get('62826bf03710200044e0bfc8bcbe5df9')');\n\nIf the record is not found in the table, the script will create a new one. \n\nTo make sure we are updating and not inserting, it is better to wrap up the get method with an If statement."
  },
  {
    "path": "Server-Side Components/Background Scripts/Updating a record in the sys_user table/script.js",
    "content": "var grUser = new GlideRecord('sys_user');\n\nif (grUser.get('62826bf03710200044e0bfc8bcbe5df9')) {\n    grUser.user_name = 'test.user';\n    grUser.first_name = 'test';\n    grUser.last_name = 'user';\n    grUser.email = 'test.user@servicenow';\n    grUser.update();\n} else {\n    gs.info('Record not found.');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/User Access Tester/README.md",
    "content": "Simple background script to test user access to records and fields by impersonating different users.\n\n## What it does\n\n- Impersonates a specified user\n- Tests access to a specific record\n- Checks visibility of configured fields\n- Returns to original user context\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/User Access Tester/userAccessTester.js",
    "content": "var CONFIG = {\n    tableName: 'incident',\n    recordSysId: 'your_record_sys_id_here',\n    userToImpersonate: 'user_sys_id_here',\n    fieldsToTest: ['number', 'short_description', 'priority', 'assignment_group']\n};\n\nvar originalUserID = gs.getUserID();\nvar impersonator = new GlideImpersonate();\n\ngs.print(\"Original user: \" + gs.getUser().getDisplayName());\n\nimpersonator.impersonate(CONFIG.userToImpersonate);\ngs.print(\"Now impersonating: \" + gs.getUser().getDisplayName());\n\nvar gr = new GlideRecordSecure(CONFIG.tableName);\nif (gr.get(CONFIG.recordSysId)) {\n    gs.print(\"Record accessible: \" + gr.getDisplayValue());\n    \n    for (var i = 0; i < CONFIG.fieldsToTest.length; i++) {\n        var field = CONFIG.fieldsToTest[i];\n        var value = gr.getDisplayValue(field);\n        gs.print(\"Field '\" + field + \"': \" + (value || 'Empty/No access'));\n    }\n} else {\n    gs.print(\"Record not found or not accessible\");\n}\n\nimpersonator.impersonate(originalUserID);\ngs.print(\"Back to original user: \" + gs.getUser().getDisplayName());\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/User Email mismatch with Cmn Notif device/README.md",
    "content": "# Email Mismatch Checker\n\n## Description\n\nThis ServiceNow background script checks for mismatches between notification devices and user records based on email addresses.\n\nIt looks for active `cmn_notif_device` records that:\n- Have a non-null `email_address`\n- Are linked to a user\n- Are of type \"Email\"\n- Are named \"Primary Email\"\n\nThen it verifies if a matching user exists in the `sys_user` table with the same email. If no match is found, the mismatch is logged.\n\n## How to Use\n\n1. Go to **Scripts > Background** in your ServiceNow instance.\n2. Paste the script.\n3. Run the script.\n4. Check the system logs for mismatch details.\n\n## Output\n\nLogs the number of mismatches and details like:\nMismatch: Device=<device_name>, Device Email=<email_address>\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/User Email mismatch with Cmn Notif device/userEmailMismatchwithcmnNotifDevice.js",
    "content": "// Background Script - Run in Scripts > Background\n\nfunction findEmailMismatches() {\n    var mismatches = [];\n\n    var deviceGR = new GlideRecord('cmn_notif_device');\n    deviceGR.addNotNullQuery('email_address'); // Only check devices with email populated\n    deviceGR.addNotNullQuery(\"user\");          // Ensure device is linked to a user\n    deviceGR.addQuery(\"type\", \"Email\");        // Filter for email-type devices\n    deviceGR.addQuery(\"name\", \"Primary Email\");// Filter for primary email devices\n    deviceGR.addActiveQuery();                 // Only active devices\n    deviceGR.query();\n\n    while (deviceGR.next()) {\n        var userGR = new GlideRecord('sys_user');\n        userGR.addQuery('email', deviceGR.email_address);\n        userGR.query();\n\n        if (!userGR.next()) {\n            // Found a mismatch\n            mismatches.push({\n                device_sys_id: deviceGR.getUniqueValue(),\n                device_name: deviceGR.getDisplayValue(),\n                email: deviceGR.email_address.toString(),\n                user_sys_id: 'No matching user found'\n            });\n        }\n    }\n\n    return mismatches;\n}\n\n// Execute and log results\nvar results = findEmailMismatches();\ngs.info('Found ' + results.length + ' email mismatches:');\n\nfor (var i = 0; i < results.length; i++) {\n    gs.info('Mismatch: Device=' + results[i].device_name + ', Device Email=' + results[i].email);\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/User Has Role Exactly/README.md",
    "content": "# hasRoleExactly\n\nThis background script might be useful if you come\nin a situation where it would be necessary to check \nwhether the logged in user has a specific role.\n\n# Use Case: \nexample: check if the user has a specific role by calling the role: hasRoleExactly('admin');\nwill return true if the user has the role and false otherwise\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/User Has Role Exactly/user_has_role_exactly.js",
    "content": "function hasRoleExactly(role) {\n    var arrayUtil = new ArrayUtil();\n    var roles = gs.getSession().getRoles() + ''; \n    var roleArray = roles.split(\",\"); \n    var isAuthorized = arrayUtil.contains(roleArray, role); \n    return isAuthorized; \n}"
  },
  {
    "path": "Server-Side Components/Background Scripts/Version Checker/README.md",
    "content": "# Version Checker for Out of the box configuration analysis\n# VersionUpdateChecker.js\n\n# Why us it?\nIf an admin needs a list of all 'customized' files from certain applications\n\n# How to use it?\nCopy/Paste this as a background script (\"Scripts - Background\" module)  \nReplace \"< < names > >\" with application names to check new versions that have been modified from OOTB  \nRun script and then copy/paste the results into Excel, then do a split on columns, and select delimited and choose the pipe symbol  \nAlternatively, save output as a .txt file and import into excel using the method linked below  \nhttps://support.microsoft.com/en-us/office/import-or-export-text-txt-or-csv-files-5250ac4c-663c-47ce-937b-339e391393ba  \n  \nInitial author credit to gary.opela@servicenow.com\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Version Checker/VersionUpdateChecker.js",
    "content": "var grVers = new GlideRecord('sys_update_version');\ngrVers.addEncodedQuery('application.nameIN<<names>>'); //change <<names>>\ngrVers.query();\n\nvar msg = \"\\n\\nUpdate Name|Type|Object Name|Application|Update Set|Action\\n\";\n\nwhile (grVers.next()){\n    var isOOTB = getOOTB(grVers.getValue('name'));\n    if (isOOTB){\n        msg += (grVers.getValue('name') + '|' + grVers.getValue('type') + '|' + grVers.getValue('record_name') + '|' + grVers.getDisplayValue('application') + '|' + grVers.getDisplayValue('source').replace('Update Set: ', '') + '|' + grVers.getValue('action') + '\\n');\n    }\n}\n\ngs.info(msg);\n\nfunction getOOTB(updateName){\n    var grU = new GlideRecord('sys_update_version');\n    grU.addEncodedQuery(\"name=\" + updateName + \"^source_table!=sys_update_set\");\n    grU.query();\n    if (grU.hasNext()){\n        return true;\n    }\n    return false;\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Virtual Agent Conversation Analytics/README.md",
    "content": "# Virtual Agent Conversation Analytics\n\nA background script that analyzes Virtual Agent conversation logs to identify the most common topics over a configurable time period.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content\n3. (Optional) Modify `daysBack` variable to set the analysis timeframe (default: 7 days)\n4. Click \"Run script\"\n\n## What It Does\n\nThe script:\n1. Queries Virtual Agent conversation logs from the past 7 days (configurable)\n2. Counts conversations by topic\n3. Displays the top 10 most common topics with conversation counts\n4. Helps identify which Virtual Agent topics are most frequently used\n\n\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/Virtual Agent Conversation Analytics/script.js",
    "content": "// Virtual Agent Conversation Analytics\n// Analyzes VA conversation logs to identify the most common topics\n\nvar daysBack = 7; // Analyze conversations from the past 7 days\n\n// Calculate date range\nvar startDate = new GlideDateTime();\nstartDate.addDaysLocalTime(-daysBack);\n\ngs.info('=== Virtual Agent Conversation Analytics ===');\ngs.info('Analyzing conversations from: ' + startDate.getDisplayValue());\n\n// Get conversation logs\nvar convGr = new GlideRecord('sys_cs_conversation');\nconvGr.addQuery('sys_created_on', '>=', startDate);\nconvGr.query();\n\nvar totalConversations = convGr.getRowCount();\ngs.info('Total Conversations: ' + totalConversations);\n\n// Auto-detect topic field (handles schema variations)\nvar topicField = convGr.isValidField('topic') ? 'topic' : \n                 (convGr.isValidField('selected_topic') ? 'selected_topic' : null);\n\nif (!topicField) {\n    gs.warn('No topic field found on sys_cs_conversation table');\n} else {\n    // Track topic counts\n    var topicCounts = {};\n\n    while (convGr.next()) {\n        var topicId = convGr.getValue(topicField);\n        \n        if (topicId) {\n            var topicGr = new GlideRecord('sys_cs_topic');\n            if (topicGr.get(topicId)) {\n                var topicName = topicGr.getValue('name');\n                \n                if (!topicCounts[topicName]) {\n                    topicCounts[topicName] = 0;\n                }\n                topicCounts[topicName]++;\n            }\n        }\n    }\n\n    // Sort and display most common topics\n    gs.info('\\n=== Most Common Topics ===');\n    var sortedTopics = [];\n    for (var topic in topicCounts) {\n        sortedTopics.push({name: topic, count: topicCounts[topic]});\n    }\n    sortedTopics.sort(function(a, b) { return b.count - a.count; });\n\n    if (sortedTopics.length === 0) {\n        gs.info('No topics found in the selected time range');\n    } else {\n        for (var i = 0; i < Math.min(10, sortedTopics.length); i++) {\n            gs.info((i + 1) + '. ' + sortedTopics[i].name + ': ' + sortedTopics[i].count + ' conversations');\n        }\n    }\n}\n\ngs.info('\\n=== Analysis Complete ===');"
  },
  {
    "path": "Server-Side Components/Background Scripts/Virtual Agent Topic Coverage Report/README.md",
    "content": "# Virtual Agent Topic Coverage Report\n\nA background script that analyzes Virtual Agent topic configuration health by identifying inactive, unpublished, or unused topics.\n\n## Usage\n\n1. Navigate to **System Definition → Scripts - Background**\n2. Copy and paste the script content\n3. (Optional) Modify `daysBack` variable to set the usage analysis timeframe (default: 30 days)\n4. Click \"Run script\"\n\n## What It Does\n\nThe script:\n1. Queries all Virtual Agent topics in the system\n2. Checks each topic's active and published status\n3. Counts conversations per topic over the past 30 days (configurable)\n4. Displays inactive topics, unpublished topics, and zero-usage topics\n5. Helps identify topics that need attention before go-live or during health checks\n\n## Report Categories\n\n**Inactive Topics**: Topics where the \"Active\" checkbox is unchecked. These topics are disabled and won't respond to user inputs even if published.\n\n**Unpublished Topics**: Topics where the \"Published\" checkbox is unchecked. These are draft topics not yet available to end users.\n\n**Topics with Zero Usage**: Topics that are both active and published but have had no conversations in the specified timeframe. May indicate topics that need better training phrases or are not discoverable by users."
  },
  {
    "path": "Server-Side Components/Background Scripts/Virtual Agent Topic Coverage Report/script.js",
    "content": "// Virtual Agent Topic Coverage Report\n// Analyzes VA topic configuration health and usage patterns\n\nvar daysBack = 30; // Analyze topic usage from the past 30 days\n\n// Calculate date range for usage analysis\nvar startDate = new GlideDateTime();\nstartDate.addDaysLocalTime(-daysBack);\n\ngs.info('=== Virtual Agent Topic Coverage Report ===');\ngs.info('Analyzing topics and usage from: ' + startDate.getDisplayValue());\n\n// Get all VA topics\nvar topicGr = new GlideRecord('sys_cs_topic');\nif (!topicGr.isValid()) {\n    gs.warn('Table sys_cs_topic not found. Virtual Agent may not be installed.');\n} else {\n    topicGr.query();\n    \n    var totalTopics = topicGr.getRowCount();\n    gs.info('Total Topics: ' + totalTopics);\n    \n    var inactiveTopics = [];\n    var unpublishedTopics = [];\n    var zeroUsageTopics = [];\n    var topicUsage = {};\n    \n    // Auto-detect conversation table field name\n    var convGr = new GlideRecord('sys_cs_conversation');\n    var topicField = null;\n    if (convGr.isValid()) {\n        topicField = convGr.isValidField('topic') ? 'topic' : \n                     (convGr.isValidField('selected_topic') ? 'selected_topic' : null);\n    }\n    \n    while (topicGr.next()) {\n        var topicId = topicGr.getUniqueValue();\n        var topicName = topicGr.getValue('name');\n        var isActive = topicGr.getValue('active') == 'true' || topicGr.getValue('active') == '1';\n        var isPublished = topicGr.getValue('published') == 'true' || topicGr.getValue('published') == '1';\n        \n        // Track inactive topics\n        if (!isActive) {\n            inactiveTopics.push(topicName);\n        }\n        \n        // Track unpublished topics\n        if (!isPublished) {\n            unpublishedTopics.push(topicName);\n        }\n        \n        // Count conversations for this topic (if conversation table exists)\n        var conversationCount = 0;\n        if (topicField) {\n            var convCountGr = new GlideAggregate('sys_cs_conversation');\n            convCountGr.addQuery(topicField, topicId);\n            convCountGr.addQuery('sys_created_on', '>=', startDate);\n            convCountGr.addAggregate('COUNT');\n            convCountGr.query();\n            if (convCountGr.next()) {\n                conversationCount = parseInt(convCountGr.getAggregate('COUNT')) || 0;\n            }\n        }\n        \n        topicUsage[topicName] = conversationCount;\n        \n        // Track topics with zero usage\n        if (isActive && isPublished && conversationCount === 0) {\n            zeroUsageTopics.push(topicName);\n        }\n    }\n    \n    // Display results\n    gs.info('\\n=== Inactive Topics ===');\n    if (inactiveTopics.length > 0) {\n        for (var i = 0; i < inactiveTopics.length; i++) {\n            gs.info((i + 1) + '. ' + inactiveTopics[i]);\n        }\n    } else {\n        gs.info('No inactive topics found');\n    }\n    \n    gs.info('\\n=== Unpublished Topics ===');\n    if (unpublishedTopics.length > 0) {\n        for (var j = 0; j < unpublishedTopics.length; j++) {\n            gs.info((j + 1) + '. ' + unpublishedTopics[j]);\n        }\n    } else {\n        gs.info('No unpublished topics found');\n    }\n    \n    gs.info('\\n=== Topics with Zero Usage (Active & Published) ===');\n    if (zeroUsageTopics.length > 0) {\n        for (var k = 0; k < zeroUsageTopics.length; k++) {\n            gs.info((k + 1) + '. ' + zeroUsageTopics[k]);\n        }\n    } else {\n        if (topicField) {\n            gs.info('All active & published topics have been used');\n        } else {\n            gs.info('Cannot analyze usage - conversation table not available');\n        }\n    }\n}\n\ngs.info('\\n=== Analysis Complete ===');\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/add member to groups/Add the members to List of the Groups using GlideRecord.js",
    "content": "var rec = new GlideRecord('sys_user_group');\nrec.addEncodedQuery('');  //get the list of groups needed.\nrec.query();\nwhile (rec.next())\n{\n    var rec1 = new GlideRecord('sys_user_grmember');\n    rec1.addQuery('group' , rec.sys_id);\n    rec1.addQuery('user' , '7279f455939e71944c77b6b5fbba1033');   // put the sys_id of \"user\" here\n    rec1.query();\nif(!rec1.next())  //checking if group member is already existed. if not, we add them.\n{\nrec1.initialize();\nrec1.group = rec.sys_id;\nrec1.user = '7279f455939e71944c77b6b5fbba1033'; // put the sys_id of \"user 1\" here, to insert group member\nrec1.insert();\ngs.log(\"User group record inserted\");\n}\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/add member to groups/README.md",
    "content": "**Script to Add a User to list of User Groups Where They Are Not Already a Member**\n\nPurpose: we often recive requests add a single group member to multiple groups. that's a manual task. this script makes it very simple.\n\n- **The first GlideRecord('sys_user_group')**: Creates a GlideRecord instance to query the `sys_user_group` table.\n   \n- **addEncodedQuery('')**: Add the required list of groups here, by copying filter from list of groups.\n\n- We use while loop to loop through list of groups.\n\n- **The second GlideRecord('sys_user_grmember')**: For each group, creates a new GlideRecord instance to query the `sys_user_grmember` table (user-group memberships).\n\n- **addQuery('group', rec.sys_id)**: Filters the `sys_user_grmember` records by the current group’s `sys_id`.\n\n- **addQuery('user', '7279f455939e71944c77b6b5fbba1033')**: Filters the records for the specific user's `sys_id` ( this is sample sys id. replace with the actual `sys_id` of the user).\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/encryptAndDecryptNonPasswordFields/README.md",
    "content": "Dear ServiceNow Community,\n\n\nThe GlideEncrypter API uses 3DES encryption standard with NIST 800-131 A Rev2 has recommended against using to encrypt data after 2023. ServiceNow offers alternative cryptographic solutions to the GlideEncrypter API. \n\nGlide Element API to encrypt/decrypt password2 values through GlideRecord.\n\nBelow are the sample scripts I ran in my PDI: For Password fields. \n\nNote: 'u_pass' is Password (2 Way Encrypted) field.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/encryptAndDecryptNonPasswordFields/encryptAndDecryptNonPasswordFields.js",
    "content": "//To Encrypt password field\nvar grIncident = new GlideRecord('incident');\nif (grIncident.get('dc1c4143476202101b589d2f316d4390')) {\n grIncident.setDisplayValue('u_pass', 'demo@123');\n grIncident.update();\n}\n//NOTE: You can't use the setValue() API for the Password2 field\n\n//To print cipher text\nvar grIncident = new GlideRecord('incident');\nif (grIncident.get('dc1c4143476202101b589d2f316d4390')) {\n gs.info('Encrypted cipher test of password ' + grIncident.getValue('u_pass'));\n}\n\n//To decrypt password field\nvar grIncident = new GlideRecord('incident');\nif (grIncident.get('dc1c4143476202101b589d2f316d4390')) {\n var result = grIncident.u_pass.getDecryptedValue();\n gs.info(\"Decrypted password- \" +result);\n}\n//NOTE: The getDecryptedValue() API isn't scoped. It's available globally.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/encryptAndDecryptPasswordFields/Encrypt and Decrypt Non-Password Fields.js",
    "content": "//For Non-password fields Encryption syntax using Key Management Framework Cryptographic module\n\nvar password = \"Hello World\";\nvar encryptOp = new sn_kmf_ns.KMFCryptoOperation(\"global.vamsi_glideencrypter\", \"SYMMETRIC_ENCRYPTION\")\n .withInputFormat(\"KMFNone\");\nvar encryptedText = encryptOp.doOperation(password); //Encrypting Hello world\ngs.info(\"After Encryption: \" + encryptedText);\n\n\n//For Non-password fields Decryption syntax using Key Management Framework Cryptographic module\n\nvar encryptOp = new sn_kmf_ns.KMFCryptoOperation(\"global.vamsi_glideencrypter\", \"SYMMETRIC_DECRYPTION\")\n .withOutputFormat(\"KMFNone\");\nvar clear_text = encryptOp.doOperation('91ddbb5d47c012101b589d2f316d438012p3lgrR72vEQW5yLk-WXKQ==aGqxYzUXuyLt3HTqcW6-HA=='); //Pass Cipher text of Hello World (Which is the output of first script)\ngs.info(\"After decryption: \" + clear_text);\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/encryptAndDecryptPasswordFields/README.md",
    "content": "Generally when you want to encrypt or decrypt any Non-password fields earlier we have Glide Encrypter API methods for encryption and decryption. \nThe GlideEncrypter API uses 3DES encryption standard with NIST 800-131 A Rev2 has recommended against using to encrypt data after 2023. \nServiceNow offers alternative cryptographic (Key Management Framwork) solutions to the GlideEncrypter API. \n\nNote: ServiceNow recommending to deprecate GlideEncrypter API with in the instances as soon as possible. The actual dead line is September 2025.\n\nThese are the sample scripts I ran in my PDI: For Non-password fields. I used AES 256 algorithm for Symmetric Data Encryption/Decryption.\n\nTo test the scripts you need to create Cryptographic module and generate the key. \n\n\"global.vamsi_glideencrypter\" is my cryptographic module name."
  },
  {
    "path": "Server-Side Components/Background Scripts/findTableSize/README.md",
    "content": "The script retrieves and logs the size of the `sys_attachment_doc` table in ServiceNow by querying the `sys_physical_table_stats` table, which stores metadata about the physical size of tables in the instance.\n\n   - The variable `table_name` is defined as `'sys_attachment_doc'`, which is the table that holds the data for attachments in ServiceNow.\n   - A new GlideRecord instance `tabSizeinGB` is created for the `sys_physical_table_stats` table. This table contains information about the physical size of tables.\n   - The script then adds a query to find the record where the `table_name` field matches `'sys_attachment_doc'`.\n   - The `setLimit(1)` method ensures that only one record is returned, even if there are multiple records for the same table (though typically there should only be one).\n   - The `query()` method executes the query, and the `if (tabSizeinGB.next())` condition checks if a matching record is found.\n   - If a record is found:\n     - It attempts to retrieve the value of the `table_size_in_gb` field (the size of the table in gigabytes) using the `getValue('table_size_in_gb')` method.\n     - If the size is available, the script logs the table name and its size to the system logs using `gs.info()`.\n     - If the size field is empty or not populated, it logs a message stating that the \"Table size information is not available.\"\n   - If no matching record is found, the script logs \"Table not found!\" indicating that the `sys_attachment_doc` table's size could not be retrieved.\n\n- Purpose: The script is designed to retrieve and display the size of a specified table in gigabytes from the `sys_physical_table_stats` table.\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/findTableSize/findTableSize.js",
    "content": "var table_name = 'sys_attachment_doc'; //The file data of attachment is stored in this Table in ServiceNow\n\nvar tabSizeinGB = new GlideRecord('sys_physical_table_stats');\ntabSizeinGB.addQuery('table_name', table_name); //Querying with the 'sys_attachment_doc' table to get the size of it\ntabSizeinGB.setLimit(1); //This will limit to one record query\ntabSizeinGB.query();\nif (tabSizeinGB.next()) {\n    gs.info('[' + tabSizeinGB.getValue('table_name') +']' + ' table Size is: ' + tabSizeinGB.getValue('table_size_in_gb') + ' GB');\n} else {\n    gs.info('Table not found!');\n}\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/inserting a new record into the sys_user table/README.md",
    "content": "# inserting a new record into the sys_user table.\nWith this code, we are creating a new GlideRecord object for the 'sys_user' table, initializing it, setting the values for the 'user_name,' 'first_name,' 'last_name,' and 'email' fields, and then inserting a new record into the 'sys_user' table with the specified values.\nIn sys_user table a new rcord will be created\n"
  },
  {
    "path": "Server-Side Components/Background Scripts/inserting a new record into the sys_user table/script.js",
    "content": "var gr=new GlideRecord('sys_user');\ngr.initialize();\ngr.user_name='test.user';\ngr.first_name='test';\ngr.last_name='user';\ngr.email='test.user@servicenow';\ngr.insert();\n"
  },
  {
    "path": "Server-Side Components/Business Rules/ATF Duplicate Execution Order/ATF_Duplicate_Execution_order.js",
    "content": "/**\n * Executes a business rule to find duplicate execution orders in ATF.\n * @Table sys_atf_test\n * @param {GlideRecord} current - The current GlideRecord object.\n * @param {GlideRecord} previous - The previous GlideRecord object. (null when async)\n * @returns {undefined}\n */\n\n(function executeRule(current, previous /*null when async*/) {\nvar order_array = testDuplicateTestStepExectionOrder(current.sys_id);\n\nif (order_array.length > 0)\n\tgs.addErrorMessage('WARNING: Test steps with duplicate Execution order: [' + order_array + '].');\n\n})(current, previous);\n\n\n/**\n * Returns an array of active tests that have at least two active test steps with the same execution order.\n * @param {string} testSysId - The sys_id of the test.\n * @returns {Array} An array of sys_ids or order numbers.\n */\n\nfunction testDuplicateTestStepExectionOrder (testSysId) {\n\n\t//if tests_sys_id has a value then return itself if the test fails\n\tvar result = [];\n\n\tvar ga = new GlideAggregate('sys_atf_step');\n\n\tif (test_sys_id)\n\t\tga.addQuery('test.sys_id', test_sys_id);\n\n\tga.addQuery('active', 'true');\n\n\tga.addAggregate('COUNT');\n\n\tga.groupBy('test');\n\tga.groupBy('order');\n\n\tga.query();\n\n\twhile (ga.next()) {\n\t\tif (ga.getAggregate('COUNT') > 1)\n\t\t\tif (test_sys_id)\n\t\t\t\tresult.push(ga.getValue('order'));\n\t\t\telse\n\t\t\t\tresult.push(ga.getValue('test'));\n\t}\n\n\treturn result;\n\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/ATF Duplicate Execution Order/README.md",
    "content": "Usage : Executes a business rule to find duplicate execution orders in ATF.\nExecutes on table sys_atf_test\n\nThe business rule consists of two main parts:\n\nexecuteRule Function:\n  Executes the business rule logic when a specific event occurs.\n  It checks for duplicate execution orders within ATF and generates an error message if duplicates are found.\n\ntestDuplicateTestStepExectionOrder Function:\n  A helper function responsible for identifying duplicate execution orders.\n  Returns an array of active tests that contain at least two active test steps with the same execution order.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Abort Parent Incident Closure When Child is Open/README.md",
    "content": "This business rule is designed for ServiceNow to prevent a parent incident from being closed or resolved while it still has active child incidents.\nIf a user attempts to set the parent incident's state to \"Resolved,\" \"Closed,\" or \"Cancelled,\" the rule will query for any related child incidents that are still open. \nIf open children are found, the update will be aborted, and an error message will be displayed to the user.\n\nNavigate to System Definition > Business Rules in the ServiceNow filter navigator.\nClick New.\nFill out the form with the following details:\nName: Prevent Parent Closure with Open Children\nTable: Incident [incident]\nAdvanced: true\nWhen: before\nUpdate: Check this box.\nIn the When to run tab, set the Condition field:\ncurrent.state.changesTo(7) || current.state.changesTo(6) || current.state.changesTo(8)  //The state values are: 6 (Resolved), 7 (Closed), 8 (Cancelled).\nNote: The state values (6, 7, 8) may vary based on your instance configuration.\nIn the Advanced tab, paste the provided script into the Script field.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Abort Parent Incident Closure When Child is Open/scriptBR.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    \n    \n    //When the Incident state values are set to: 6 (Resolved), 7 (Closed), 8 (Cancelled).\n    // The `previous.state` check prevents the script from running when a closed ticket is re-closed.\n    if ((current.state == '6' || current.state == '7' || current.state == '8') && current.state != previous.state) {\n\n        // Use GlideAggregate to efficiently count child incidents that are not yet closed.\n        var ga = new GlideAggregate('incident');\n        ga.addQuery('parent_incident', current.sys_id);\n        ga.addActiveQuery();\n        ga.addAggregate('COUNT');\n        ga.query();\n        var childCount = 0;\n        if (ga.next()) {\n            // Retrieve the aggregated count.\n            childCount = ga.getAggregate('COUNT');\n        }\n        // If open child incidents are found, abort the parent's closure and display an error.\n        if (childCount > 0) {\n            gs.addErrorMessage('Cannot close this incident. ' + childCount + ' child incidents are still open.');\n            current.setAbortAction(true);\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add HR task for HR case/Add HR Task for VIP HR Case.js",
    "content": "// Business Rule: 'After' Insert on 'sn_hr_core_case'\n(function executeRule(current, previous /*null when async*/) {\n\n    if (current.priority == \"1\" && current.subject_person.getValue('VIP') == 'true') {\n        var newTask = new GlideRecord('sn_hr_core_task');\n        newTask.initialize();\n        newTask.short_description = 'Priority VIP HR task for - ' + current.number;\n        newTask.assigned_to = current.assigned_to;\n        newTask.parent = current.sys_id;\n        newTask.insert();\n        \n        gs.addInfoMessage('A related HR task has been created for this HR case.');\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add HR task for HR case/README.md",
    "content": "This business rule is configured to run after a record is inserted in the sn_hr_core_case table (which is parent table for HR cases). \nwith conditions subjcet person is VIP and when a case priority is high.\n\nwe are simply checking the conditions at the script level. (this can be checked at the condition level as well.)\nand creating hr task \n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add itil role to ootb user query to also see inactive users/README.md",
    "content": "A common request is to also allow itil users to also be able to see inactive user records.\nThere are two pieces of code in the code.js file:\n1) A conditional piece of code that should be added to the \"Condition\" field within the business rule\n2) A single line that should be added to the \"Script\" field within the business rule\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add itil role to ootb user query to also see inactive users/code.js",
    "content": "//set condition field within user query business rule to: gs.getSession().isInteractive() && !gs.hasRole(\"admin,user_admin,itil\")\ncurrent.addActiveQuery(); //this will add an active query to the user's sys_user query if the user is in an interactive session and does not have admin, user_admin, or itil roles\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add notes on tag addition or removal/README.md",
    "content": "This business rule will operate on the label_entry table to log notes whenever tags are added or removed from specific tables. To implement this, create three system properties:\n1. custom.tag_entries.log_removal (true/false): Set this to true to enable logging of tag removals.\n2. custom.tag_entries.tables: A list of tables, separated by commas, where notes should be managed.\n3. custom.tag_entries.log_addition (true/false): Set this to true to enable logging of tag additions.\nOne challenge with tags is identifying who added or removed them from records. With these business rules in place, this information will be easily accessible.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add notes on tag addition or removal/update_notes_tag_addition.js",
    "content": "@ -0,0 +1,38 @@\n/**********************BR COnfig Start***************************/\n/*\nTable: label_entry\nWhen: async\norder: 2000\ninsert: true\nFilter: labelISNOTEMPTY^label.nameISNOTEMPTY^EQ\n*/\n/**********************BR COnfig End***************************/\n\n(function executeRule(current, previous /*null when async*/ ) {\n    // Check if logging for tag additions is enabled\n    if (gs.getProperty('custom.tag_entries.log_addition').toString() == \"true\") {\n        var current_table = current.getValue('table'); // Get the current table name\n        var allowed_tables = gs.getProperty('custom.tag_entries.tables'); // Get allowed tables for addition of notes from tag entries from properties\n        allowed_tables = allowed_tables.split(','); // Split into an array\n\n        // Verify if the current table is in the allowed list\n        if (allowed_tables.indexOf(current_table) > -1) {\n            var gr_task = new GlideRecord(current_table); // Instantiate a GlideRecord for the current table\n            try {\n                // Query the record using sys_id stored in table_key\n                gr_task.addQuery(\"sys_id\", current.table_key.getValue());\n                gr_task.query(); // Execute the query\n                \n                // If record found, update work_notes if the field is valid\n                if (gr_task.next()) {\n                    if (gr_task.isValidField('work_notes')) {\n                        gr_task.work_notes = \"Tag added: \" + current.getDisplayValue('label'); // Append tag info\n                        gr_task.update(); // Save changes\n                    }\n                }\n            } catch (e) {\n                // Log any exceptions encountered during execution\n                gs.log(\"Exception occurred in the business rule Updating record when tagged\" + e.message);\n            }\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add notes on tag addition or removal/update_notes_tag_removal.js",
    "content": "@ -0,0 +1,37 @@\n/**********************BR COnfig Start***************************/\n/*\nTable: label_entry\nWhen: before\norder: 100\ndelete: true\nFilter: labelISNOTEMPTY^label.nameISNOTEMPTY^EQ\n*/\n/**********************BR COnfig End***************************/\n(function executeRule(current, previous /*null when async*/ ) {\n    // Check if logging for tag removals is enabled\n    if (gs.getProperty('custom.tag_entries.log_removal').toString() == \"true\") {\n        var current_table = current.getValue('table'); // Retrieve the name of the current table\n        var allowed_tables = gs.getProperty('custom.tag_entries.tables'); // Get the list of allowed tables from properties\n        allowed_tables = allowed_tables.split(','); // Split the string into an array\n\n        // Verify if the current table is in the allowed list\n        if (allowed_tables.indexOf(current_table) > -1) {\n            var gr_task = new GlideRecord(current_table); // Create a GlideRecord object for the current table\n            try {\n                // Query the record using sys_id stored in table_key\n                gr_task.addQuery(\"sys_id\", current.table_key.getValue());\n                gr_task.query(); // Execute the query\n\n                // If the record is found, update work_notes if the field is valid\n                if (gr_task.next()) {\n                    if (gr_task.isValidField('work_notes')) {\n                        gr_task.work_notes = \"Tag removed: \" + current.getDisplayValue('label'); // Append removal info to work notes\n                        gr_task.update(); // Save the changes made to work notes\n                    }\n                }\n            } catch (e) {\n                // Log any exceptions encountered during execution\n                gs.log(\"Exception occurred in the business rule Updating record when tagged\" + e.message);\n            }\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add or remove a tag from the ticket whenever the comments are updated/README.md",
    "content": "The purpose of this code is to conditionally apply a specific label (label_entry) to an Incident when the person updating the record is the same as the caller. If the update is made by someone else, the label is removed.\nThis mechanism helps fulfillers quickly identify caller driven updates, enabling faster and more targeted responses. Additionally, it can be leveraged in reporting to track caller engagement.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add or remove a tag from the ticket whenever the comments are updated/code.js",
    "content": "//Business Rule: After update on the incident table\n//Condition: Additional comments changes\n//Create on global Tag record ex: Comments added\n\n(function executeRule(current, previous /*null when async*/ ) {\n\n    var caller = current.caller_id.user_name;\n// Add tag to the incident record if the comments is updated by the caller\n    if (current.sys_updated_by == caller) {\n        var add_tag_entry = new GlideRecord('label_entry');\n        add_tag_entry.initialize();\n        add_tag_entry.label = '<sys_id  of the Tag>';\n        add_tag_entry.table = 'incident';\n        add_tag_entry.table_key = current.sys_id;\n        add_tag_entry.insert();\n    } else {\n// Remove tag from the incident record if the agent responds back to the caller\n        var remove_tag_entry = new GlideRecord('label_entry');\n        remove_tag_entry.addEncodedQuery(\"label=<sys_id of the Tag>^table_key=\" + current.sys_id);\n        remove_tag_entry.query();\n        if (remove_tag_entry.next()) {\n            remove_tag_entry.deleteRecord();\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add woknotes for 75 percent SLA/README.md",
    "content": "This business rule will run on the [sys_email_log] table.\nWhen to run: after, insert\nFilter condition: Notification is [select the notification]\n\nIn the Advance> script> paste the code part.\n\nThis business rule will help to provide exceeded time information as a UI \nabout the SLA in the worknotes of the ticket when it has been reached to certain time period(here it is 75%).\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add woknotes for 75 percent SLA/addWorknotesForSLA.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\tvar getTaskSLA = new GlideRecord('task_sla');\n\t//get the task SLA Record\n\tgetTaskSLA.addQuery('sys_id', current.event.instance);\n\tgetTaskSLA.query();\n\twhile(getTaskSLA.next()){\n\t\tvar incRec = new GlideRecord('incident');\n\t\t//Hardware Group sysID - 8a5055c9c61122780043563ef53438e3\n\t\tincRec.addEncodedQuery(\"active=true^sys_id=8a5055c9c61122780043563ef53438e3\");\n\t\tincRec.addQuery('sys_id', getTaskSLA.task);\n\t\tincRec.query();\n\t\twhile(incRec.next()){\n\t\t\tincRec.work_notes = \"This ticket\" + incRec.number +\" has exceeded 75% of SLA.\";\n\t\t\tincRec.update();\n\t\t}\n\t}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add work notes for relevant Change Requests for Incident/README.md",
    "content": "Adds the Change Request numbers to the work notes of incidents that have the same CI.\nOperates on the Incident table as a Before Business Rule.\n\nExcludes CRs that are closed and cancelled.\nHelps agents to quickly identify related changes during incident triage.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Add work notes for relevant Change Requests for Incident/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\tif(current.cmdb_ci){\n\t\tvar ci = current.cmdb_ci.getValue();\n\n\t\tvar chng = new GlideRecord('change_request');\n\t\tchng.addQuery('cmdb_ci', ci);\n\t\tchng.addQuery('state', '!=', '3');\n\t\tchng.addQuery('state', '!=', '4');\n\t\tchng.query();\n\n\t\tvar work_notes = '';\n\n\t\twhile(chng.next()){\n\t\t\twork_notes += chng.getValue('number') + '\\n';\n\t\t}\n\n\t\tif(work_notes){\n\t\t\tcurrent.work_notes = 'Following change requests are associated with the same CI. You can attach one of them.\\n' + work_notes;\n\t\t}\n\t\n\t}\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/After-BR to generate approvals for catalog tasks/README.md",
    "content": "This code snippet will help you to generate approvals for catalog tasks via scripting. You just need to create an after insert BR and put this script there.\nThis script can be used in a workflow run script as well and you can modify the script a little bit and use it for other tables as well. \n\nFun fact: When you are playing with Document Id type field. You need to keep a field as dependent for the document ID like we have 'Source table' for 'Approving' field to put the correct table name there and with the help of that you can easily set the document ID field.\n\nFor e.g. dependent field name is u_table_name, so your script can be something like below:\n\n- obj.u_table_name = 'Name of the table for your document ID type field';\n- obj.u_document_id = 'Sys_id of the correct record from above table';\n- obj.update();\n\nwhere 'obj' is an object of the record you are referring to.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/After-BR to generate approvals for catalog tasks/approval.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\nvar sysApp = new GlideRecord('sysapproval_approver');\n  sysApp.initialize();\n\n  sysApp.state = 'requested'; //set the state to requested\n  sysApp.sysapproval = current.sys_id; // set the 'Approval for' field with the current catalog task\n  sysApp.source_table = 'sc_task'; // set the source table field so that document ID table can be updated properly as it is dependent on source table field.\n  sysApp.approver = 'sys_id of the person for whom you want to trigger this approval'; // set the approver\n  sysApp.document_id = current.sys_id; //set the sys_id to populate correct value in the Approving field.\n  \n  sysApp.insert(); // Insert/Create the approval record.\n  \n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Allow only unique insert/README.md",
    "content": "Before Business rule to check for existing entry in a table , incase one would want to make an insert to be unique based on a certain criteria.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Allow only unique insert/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\n\tvar gr = new GlideRecord(\"<table_name>\");\n\tgr.addQuery(\"<unique_field>\", current.<unique_field_name>);\n\tgr.query();\n\tif (gr.next()) {\n\t\tgs.addErrorMessage(\"Entry for this <field_name> already exists\");\n\t\tcurrent.setAbortAction(true);\n\t}\n\t\n\t\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Approval Matrix/Matrix.js",
    "content": "/*Scenario :\nWhenever a record (like an HR Case, Request Item, or Change) is created, the script:\nLooks up the right approvers based on dynamic rules (department, amount, category, etc.)\nAutomatically creates approvals in the sysapproval_approver table.*/\n\n/* Business Rule :\n\nTable: sc_request or proc_po_request or  custom table\nWhen: After Insert\nCondition: Only when approval is required */\n\n(function executeRule(current, previous /*null when async*/) {\n\n    try {\n        var dept = current.u_department.name + '';\n        var amount = parseFloat(current.u_amount + '');\n        if (!dept || isNaN(amount)) {\n            gs.info('Approval Matrix: Missing department or amount');\n            return;\n        }\n\n        // Query approval matrix for matching rule\n        var matrix = new GlideRecord('u_approval_matrix');\n        matrix.addQuery('u_department.name', dept);\n        matrix.addQuery('u_min_amount', '<=', amount);\n        matrix.addQuery('u_max_amount', '>=', amount);\n        matrix.query();\n\n        if (!matrix.hasNext()) {\n            gs.info('Approval Matrix: No matching rule found for ' + dept + ', amount: ' + amount);\n            return;\n        }\n\n        while (matrix.next()) {\n            var approverUser = '';\n            \n            // Option 1: Approver directly specified\n            if (!gs.nil(matrix.u_approver)) {\n                approverUser = matrix.u_approver;\n            }\n            // Option 2: Use role from requester’s hierarchy\n            else if (!gs.nil(matrix.u_role)) {\n                approverUser = getApproverByRole(current.requested_for, matrix.u_role + '');\n            }\n\n            if (approverUser) {\n                createApproval(current.sys_id, current.getTableName(), approverUser);\n                gs.info('Approval Matrix: Created approval for ' + approverUser);\n            }\n        }\n\n    } catch (ex) {\n        gs.error('Approval Matrix Error: ' + ex.message);\n    }\n\n    // --- Helper: Find approver based on user role ---\n    function getApproverByRole(userSysId, roleName) {\n        var usr = new GlideRecord('sys_user');\n        if (usr.get(userSysId)) {\n            if (roleName == 'Manager' && usr.manager) return usr.manager;\n            if (roleName == 'Director' && usr.u_director) return usr.u_director; // custom field\n            if (roleName == 'VP' && usr.u_vp) return usr.u_vp;\n        }\n        return '';\n    }\n\n    // --- Helper: Create approval record ---\n    function createApproval(targetSysId, targetTable, approverSysId) {\n        var appr = new GlideRecord('sysapproval_approver');\n        appr.initialize();\n        appr.sysapproval = targetSysId;\n        appr.table_name = targetTable;\n        appr.state = 'requested';\n        appr.approver = approverSysId;\n        appr.insert();\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Approval Matrix/README.md",
    "content": "# ServiceNow Approval Matrix Generator  \n**A Dynamic, Data-Driven Approval Workflow Engine for ServiceNow**\n\n---\n\n## Overview\n\nThe **Approval Matrix Generator** is a configurable engine that automates approval generation in ServiceNow based on business rules —  \nwithout hard-coding approvers or creating dozens of Flow Designer flows.  \n\nBy maintaining a simple **Approval Matrix table**, you can define which user or role should approve a request dynamically (e.g., based on department and amount).  \nThis approach provides scalability, maintainability, and full visibility across all approval logic.\n\n---\n\n## Key Highlights\n\n✅ 100% native ServiceNow solution (no plugins)  \n✅ Centralized approval logic in a single configuration table  \n✅ Works for ITSM, HRSD, Finance, or Procurement workflows  \n✅ Supports multi-level approvals (Manager → Director → CFO)  \n✅ Can run via **Business Rule** \n\n---\n\n## Use Case\n\nAn organization wants dynamic approval routing for procurement or HR requests based on:\n- Department (IT, HR, Finance)\n- Request amount (₹0–₹10,000, etc.)\n- Approval role (Manager, Director, VP)\n\nInstead of building multiple flows, the Approval Matrix defines all rules in a single table.  \nWhen a new request is submitted, the script automatically finds and assigns the correct approvers.\n\n---\n\n## Step 1 — Create Table `u_approval_matrix`\n\n| Field | Type | Description |\n|--------|------|-------------|\n| **u_department** | Reference (sys_user_group) | Department owning the rule |\n| **u_min_amount** | Decimal | Minimum amount for range |\n| **u_max_amount** | Decimal | Maximum amount for range |\n| **u_role** | Choice | Role type – Manager / Director / VP |\n| **u_approver** | Reference (sys_user) | Direct approver (optional) |\n\nThis table drives all the logic.  \nExample data:\n\n| Department | Min | Max | Role | Approver |\n|-------------|-----|-----|------|-----------|\n| IT | 0 | 5000 | Manager | *(blank)* |\n| IT | 5000 | 10000 | Director | *(blank)* |\n| Finance | 0 | 10000 | *(blank)* | John CFO |\n\n---\n\n## ⚙️ Step 2 — Business Rule Script\n\n**Table:** `sc_request` (or your custom table)  \n**When:** After Insert  \n**Condition:** Approval required\n\n## Example Input (Request Record)\n| Field         | Value             |\n| ------------- | ----------------- |\n| Requested For | Ravi Gaurav       |\n| Department    | IT                |\n| Amount        | 8000              |\n| Category      | Hardware Purchase |\n\n## Example Output\n| Field                | Value                                     |\n| -------------------- | ----------------------------------------- |\n| Matched Rule         | IT, 5000–10000, Role = Director           |\n| Approver Found       | Ravi’s Director (from `u_director` field) |\n| Approval State       | Requested                                 |\n| sysapproval_approver | Created Automatically                     |\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Assign specific role to user/README.md",
    "content": "**Business Rule**\n\nScript to *automatically assign* a *specific role* on creation or update of users, with additional protection against duplication of roles. You can run that Business Rules based on your needs (on user creation or on update when specific field was changed). To assign different role, copy sys_id of chose role into ROLE_ID variable.\n\n**Example configuration of Business Rule**\n![Configuration](ScreenShot1.PNG)\n\n**Example execution effect**\n![Execution](ScreenShot2.PNG)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Assign specific role to user/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    //Script to automatically assgin a specific role on creation or update of users\n\n    //Sys_id value of the selected role which is to be granted for the user (in this example it is the 'knowledge' role)\n    var ROLE_ID = '1f26da36c0a8016b000c7f06a1ce7e14';\n\n    //Query to find out the specific role asgined to current user\n    var grRole = new GlideRecord('sys_user_has_role');\n    grRole.addQuery('user', current.sys_id);\n    grRole.addQuery('role', ROLE_ID);\n    grRole.query();\n\n    //Verify if the role already exists, to prevent role duplicates\n    if (!grRole.hasNext()) {\n\n        //Initialize and insert new role for current user\n        grRole.initialize();\n        grRole.user = current.sys_id;\n        grRole.role = ROLE_ID;\n        grRole.insert();\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Async REST Call/README.md",
    "content": "This Business Rule script is used to make an async call to the REST integration (Eg:Salesforce from ServiceNow). \nAfter making all the REST service setup, we can call this BR script and is used after inserting a new user from ServiceNow. \nAs soon as we create a new user in sys_user table, we call this BR and make a REST call to the target system (Eg:SalesForce) and create an entry over there.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Async REST Call/callAsynREST.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\n\ttry { \n var r = new sn_ws.RESTMessageV2('MySFConn', 'Default GET');\n r.setStringParameterNoEscape('name', current.first_name.toString() + \" \" + current.last_name.toString());\n\n//override authentication profile \n//authentication type ='basic'/ 'oauth2'\n//r.setAuthenticationProfile(authentication type, profile name);\n\n//set a MID server name if one wants to run the message on MID\n//r.setMIDServer('MY_MID_SERVER');\n\n//if the message is configured to communicate through ECC queue, either\n//by setting a MID server or calling executeAsync, one needs to set skip_sensor\n//to true. Otherwise, one may get an intermittent error that the response body is null\n//r.setEccParameter('skip_sensor', true);\n\n //var response = r.execute();\nvar response = r.executeAsync();\nresponse.waitForResponse(10);\t\t\n var responseBody = response.getBody();\n var httpStatus = response.getStatusCode();\n}\ncatch(ex) {\n var message = ex.message;\n}\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Attachment Variable from Activity Stream to Clip Icon/Attachment Variable Fix.js",
    "content": "//Run this Business Rule async Insert on the sc_req_item table if using the attachment type variable in a Catalog Item\n(function executeRule(current, previous /*null when async*/) {\n    var gr = new GlideRecord(\"sys_attachment\");\n\t  gr.addQuery(\"table_name\", \"ZZ_YY\" + current.getTableName());\n\t  gr.addQuery(\"table_sys_id\", current.sys_id);\n\t  gr.query();\n\t  while (gr.next()) {\n\t\t    gr.table_name = current.getTableName();\n\t\t    gr.update();\n      \t\n\t  }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Attachment Variable from Activity Stream to Clip Icon/README.md",
    "content": "When attaching a file via an attachment type variable, on the target record the attachment appears in the Activity Stream instead of at the top associated with the paper clip icon, where one typically looks for / notices attachments.  This Business Rule will convert the entry in the sys_attachment table so that the attachment added via an attachment type variable appears at the top of the record, associated with the paper clip icon.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AttachmentFormatValidator/AttachmentFormatValidator.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    if (current.table_name == 'incident') {\n\n        // Fetch the file name of the uploaded attachment\n        var fileName = current.getValue('file_name');\n\n        // Fetch allowed extensions from system property (comma-separated, lowercase)\n        var allowedExtensions = gs.getProperty('attachment.format.allowedExtensions', 'pdf,docx,png,jpg')\n            .toLowerCase()\n            .split(',');\n\n        var fileExtension = '';\n        if (fileName && fileName.indexOf('.') !== -1) {\n            fileExtension = fileName.split('.').pop().toLowerCase();\n        }\n\n        // If file extension not allowed — prevent insert\n        if (allowedExtensions.indexOf(fileExtension) === -1) {\n            gs.addErrorMessage('File type \"' + fileExtension + '\" is not allowed. Allowed types: ' + allowedExtensions.join(', '));\n            gs.log('Attachment blocked: Disallowed file type \"' + fileExtension + '\" for table ' + current.table_name);\n            current.setAbortAction(true);\n            return false;\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AttachmentFormatValidator/Readme.md",
    "content": "The validator runs automatically on the sys_attachment table during record creation and checks each file extension against an allowed list defined in a system property.\nIf a file type is not allowed, the upload is blocked, the record creation is aborted, and a descriptive error is logged.\n**Key Features:**\nServer‑side enforcement (cannot be bypassed through APIs or imports).\nConfigurable allowed file extensions through a single system property.\nOptional restriction to specific business tables.\nLightweight validation for secure instance operation.\n**Functionality Summary**\nEach attachment upload triggers the Business Rule before insert.\nThe file name and extension are extracted.\nAllowed file extensions are read from the system property attachment.format.allowedExtensions.\nThe script checks whether the uploaded file complies with this configuration.\nIf disallowed, the upload is rejected and a clear error message appears in the system log or UI.\n\n**Configuration**\nSystem Property\nattachment.format.allowedExtensions -\tDefines which file types users are allowed to upload - sample values : pdf,docx,xlsx,png,jpg\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Attachments check High-Risk or High-Impact Change request/README.md",
    "content": "This Before Update business rule acts as a safeguard in a Change management process, \nensuring that critical changes(those marked as high impact or high risk)\nare properly documented before progressing to key implementation stages.\n\n**BR Type**: 'Before', 'Update'\n**Table**: Change Request (change_request)\n**Condition**: 'State' 'changes to' 'Scheduled' OR 'State' 'changes to' 'Implement'\n\n**What It Does**:\n-The BR triggers before a change request record is updated, specifically when the state changes to either Scheduled or Implement.\n\n-It checks whether the change is classified as high impact or high risk.\n\n-If the change meets either of those criteria, it verifies that at least two attachments are present on the record. \n These attachments are expected to be essential supporting documents like an Implementation Plan or Backout Procedure.\n\n-If the required documentation is missing, the rule blocks the state change and displays an error message to the user, \n preventing the change from moving forward until compliance is met.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Attachments check High-Risk or High-Impact Change request/attachmentcheckonhighriskimpactChange.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    var minAttachments = 2;\n\n    var isHighImpact = current.impact == '1';\n    var isHighRisk = current.risk == '2';\n\n    if (isHighImpact || isHighRisk) {\n\n        var attachmentCount = 0;\n        var attachment = new GlideAggregate('sys_attachment');\n        attachment.addQuery('table_sys_id', current.sys_id);\n        attachment.addAggregate('COUNT');\n        attachment.query();\n        if (attachment.next()) {\n            attachmentCount = attachment.getAggregate('COUNT');\n        }\n\n        if (attachmentCount < minAttachments) {\n\n            gs.addErrorMessage('State Change aborted: High-Impact/High-Risk Changes require at least ' + minAttachments + ' supporting documents (eg: Implementation Plan, Backout Procedure)' + 'attached before moving to the Scheduled/Implementation phase.');\n            current.setAbortAction(true);\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Approve VIP Approvals/README.md",
    "content": "## Description:\nThis Business Rule will auto-approve an Approval [sysapproval_approver] record when the Approver and the Requested for on a RITM are the same, and the user is a VIP User. This allows VIP users to receive the services they requested faster and avoid an unecesary approval step in the process.\n\n## Usage Instructions/Examples:\nThis script is specfic for RITM's but could easily be refactored to work for other approvals on the platform (i.e. change requests).\n\n#### When to run values:\n\n- When: after\n    - Note: This could run before, but I choose to make an update on another table (aka add a comment to the RITM about the auto-approval)\n    - Note 2: If you choose to run this before, please remove the 'current.update()' from line 11 in the script\n- Insert: true\n- Update: false\n    - Note: This could be updated to true if needed for your business process\n- Filter Conditions: Source table is sc_req_item AND State changes to Requested\n    - Note: The source table can be changed to other tables such as change_request\n\n## Prerequisites/Dependencies:\n1) A Catalog Item with approvals from VIP users\n2) A business process that allows VIP Users to bypass their own approvals\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Approve VIP Approvals/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    // Query the requested item to get the requested_for user\n    var grReqItem = new GlideRecord('sc_req_item');\n    grReqItem.get(current.sysapproval);\n    var requestedFor = grReqItem.request.requested_for;\n\n    // Check if the approver is the same as the requested_for user AND a VIP User\n    if (requestedFor == current.approver && current.approver.vip == true) {\n        current.setValue('state', 'approved');\n        current.update(); //Only needed because this is an after BR, remove this if you decide to do a before BR\n\n        grReqItem.comments = \"This request was auto-approved due to Requester's VIP status.\";\n        grReqItem.update();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Create Problem Records for Recurring Incidents/README.md",
    "content": "This \"after\" business rule automatically creates a Problem record when a particular Configuration Item (CI) has had 5 or more incidents in the last 24 hours, and no open Problem already exists for that CI.\nThis helps in proactive problem management, aiming to address recurring issues.\nHere’s the working of the code explained:\n\n - Check if CI is present in the current Incident (current.cmdb_ci).\n - Count incidents created in the last 24 hours for the same CI using GlideAggregate.\n\nIf 5 or more incidents are found for that CI:\n - Query the Problem table to check if an open Problem (not closed) already exists for that CI.\n - If no open Problem exists, create a new Problem record with: The same CI, A predefined short description And set its state to New (1).\n - Log a message indicating that a Problem has been created.\nThis automates Problem creation for frequent incidents on the same CI.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Create Problem Records for Recurring Incidents/code.js",
    "content": "(function executeRule(current, previous) {\n    if (!current.cmdb_ci)\n        return;\n\n    var ck = new GlideAggregate('incident');\n    ck.addQuery('cmdb_ci', current.cmdb_ci);\n    ck.addQuery('sys_created_on', '>=', gs.daysAgoStart(1));\n    ck.addAggregate('COUNT');\n    ck.query();\n\n    if (ck.next() && ck.getAggregate('COUNT') >= 5) {\n        var problemGR = new GlideRecord('problem');\n        problemGR.addQuery('cmdb_ci', current.cmdb_ci);\n        problemGR.addQuery('state', '<', 8); // Not Closed\n        problemGR.query();\n\n        if (!problemGR.hasNext()) {\n            problemGR.initialize();\n            problemGR.short_description = 'Recurring incidents on ' + current.cmdb_ci.name;\n            problemGR.cmdb_ci = current.cmdb_ci;\n            problemGR.state = 1; // New\n            problemGR.insert();\n            gs.log('Problem created for recurring incidents on CI: ' + current.cmdb_ci.name);\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Incident Notification and Escalation/README.md",
    "content": "# Complex Incident Escalation and Notification\n\n## Description\nThis project demonstrates how to implement a complex Business Rule for incident escalation and notification in ServiceNow. The Business Rule escalates incidents based on priority and time elapsed since creation, notifies the assigned group and incident manager, reassigns the incident to a higher support group if the SLA is breached, and logs all actions taken for auditing purposes.\n\n## Features\n- Escalate incidents based on priority and time elapsed since creation.\n- Notify the assigned group and incident manager.\n- Automatically reassign incidents to a higher support group if the SLA is breached.\n- Log all actions taken by the Business Rule.\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Incident Notification and Escalation/incident_notification.js",
    "content": "// Business Rule: Complex Incident Escalation\n// Escalates incidents based on priority and elapsed time since creation. \n// Notifies the assigned group and incident manager, reassigns the incident if SLA is breached, and logs all actions.\n\n(function executeRule(current, previous /* null when async */) {\n    const ESCALATION_THRESHOLD_HOURS = 4;\n    const SLA_BREACH_THRESHOLD_HOURS = 8;\n    const HIGHER_SUPPORT_GROUP_ID = gs.getProperty('esc.incident.higher_support_group', '');\n\n    if (!HIGHER_SUPPORT_GROUP_ID) {\n        gs.error('Higher Support Group sys_id not defined. Please configure esc.incident.higher_support_group.');\n        return;\n    }\n    const timeElapsedInHours = parseInt(gs.dateDiff(current.getValue('sys_created_on'), gs.nowDateTime(), true)) / 3600;\n    if (isEscalationNeeded(current, timeElapsedInHours)) {\n        escalateIncidentPriority(current);\n        notifyAssignedGroupAndManager(current);\n        if (isSLABreached(timeElapsedInHours)) {\n            reassignToHigherSupportGroup(current, HIGHER_SUPPORT_GROUP_ID);\n        }\n\n        logEscalationActions(current);\n    }\n    function isEscalationNeeded(incident, timeElapsed) {\n        return incident.getValue('state') != 6 && timeElapsed > ESCALATION_THRESHOLD_HOURS;\n    }\n    function escalateIncidentPriority(incident) {\n        const newPriority = Math.max(1, incident.getValue('priority') - 1);\n        incident.setValue('priority', newPriority);\n        gs.addInfoMessage('Incident priority has been escalated to ' + newPriority + '.');\n    }\n    function notifyAssignedGroupAndManager(incident) {\n        const assignedGroup = incident.getValue('assignment_group');\n        const incidentManager = incident.getValue('u_incident_manager');\n        gs.eventQueue('incident.escalation.notification', incident, assignedGroup, incidentManager);\n    }\n    function isSLABreached(timeElapsed) {\n        return timeElapsed > SLA_BREACH_THRESHOLD_HOURS;\n    }\n    function reassignToHigherSupportGroup(incident, groupId) {\n        incident.setValue('assignment_group', groupId);\n        gs.addInfoMessage('Incident reassigned to a higher support group due to SLA breach.');\n    }\n    function logEscalationActions(incident) {\n        const logMessage = 'Incident escalated. Priority: ' + incident.getValue('priority') + \n                           ', Assigned Group: ' + incident.getValue('assignment_group') + \n                           ', Incident Manager: ' + incident.getValue('u_incident_manager');\n        gs.log(logMessage, 'ComplexIncidentEscalation');\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Tag VTB Based on Record States/README.md",
    "content": "This code snippet allows users to create a business rule to update the tag/label on a vtb record based on the state/priority of the record. The example included uses Agile Story records as the example.\nThis extends on a ServiceNow functionality: https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB1117228\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto Tag VTB Based on Record States/code_snippet.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Retrieve the SysIDs from system properties - holding in system properties to overcome not hardcoding the SysID\n    var highPriorityLabelSysId = gs.getProperty('label.high_priority');\n    var blockedLabelSysId = gs.getProperty('label.blocked');\n    var readyForQALabelSysId = gs.getProperty('label.ready_for_qa');\n    var qaCompletedLabelSysId = gs.getProperty('label.qa_completed');\n    var readyForDeploymentLabelSysId = gs.getProperty('label.ready_for_deployment');\n\n    // Query the 'vtb_card' table for related task records\n    var gr = new GlideRecord('vtb_card');\n    gr.addQuery('task', current.sys_id);\n    gr.query();\n\n    // Loop through all matching vtb_card records\n    while (gr.next()) {\n\n        // Handle High Priority label\n        var highPriorityTag = new GlideRecord('label_entry');\n        highPriorityTag.addQuery('table', 'vtb_card');\n        highPriorityTag.addQuery('table_key', gr.sys_id);\n        highPriorityTag.addQuery('label', highPriorityLabelSysId);\n        highPriorityTag.query();\n        if (current.priority == 1 && !highPriorityTag.next()) {  // Add label if not already present\n            highPriorityTag = new GlideRecord('label_entry');\n            highPriorityTag.label = highPriorityLabelSysId; // High Priority Label SysID\n            highPriorityTag.table = 'vtb_card';\n            highPriorityTag.read = 'yes';\n            highPriorityTag.title = \"High Priority Tag for \" + gr.task.number;\n            highPriorityTag.table_key = gr.sys_id;\n            highPriorityTag.insert();\n        } else if (current.priority != 1 && highPriorityTag.next()) {  // Remove label if priority is not high\n            highPriorityTag.deleteRecord();\n        }\n\n        // Handle Blocked label\n        var blockedTag = new GlideRecord('label_entry');\n        blockedTag.addQuery('table', 'vtb_card');\n        blockedTag.addQuery('table_key', gr.sys_id);\n        blockedTag.addQuery('label', blockedLabelSysId);\n        blockedTag.query();\n        if (current.blocked == true && !blockedTag.next()) {  // Add label if not already present\n            blockedTag = new GlideRecord('label_entry');\n            blockedTag.label = blockedLabelSysId; // Blocked Label SysID\n            blockedTag.table = 'vtb_card';\n            blockedTag.read = 'yes';\n            blockedTag.title = \"Blocked Tag for \" + gr.task.number;\n            blockedTag.table_key = gr.sys_id;\n            blockedTag.insert();\n        } else if (current.blocked == false && blockedTag.next()) {  // Remove label if blocked is false\n            blockedTag.deleteRecord();\n        }\n\n        // Handle Ready for QA label\n        var readyForQATag = new GlideRecord('label_entry');\n        readyForQATag.addQuery('table', 'vtb_card');\n        readyForQATag.addQuery('table_key', gr.sys_id);\n        readyForQATag.addQuery('label', readyForQALabelSysId);\n        readyForQATag.query();\n        if (current.u_implementation_stage == 'QA Required' && !readyForQATag.next()) {  // Add label if not already present\n            readyForQATag = new GlideRecord('label_entry');\n            readyForQATag.label = readyForQALabelSysId; // Ready for QA Label SysID\n            readyForQATag.table = 'vtb_card';\n            readyForQATag.read = 'yes';\n            readyForQATag.title = \"Ready for QA Tag for \" + gr.task.number;\n            readyForQATag.table_key = gr.sys_id;\n            readyForQATag.insert();\n        } else if (current.u_implementation_stage != 'QA Required' && readyForQATag.next()) {  // Remove label if stage is no longer QA Required\n            readyForQATag.deleteRecord();\n        }\n\n        // Handle QA Completed label\n        var qaCompletedTag = new GlideRecord('label_entry');\n        qaCompletedTag.addQuery('table', 'vtb_card');\n        qaCompletedTag.addQuery('table_key', gr.sys_id);\n        qaCompletedTag.addQuery('label', qaCompletedLabelSysId);\n        qaCompletedTag.query();\n        if (current.u_implementation_stage == 'QA Completed' && !qaCompletedTag.next()) {  // Add label if not already present\n            qaCompletedTag = new GlideRecord('label_entry');\n            qaCompletedTag.label = qaCompletedLabelSysId; // QA Completed Label SysID\n            qaCompletedTag.table = 'vtb_card';\n            qaCompletedTag.read = 'yes';\n            qaCompletedTag.title = \"QA Completed Tag for \" + gr.task.number;\n            qaCompletedTag.table_key = gr.sys_id;\n            qaCompletedTag.insert();\n        } else if (current.u_implementation_stage != 'QA Completed' && qaCompletedTag.next()) {  // Remove label if stage is no longer QA Completed\n            qaCompletedTag.deleteRecord();\n        }\n\n        // Handle Ready for Deployment label\n        var readyForDeploymentTag = new GlideRecord('label_entry');\n        readyForDeploymentTag.addQuery('table', 'vtb_card');\n        readyForDeploymentTag.addQuery('table_key', gr.sys_id);\n        readyForDeploymentTag.addQuery('label', readyForDeploymentLabelSysId);\n        readyForDeploymentTag.query();\n        if (current.u_implementation_stage == 'Ready for Deployment' && !readyForDeploymentTag.next()) {  // Add label if not already present\n            readyForDeploymentTag = new GlideRecord('label_entry');\n            readyForDeploymentTag.label = readyForDeploymentLabelSysId; // Ready for Deployment Label SysID\n            readyForDeploymentTag.table = 'vtb_card';\n            readyForDeploymentTag.read = 'yes';\n            readyForDeploymentTag.title = \"Ready for Deployment Tag for \" + gr.task.number;\n            readyForDeploymentTag.table_key = gr.sys_id;\n            readyForDeploymentTag.insert();\n        } else if (current.u_implementation_stage != 'Ready for Deployment' && readyForDeploymentTag.next()) {  // Remove label if stage is no longer Ready for Deployment\n            readyForDeploymentTag.deleteRecord();\n        }\n\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto add email recipients to the message body when Email Override is on/IncludeEmailRecipientsInBody.js",
    "content": "//Name: Include email recipients in body\n//Table: Email (sys_email)\n//When: Before\n//Insert: true\n//Update: true\n//Condition: Type is \"send-ready\"\n\nfunction onBefore(current, previous) {\n\t//Add the recipients to the body of the email so that testers can see who the email was meant for.\n\tvar recipients = \"\\n*****\\nEmail override is on.  All outbound emails are currently sent to: \" + gs.getProperty(\"glide.email.test.user\");\n\trecipients += \"\\nOriginal Intended Recipients: \";\n\tif (current.direct != \"\") {\n\t\trecipients += \"\\nTO: \" + current.direct;\n\t}\n\tif (current.copied != \"\") {\n\t\trecipients += \"\\nCC: \" + current.copied;\n\t}\n\tif (current.blind_copied != \"\") {\n\t\trecipients += \"\\nBCC: \" + current.blind_copied;\n\t}\n\trecipients += \"\\n*****\\n\";\n\t\n\tcurrent.body_text = current.body_text + \"\\n\\n\\n*****\\n\" + recipients + \"\\n*****\\n\";\n\tcurrent.body = current.body + \"<html><body><div><br />\" + recipients.replace(/\\r\\n|[\\r\\n]/g, \"<br /></div></body></html>\");\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto add email recipients to the message body when Email Override is on/README.md",
    "content": "A common complaint I hear about testing email is that people don’t know who was actually supposed to receive emails when the override is on ([glide.email.test.user](https://docs.servicenow.com/bundle/rome-servicenow-platform/page/administer/reference-pages/reference/r_OutboundMailConfiguration.html) property).\n\nThe included business rule takes the intended recipients information and puts it in the body of the email at the bottom as shown below. This information can be used by testers to validate that emails would have been sent to the correct recipients.\n\n*****\nEmail override is on. All outbound emails are currently sent to: bob.atherton@example.com, ccarter@example.com</br>\nOriginal Intended Recipients:</br>\nTO: Ray.Hatch@example.com,Ron.deGuzman@example.com,Glen.Traasdahl@example.com</br>\nCC: Cherdell.Singleton@example.com,Ruthe.Aggarwal@example.com</br>\nBCC: Morgan.Avery@example.com</br>\n*****\n\nIf there aren’t any CC or BCC recipients those lines will be omitted respectively.\n\nThe business rule checks to see if the override is on, so it’s ok to have in a production instance because it won’t apply once they remove the override value.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto approve if previously approved/Auto_approve approvals.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\nif(current.sysapproval.sys_class_name=='<pass table dictionary name here>'){\n    var recis = new GlideRecord('sysapproval_approver');\n    recis.addQuery('sysapproval', current.sysapproval);\n    recis.addQuery('state', 'approved');\n    recis.addQuery('approver', current.approver);\n    recis.setLimit(1);\n    recis.query();\n    if (recis.next()) {\n\n        current.state = 'approved';\n        current.comments = 'Auto-Approved as this request was already approved by ' + current.approver.getDisplayValue();\n    } \n}\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto approve if previously approved/README.md",
    "content": "The purpose of this BR is to avoid redundant approval actions if the ticket is sent to same approver.\nThis should be a Before Insert Business rule that runs on the Approval table.\nReplace the table name accordingly in line 3 of the code to limit it to particular table. For instance, there may be need for auto-approval only for RITMs and not for Change requests.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto close incident if all related changes are closed/Readme.md",
    "content": "Business Rule: Auto-Close Incident When All Related Changes Are Closed\nTable : change_request\nWhen to Run: After update\nCondition: state changes to Closed (or your equivalent \"Closed\" state number, e.g. state == 3)\n\nDetailed Working\n1. Trigger Point\nThis After Business Rule runs after a Change Request record is updated.\nSpecifically, it checks when the state changes to “Closed”.\n\n2. Check for Related Incident\nThe script retrieves the incident reference field (incident) from the current change request.\nIf there’s no linked incident, it skips execution.\n\n3. Check for Any Remaining Open Change Requests\nA new GlideRecord query checks for other Change Requests linked to the same incident where:\nIf any such records exist, it means not all change requests are closed — so the incident remains open.\n\n4. Close the Incident Automatically\nIf no open Change Requests remain, the script:\nFetches the linked incident.\nSets:\tstate = 7 (Closed)\n\tclose_code = Auto Closed\n\tclose_notes = Auto closure as all changes are closed.\nUpdates the record.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto close incident if all related changes are closed/auto_close_incident_if_all_related_change_requests_closed.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    // Run only when change_request moves to Closed\n    if (current.state != previous.state && current.state == 3) { // 3 = Closed\n        var incidentSysId = current.incident;  // assuming there is a reference field to Incident\n        \n        if (!incidentSysId) {\n            gs.info(\"No related incident found for this change request: \" + current.number);\n            return;\n        }\n\n        // Query for other open change requests linked to the same incident\n        var otherCR = new GlideRecord('change_request');\n        otherCR.addQuery('incident', incidentSysId);\n        otherCR.addQuery('sys_id', '!=', current.sys_id);\n        otherCR.addQuery('state', '!=', 3); // not closed\n        otherCR.query();\n\n        if (otherCR.hasNext()) {\n            gs.info(\"Incident \" + incidentSysId + \" still has open change requests. Not closing incident.\");\n            return;\n        }\n\n        // If no open change requests remain, close the incident\n        var inc = new GlideRecord('incident');\n        if (inc.get(incidentSysId)) {\n            inc.state = 7; // 7 = Closed (modify as per your instance)\n            inc.close_code = 'Auto Closed';\n            inc.close_notes = 'Incident auto-closed as all associated change requests are closed.';\n            inc.update();\n            gs.info(\"Incident \" + inc.number + \" auto-closed as all related change requests are closed.\");\n        }\n\n    }\n})(current, previous);\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto tag incident/Auto_Tag_Incident.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // ==============================================================\n    // Business Rule: Incident Auto-Tagging\n    // Table: Incident\n    // --------------------------------------------------------------\n    // Purpose:\n    // Automatically detect keywords (like \"email\", \"vpn\", \"server\")\n    // from the Incident’s short description and description fields.\n    //\n    // Based on detected keywords:\n    //  - A corresponding Label is created (if not already present)\n    //  - A related Label Entry is created for that Incident\n    //\n    // 💡 Benefit:\n    // Adding Labels and Label Entries helps automatically tag\n    // incidents with the right keywords — improving searchability,\n    // categorization, and visibility across the platform.\n    // ==============================================================\n\n    // Step 1: Collect tags from the description\n    var tags = [];\n    var desc = (current.short_description + \" \" + current.description).toLowerCase();\n\n    if (desc.indexOf(\"email\") > -1) tags.push(\"Email\");\n    if (desc.indexOf(\"vpn\") > -1) tags.push(\"VPN\");\n    if (desc.indexOf(\"server\") > -1) tags.push(\"Server\");\n\n    // Step 2: Process each detected tag\n    for (var i = 0; i < tags.length; i++) {\n\n        var tagName = tags[i]; // e.g., \"Email\"\n        var labelSysId;\n\n        // Step 2.1: Check if Label already exists\n        var labelGR = new GlideRecord(\"label\");\n        labelGR.addQuery(\"name\", tagName);\n        labelGR.query();\n\n        if (labelGR.next()) {\n            // Label exists — reuse it\n            labelSysId = labelGR.sys_id.toString();\n        } else {\n            // Create new Label\n            var newLabel = new GlideRecord(\"label\");\n            newLabel.initialize();\n            newLabel.name = tagName;\n            newLabel.owner = gs.getUserID();       // Logged-in user sys_id\n            newLabel.viewable_by = \"everyone\";     // Backend value of choice\n            newLabel.active = true;\n            labelSysId = newLabel.insert();\n        }\n\n        // Step 2.2: Create Label Entry if not already present\n        var entryGR = new GlideRecord(\"label_entry\");\n        entryGR.addQuery(\"table_key\", current.sys_id);\n        entryGR.addQuery(\"label\", labelSysId);\n        entryGR.query();\n\n        if (!entryGR.hasNext()) {\n\n            var newEntry = new GlideRecord(\"label_entry\");\n            newEntry.initialize();\n            newEntry.title = \"Incident - \" + current.number; // e.g., Incident - INC0010003\n            newEntry.label = labelSysId;\n            newEntry.url = \"incident.do?sys_id=\" + current.sys_id + \"&sysparm_view=\";\n            newEntry.table = \"incident\";\n            newEntry.id_type = \"Incident\";\n            newEntry.table_key = current.sys_id;\n            newEntry.read = \"yes\"; \n            newEntry.insert();\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto tag incident/README.md",
    "content": "📘 Overview\n\nThis feature automatically scans newly created or updated Incidents for specific keywords (like Email, VPN, Server) and attaches structured Labels automatically wheneven any incident gets created.\nIt leverages ServiceNow’s native label and label_entry tables to organize incidents into searchable categories — improving visibility, reporting, and problem trend analysis.\n\n🎯 Problem Statement\n\nManual tagging of incidents is inconsistent and time-consuming. As a result, identifying recurring issues or related incidents becomes difficult.\nThe Incident Auto-Tagging solution automates this process, ensuring every incident containing common keywords is automatically labeled and linked for faster triage and analytics.\n\n💡 Solution Approach\n\nA Business Rule runs on the Incident table (Before Insert) to:\n\nDetect defined keywords from Short Description and Description.\n\nCreate or reuse a Label (label table) for each keyword.\n\nCreate a Label Entry (label_entry table) linking the Label to the Incident with details such as title, URL, table, etc.\n\nPrevent duplicates by verifying existing entries before insertion.\n\n🧩 Benefits\n\n🕒 Time saving: Minimizes time as no manual tagging is needed\n\n🔍 Quick Search: Filter incidents by keyword-based labels.\n\n⚡ Faster Resolution: Group recurring issues for faster response.\n\n📊 Analytics Ready: Enables trend and problem management reporting.\n\n🧠 Reusable Logic: Extendable to Change Requests or Catalog Items.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-Assign Incident Based on Keywords, CI, and Department/Readme.md",
    "content": "Auto-Assign Incident Based on Keywords, CI, and Department  using Before Insert Business Rule\n\nAutomatically assigns incidents to the correct assignment group based on:\n\n1.Keywords in the short description.\n\n2.Configuration Item (CI) category.\n\n3.Caller’s department.\n\nIncidents should be routed automatically without manual intervention.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-Assign Incident Based on Keywords, CI, and Department/script.js",
    "content": "//Before Insert Business Rule\n\n(function executeRule(current, previous) {\n\n    //  Keyword-based assignment\n    var shortDesc = current.short_description.toLowerCase();\n    if (shortDesc.includes(\"server down\")) {\n        current.assignment_group = 'SERVER_SUPPORT_GROUP_SYS_ID';\n        return;\n    } else if (shortDesc.includes(\"email issue\")) {\n        current.assignment_group = 'EMAIL_SUPPORT_GROUP_SYS_ID';\n        return;\n    }\n\n    //  CI-based assignment \n    if (current.cmdb_ci) {\n        var ciGR = new GlideRecord('cmdb_ci');\n        if (ciGR.get(current.cmdb_ci)) {\n            if (ciGR.category == 'Database') {\n                current.assignment_group = 'DATABASE_SUPPORT_GROUP_SYS_ID';\n                return;\n            } else if (ciGR.category == 'Server') {\n                current.assignment_group = 'SERVER_SUPPORT_GROUP_SYS_ID';\n                return;\n            }\n        }\n    }\n\n    //  Department-based assignment\n    if (current.caller_id) {\n        var callerGR = new GlideRecord('sys_user');\n        if (callerGR.get(current.caller_id)) {\n            switch (callerGR.department.name) {\n                case 'IT':\n                    current.assignment_group = 'IT_SUPPORT_GROUP_SYS_ID';\n                    break;\n                case 'HR':\n                    current.assignment_group = 'HR_SUPPORT_GROUP_SYS_ID';\n                    break;\n                default:\n                    current.assignment_group = 'GENERAL_SUPPORT_GROUP_SYS_ID';\n            }\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-Generate Knowledge Article for Resolved Incidents/README.md",
    "content": "## Overview\nThis ServiceNow Business Rule automatically creates a Knowledge Article when an Incident is resolved and includes detailed resolution notes.  \nIt helps promote knowledge sharing, reduce repeated issues, and improve ITSM efficiency.\n\n\n## Features\n- Automatically creates a Knowledge Article in the **Draft** state.\n- Extracts content from the Incident's **Resolution Notes**.\n- Prevents duplicate Knowledge Articles by checking for similar issue titles.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-Generate Knowledge Article for Resolved Incidents/script.js",
    "content": "(function executeRule(current, previous) {\n\n    // Only run when the incident moves to Resolved\n    if (current.state == previous.state || current.state != 6) {\n        return;\n    }\n\n    // Make sure we have resolution notes to use for the KB article\n    if (!current.close_notes) {\n        gs.info('Skipping KB creation: No resolution notes found for ' + current.number);\n        return;\n    }\n\n    // Get a clean version of the short description for comparison\n    var issueTitle = current.short_description ? current.short_description.toLowerCase().trim() : '';\n\n    // Check if a similar KB article already exists\n    var kbCheck = new GlideRecord('kb_knowledge');\n    kbCheck.addQuery('short_description', 'CONTAINS', issueTitle);\n    kbCheck.query();\n\n    if (kbCheck.next()) {\n        gs.info('KB already exists for issue: ' + current.number + '. Skipping new KB creation.');\n        return;\n    }\n\n    // Create a new Knowledge Article\n    var kb = new GlideRecord('kb_knowledge');\n    kb.initialize();\n    kb.short_description = current.short_description;\n    kb.text = current.close_notes;\n    kb.workflow_state = 'draft';\n    kb.kb_category = ''; // You can set a default category if needed\n    kb.u_source_incident = current.number; // Optional: track which incident created it\n    kb.insert();\n\n    gs.info('New KB article created from Incident: ' + current.number);\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-approved opened by is approver/ReadMe.md",
    "content": ">**When a new Approval record (sysapproval_approver table) is created**\n\n1. Create a Before Business Rule on the Approval (sysapproval_approver) table.\n\n2. Check if the table of the record being approved is the Requested For table.\n\n3. If it does:\n\n\tVerify whether the Approver (approver) is the same as the Opened by (opened_by) field on the related Requested For record.\n\n\tIf both match:\n\n\t\tAutomatically approve the approval record.\n\n\t\tAdd appropriate approval comments (e.g., “Auto-approved since approver is the requestor (Opened By).”)\n\n\t\tUse setWorkflow(false) to prevent triggering additional workflows or business rules.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-approved opened by is approver/code.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    try {\n        gs.setWorkflow(false);\n        if (!current.sysapproval)\n            return;\n\n        // Get the parent record (RITM / REQ / etc.)\n        var parent = current.sysapproval.getRefRecord();\n        if (!parent || !parent.isValidRecord())\n            return;\n\n        if (parent.getTableName() == \"sc_req_item\") {\n\n            // Load Opened By user record\n            var userGR = new GlideRecord(\"sys_user\");\n            if (!userGR.get(parent.requested_for))\n                return;\n\n\n            // If approver == Opened By → auto-approve\n            if (current.approver == parent.opened_by) {\n                current.state = \"approved\";\n                current.comments = \"Auto-approved as \" + current.getDisplayValue(\"approver\") + \" is the manager of Requested For\";\n                current.update();\n\n                // Also update parent RITM stage to 'Approved'\n                parent.stage = \"approved\";\n                parent.update();\n            }\n        }\n\n    } catch (ex) {\n        gs.error(\"Auto-approval BR error: \" + ex.message);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-assign and notify owners of Affected CIs/BRScript.js",
    "content": "// Business Rule: After Insert or After Update on change_request\n// Purpose: Create Change Tasks for each affected CI and notify its owner\n(function executeRule(current, previous /*null when insert*/) {\n\n    // Query the task_ci table to get all CIs linked to this Change Request\n    var ciRel = new GlideRecord('task_ci');\n    ciRel.addQuery('task', current.sys_id);\n    ciRel.query();\n\n    while (ciRel.next()) {\n        var ci = ciRel.ci_item.getRefRecord();  // Fetch the actual CI record\n\n        if (ci.owner) {  // Proceed only if CI has an owner\n\n            // Check if a Change Task for this CI and owner already exists\n            var existingTask = new GlideRecord('change_task');\n            existingTask.addQuery('change_request', current.sys_id);\n            existingTask.addQuery('cmdb_ci', ci.sys_id);\n            existingTask.addQuery('assigned_to', ci.owner);\n            existingTask.query();\n\n            if (!existingTask.next()) {\n                // Create new Change Task for CI Owner\n                var ct = new GlideRecord('change_task');\n                ct.initialize();\n                ct.change_request = current.sys_id;\n                ct.cmdb_ci = ci.sys_id;\n                ct.assigned_to = ci.owner;\n                ct.short_description = 'Review Change for your CI: ' + ci.name;\n                ct.insert();\n\n                // Trigger a notification event\n                gs.eventQueue('change.ci.owner.notification', ct, ci.owner, current.sys_id);\n            }\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Auto-assign and notify owners of Affected CIs/README.md",
    "content": "Create Change Tasks for each affected CI and notify its owner\n\n1. Create a Business Rule - After Insert/Update Change Request Table\n2. Query the task_ci table to get all CIs linked to this Change Request \n4. Fetch all the actual CI records present in the table\n5. Proceed if CI has a owner, check if a Change Task for this CI and owner already exists\n6. If not existing create a change task for CI owner\n7. Triggers an event to notify the CI owner (email/push).\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AutoApplyTemplateOnRecord/README.md",
    "content": "//Used to automate the application of template when additional fields are required for a process. ex. incident, change etc.\n\n//Template\n// Navigate to System Definition > Templates and create new template\n// Fill Name, Table, short description, and Template fields.\n\n//Business Rule\n// Navigate to System Definition > Business Rule and create a new rule\n// Configure the trigger condition and when to run to meet your business need.\n// Advanced table: Input script and replace place holder variable values.\n// var templateName = \"Your Template Name\"; //replace template name\n// current.applyTemplate(templateName);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AutoApplyTemplateOnRecord/auto_apply_template_on_record.js",
    "content": "// Before Business rule applied on Glide Record Table ex. story[rm_story].\n\n\n(function executeRule(current, previous /*null when async*/) {\n\n\t// Auto Apply Template on Glide Record Table: Story\n\tvar templateName = \"Story creation\"; //replace template name\n    current.applyTemplate(templateName);\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AutoAssignment/Auto Assign Incident.js",
    "content": "// Business Rule: Auto Assign Incident\n// When: Before Insert & Table: Incident\n(function executeRule(current, previous /*null when async*/) {\n    if (current.assigned_to.nil()) {\n        var group = new GlideRecord('sys_user_group');\n        group.addQuery('name', 'IT Support');\n        group.query();\n        if (group.next()) {\n            current.assigned_to = group.getValue('manager');\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AutoAssignment/README.md",
    "content": "This Business Rule runs before an incident is inserted.\nIf no user is assigned, it looks up the \"IT Support\" group and assigns the incident to the group's manager.\nThis ensures that incidents are promptly directed to the appropriate personnel.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AutoCreation of Problem from Incident/README.md",
    "content": "This is After- Business rule Created on Incident Table.\nIn which I have enabled Both insert and Update check Box to true.\nIntially, we will create a field called major Incident(true/false field).\nIf the user checks that and updates an Incident record Immediately a Problem record will be generated with the current values of the Incident\nA pop up message will be displayed as well.\nIf we want we can even put the condition's that only an incident manager can enable this checkBox\nWithout Installing the Major Incident Managment plugin, we can use this. But this will provide limted features for free.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/AutoCreation of Problem from Incident/whenMajorIncidentIsTrueCreateProblem.js",
    "content": "//This is a After-Busiens Rule with insert and update(checked) created on Incident Table\n//condition as Major Incident is True\n(function executeRule(current, previous /*null when async*/ ) {\n\n    \n    var gr = new GlideRecord('problem');\n    gr.initialize();\n    gr.first_reported_by_task = current.getUniqueValue();\n    gr.business_service = current.business_service.getValue();\n    gr.short_description = current.short_description;\n    gr.description = current.description;\n\tgr.urgency = current.urgency;\n\tgr.impact= current.impact;\n    gr.insert();\n\tgs.addInfoMessage('problem number'+gr.number.getDisplayValue());\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automate Role Assignment for New User/README.md",
    "content": "# Overview\nThis snippet for ServiceNow developers automate the process of assigning roles to users based on their department. It helps to simplify user role management, especially useful in organizations where specific departments require predefined access levels. \n\n# How It Works\nIn Business Rule settings within ServiceNow: \n- Trigger: runs the script \"before\" an update to the `sys_user` table when a user’s department changes.\n- Condition: Only triggers when `current.department.changes()` - ensures that the script only runs when the department field is modified.\n- Role Mapping: Uses the `rolesByDepartment` dictionary to assign roles based on the user’s department.\n    \n# Implementation\n\nEdit `rolesByDepartment` to match your organization’s needs.\n\n```\nvar rolesByDepartment = {\n        <department_name> : [role_1, role_2]\n    };\n```"
  },
  {
    "path": "Server-Side Components/Business Rules/Automate Role Assignment for New User/autoRoleAssignment.js",
    "content": "// Script to automatically assign roles based on user department\n(function executeRule(current, previous /*null when async*/) {\n    var department = current.department.getDisplayValue();\n    \n    // Define roles by department\n    var rolesByDepartment = {\n        \"IT\": [\"itil\", \"asset\"],\n        \"HR\": [\"hr_manager\", \"employee\"],\n        \"Finance\": [\"finance_analyst\", \"approver\"]\n    };\n    \n    // Remove existing roles\n    current.roles = [];\n    \n    // Assign new roles based on department\n    if (rolesByDepartment[department]) {\n        rolesByDepartment[department].forEach(function(role) {\n            current.roles.add(role);\n        });\n        current.update();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automated Incident Categorization Based on Keywords/Automated Incident Categorization Based on Keywords.js",
    "content": "(function() {\n    var keywordCategoryMap = {\n        'network': 'Network Issues',\n        'server': 'Server Issues',\n        'database': 'Database Issues',\n        'application': 'Application Issues',\n        'login': 'User Access Issues',\n        'password': 'User Access Issues',\n        'error': 'General Errors',\n        'crash': 'Application Issues',\n        'slow': 'Performance Issues',\n    };\n\n    function categorizeIncident(shortDescription, description) {\n        var combinedText = (shortDescription + ' ' + description).toLowerCase();\n\n        for (var keyword in keywordCategoryMap) {\n            if (combinedText.indexOf(keyword) !== -1) {\n                return keywordCategoryMap[keyword]; \n            }\n        }\n        return 'Uncategorized'; \n\n    if (current.operation() === 'insert') {\n        var category = categorizeIncident(current.short_description, current.description);\n        current.category = category; \n        current.update();\n\n        gs.info('Incident ' + current.number + ' categorized as: ' + category);\n    }\n})();\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automated Incident Categorization Based on Keywords/README.md",
    "content": "**Overview** : This Business Rule will automatically categorize incidents based on the defined keywords when a new incident is created.\n\n**Setting Up the Business Rule**\n\n**Name**: Automated Incident Categorization\n\n**Table**: Incident [incident]\n\n**When**: Before\n\n**Insert**: Checked\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automated SLA Monitoring and Escalation/Automated SLA Monitoring and Escalation.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    if (current.sla_due && current.sla_due <= gs.minutesAgo(30)) {\n        current.priority = '1'; \n        gs.info('Incident ' + current.number + ' escalated due to impending SLA breach.');\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automated SLA Monitoring and Escalation/README.md",
    "content": "**Create a New Business Rule:**\n\n    1. Navigate to System Definition > Business Rules in your ServiceNow instance.\n    \n    2. Click on New to create a new business rule.\n\n**Configure the Business Rule:**\n\n    1. Name: Set a descriptive name (e.g., \"SLA Breach Check\").\n    \n    2. Table: Set to Incident.\n    \n    3. When: Select Before.\n    \n    4. Insert: Check this box.\n    \n    5. Update: Check this box.\n    \n    6. Condition: You can set it to check if the state is \"In Progress\" and if the SLA is about to breach.\n    \n    Use the following condition script:\n    \n    '''current.state == 'In Progress' && current.sla_due <= gs.minutesAgo(30);'''\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatic Group Membership Updates via API/README.md",
    "content": "# Overview\nThis code snippet helps ServiceNow developers manage group memberships automatically by integrating with an external API. It retrieves group membership data from a specified API endpoint and updates user-group relationships in ServiceNow accordingly. \n\nThis is useful for organizations where user groups are managed dynamically in external systems, and developer want a seamless and up-to-date integration with ServiceNow.\n\n# How It Works\nThe script:\n- Fetches API Data: It connects to an external API (specified by the `apiEndpoint` variable) to retrieve the current group membership details.\n- Parses API Response: The response is parsed to extract user information (based on email) and group identifiers.\n- Updates Group Memberships:\n    - For each member in the response, the script queries the `sys_user` table to locate the user in ServiceNow based on their email address.\n    - Once a user is found, the script creates a new record in the `sys_user_grmember` table, associating the user with the appropriate group.\n\n# Implementation\n- Define the `apiEndpoint` URL, replacing `https://your-group-api.com/members` with the actual endpoint from which group membership data will be fetched.\n- Ensure that any necessary authentication for the API is configured, such as API keys or tokens.\n- This script uses email as a unique identifier for users. Adjust `userGR.addQuery('email', member.email)`; if another identifier is needed.\n- Deploy the script as a Business Rule in ServiceNow, setting the appropriate table and conditions under which it should execute. For example, it could run on a schedule or be triggered by a specific update."
  },
  {
    "path": "Server-Side Components/Business Rules/Automatic Group Membership Updates via API/autoGroupMembershipUpdate.js",
    "content": "// Script to update group memberships based on API data\n(function executeRule(current, previous /*null when async*/) {\n    var apiEndpoint = 'https://your-group-api.com/members';\n    var request = new sn_ws.RESTMessageV2();\n    request.setEndpoint(apiEndpoint);\n    request.setHttpMethod('GET');\n\n    var response = request.execute();\n    var responseData = JSON.parse(response.getBody());\n    \n    // Update group memberships\n    responseData.members.forEach(function(member) {\n        var userGR = new GlideRecord('sys_user');\n        userGR.addQuery('email', member.email);\n        userGR.query();\n        \n        if (userGR.next()) {\n            var groupMembership = new GlideRecord('sys_user_grmember');\n            groupMembership.initialize();\n            groupMembership.group = member.group_id;\n            groupMembership.user = userGR.sys_id;\n            groupMembership.insert();\n        }\n    });\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatic Relationship Builder/README.md",
    "content": "# ServiceNow Automatic Relationship Builder  \n**Auto-create CMDB relationships dynamically on CI insert or update**\n\n---\n\n## Overview\n\nThe **Automatic Relationship Builder** ensures that your CMDB stays complete and accurate by automatically creating parent–child relationships between Configuration Items (CIs) whenever they are inserted or updated.  \n\nInstead of manually linking servers, applications, and databases, this Business Rule dynamically establishes **\"Runs on\"**, **\"Depends on\"**, or **\"Connected to\"** relationships based on CI attributes.\n\n---\n\n## Key Highlights\n\nBuilds CMDB relationships automatically  \nEliminates manual linking of dependent CIs  \n\n---\n\n## Use Case\n\nWhen a new **Application CI** is created or discovered and its **host CI (server)** is known,  \nthe script automatically builds a relationship of type **“Runs on::Runs”** between the two.\n\nThis keeps your CMDB up-to-date without human intervention.\n\n---\n\n## Table and Trigger\n\n| Item | Value |\n|------|-------|\n| **Table** | `cmdb_ci_appl` |\n| **Trigger** | After Insert / After Update |\n| **Condition** | `u_host` field is populated |\n| **Purpose** | Create a “Runs on” relationship between host and application |\n\n---\n\n## Script — Business Rule\n\n\nBusiness Rule: Auto Relationship Builder\nTable: cmdb_ci_appl\nWhen: after insert / after update\n\n## Example Input (New CI Record)\n| Field  | Value                        |\n| ------ | ---------------------------- |\n| Name   | Payroll Application          |\n| Class  | Application (`cmdb_ci_appl`) |\n| u_Host | APP-SERVER-01                |\n| Owner  | IT Operations                |\n\n## Example Output (Created Relationship)\n\n| Field  | Value               |\n| ------ | ------------------- |\n| Parent | APP-SERVER-01       |\n| Child  | Payroll Application |\n| Type   | Runs on :: Runs     |\n| Table  | cmdb_rel_ci         |\nRelationship automatically visible in CI Relationship Viewer.\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatic Relationship Builder/relationship.js",
    "content": "/* Scenario :\nWhen a new CI (like an Application Server) is discovered or updated, automatically create “Runs on”, “Depends on”, \nor “Connected to” relationships based on specific patterns.*/\n\n// Table: cmdb_ci_appl | BR: after insert/update\n(function executeRule(current, previous) {\n    if (current.u_host) {\n        var rel = new GlideRecord('cmdb_rel_ci');\n        rel.initialize();\n        rel.parent = current.u_host;     // parent = server\n        rel.child = current.sys_id;      // child = application\n        rel.type = 'Runs on::Runs';      // relationship type\n        rel.insert();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatically Populate Incident with Work Order Number/README.md",
    "content": "# Automatically Populate Incident with Work Order Number, State , OnHold Reason using business rule\n\nThis use case automates the linkage between **Work Orders** and **Incidents** in ServiceNow. When a **Work Order** is created from an **Incident**, the system automatically :\n- Populates the Work Order reference (**u_work_order**) field on the parent Incident  \n- Sets the **Incident State → On Hold**  \n- Updates the **Hold Reason → Awaiting Field Work Order**  \n- Adds a **work note** in the Activity Stream for traceability  \n\nThis ensures a seamless connection between **ITSM** and **Field Service Management (FSM)** processes, allowing  to identify related Work Orders without navigating to related lists.\n\n## Why This Use Case\nOut-of-the-box (OOB) ServiceNow does **not** include a direct Work Order reference field on the Incident form.  \nBy default, users can only view Work Orders under the *Related Tasks → Work Orders* section, which limits quick visibility.  \n\nTo address this gap, a custom reference field (**u_work_order**) was created under the **Related Records** section ( similar to *Problem* or *Parent Incident* ).  \n- Displays the linked Work Order number directly on the Incident form    \n- Prevents manual modification while maintaining clear visibility for agents  \n\n## Process Alignment\nIn standard ITSM workflows, Incidents can be placed **On Hold** for reasons such as *Awaiting Problem*, *Awaiting Change*, or *Awaiting Vendor*.  Since there was no default option for Field Service, this customization introduces a new Hold Reason : **Awaiting Field Work Order**\n\nWhen a Work Order is created from an Incident:\n- The Incident automatically moves to **On Hold**  \n- The **Hold Reason** updates to *Awaiting Field Work Order*  \n- A **work note** logs the Activity Stream  \n\nThis behavior aligns with existing ITSM hold logic for Problem and Change processes\n\n## Prerequisites\n\n#### Ensure FSM Application Plugin is Installed\n- This customization requires the Field Service Management (FSM) plugin to be installed and active in ServiceNow instance  \n- Search for Field Service Management plugin **com.snc.work_management** and install\n- FSM provides the **Work Order [wm_order]** table\n  \n#### Create a Custom Field on the Incident Table\n- Create Custom Reference Field on Incident table with label name **Work Order** [u_work_order] and select Type as **Reference**   \n- Reference to Table Work Order [wm_order]  \n- Save and add this field to the **Incident form** under the **Related Records** section\n\n#### Add Custom Choice in On Hold Reason Field\n- Navigate to hold_reason field on Incident table\n- Configure dictionary and add **Awaiting Field Work Order** with value set as 2 and sequence as 5  \n- Save the new choice to make it selectable in the *Hold Reason* field.\n\n---\n### Creating an Incident\n![Create_Incident](Populate_WorkOrder_Number_1.png)\n\n---\n\n### Work Order Created from Incident\n![WorkOrder_From_Incident](Populate_WorkOrder_Number_2.png)\n\n---\n\n### Incident Updated with Work Order Reference, State On Hold, and Reason Awaiting Field Work Order\n![Populate_WorkOrderNumber_State_onHoldReason](Populate_WorkOrder_Number_3.png)\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatically Populate Incident with Work Order Number/businessRuleFsmUsecase.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    if (!current.initiated_from.nil()) {\n\n        var inc = new GlideRecord('incident');\n        if (inc.get(current.initiated_from)) {\n\n            // Link the Work Order reference to the Incident\n            inc.u_work_order = current.sys_id;\n\n            // Move the Incident to On Hold and set Hold Reason\n            inc.state = 3;                 // 3 = On Hold\n            inc.hold_reason = '2';         // Awaiting Field Work Order (custom choice value created)\n\n            // Add work note \n            inc.work_notes = 'Work Order ' + current.number + ' has been created and Incident moved to On Hold.';\n\n            inc.update();\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatically Throttle Incidents Raised by Same User Within Short Timeframe/README.md",
    "content": "This business rule prevents users from submitting too many incidents in a short time, acting as a rate-limiting mechanism to reduce spam or misuse of the incident form.\n\nWhat It Does:\n-Checks how many incidents the same caller has submitted in the last 10 minutes.\n-If the number of incidents is 3 or more, the rule:\n-Blocks the current incident from being submitted.\n-Displays an error message:\n\"You have submitted too many incidents in a short time. Please wait before submitting more.\"\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Automatically Throttle Incidents Raised by Same User Within Short Timeframe/code.js",
    "content": "(function executeRule(current, gsn, gs) {\n\n    var limit = 3;\n    var windowMins = 10;\n\n    var recentIncidents = new GlideRecord('incident');\n    recentIncidents.addQuery('caller_id', current.caller_id);\n    \n    var now = new GlideDateTime();\n    var cutoff = new GlideDateTime();\n    cutoff.addMinutes(-windowMins);\n    \n    recentIncidents.addQuery('sys_created_on', '>=', cutoff);\n    recentIncidents.query();\n\n    var count = 0;\n    while (recentIncidents.next()) {\n        count++;\n    }\n\n    if (count >= limit) {\n        gs.addErrorMessage(\"You have submitted too many incidents in a short time. Please wait before submitting more.\");\n        current.setAbortAction(true);\n    }\n\n})(current, gsn, gs);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Backup Critical Table Data/README.md",
    "content": "# Overview\nThis ServiceNow script automates backing up critical record data (such as task or incident records) to an external storage solution. Designed to run as a Business Rule, it helps maintain redundancy for sensitive information by copying specific record details to a backup API whenever a record is created or modified.\n\n# How It Works\n- Data Extraction: Collects key record fields (such as `sys_id`, `number`, `short_description`) from `current`.\n- API Call: Sends a `POST` request with record data to an external backup endpoint.\n- Logging: Outputs API response for monitoring.\n\n# Implementation\n1. Update the `setEndpoint` URL to match your backup API endpoint.\n2. Modify the `recordData` with table data structure as needed.\n3. Ensure the Business Rule is triggered on the appropriate conditions (e.g., on record insert/update) in the target table."
  },
  {
    "path": "Server-Side Components/Business Rules/Backup Critical Table Data/backupCriticalTableData.js",
    "content": "// Script to back up critical table data to external storage\n(function executeRule(current, previous /*null when async*/) {\n    var recordData = {\n        sys_id: current.sys_id.toString(),\n        number: current.number.toString(),\n        short_description: current.short_description.toString()\n    };\n    \n    // Call external API to store data\n    var request = new sn_ws.RESTMessageV2();\n    request.setEndpoint('https://your-backup-api.com/backup');\n    request.setHttpMethod('POST');\n    request.setRequestBody(JSON.stringify(recordData));\n    \n    var response = request.execute();\n    gs.info(\"Backup response: \" + response.getBody());\n})(current, previous);\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Block Attachments for specific conditions/Block Attachments.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    gs.addErrorMessage(gs.getMessage(\"You are not authorized to upload attachments.\"));\n    current.setAbortAction(true);\n    return false;\n\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Block Attachments for specific conditions/README.md",
    "content": "This code is used to block attachments for a specific record condition (assignment group, state, etc)"
  },
  {
    "path": "Server-Side Components/Business Rules/CMDB Auto-Relationship Builder/README.md",
    "content": "This code_snippet.js script automatically create a Depends on::Used by relationship when a new server record is created.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/CMDB Auto-Relationship Builder/code_snippet.js",
    "content": "// This code_snippet.js script automatically create a Depends on::Used by relationship when a new server record is created. This business rules run after insert on Server table\n(function executeRule(current) {\n    var rel = new GlideRecord('cmdb_rel_ci');\n    rel.initialize();\n    rel.parent = current.sys_id; // server\n    rel.child = current.u_application; // app reference\n    rel.type = 'Depends on::Used by';\n    rel.insert();\n})(current);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Calculate Incident Duration and Validation/Duration.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\r\n\r\n    /*\r\n\t\tInputs\r\n\t\t1. Opened(opened_at)\r\n\t\t2. Resolved(resolved_at)\r\n\r\n\t\tChecks & Validation\r\n\t\t1. It will check the Date/Time validation whether it Resolved time should be greater than Open time \r\n\t\t2. And it will not calculate the empty values\r\n\r\n\t\tOutputs\r\n\t\t1. If everthing has a right value like the Opened < Resolve Date/Time then Duration will be calculated and populated in the respective field\r\n\t\t2. Negative case if Opened > Resolved the duration will not calculate and it will Abort the action to save the record with Error message.\r\n\t*/\r\n\r\n\t// Getting both the date from the record\r\n    var opened = GlideDateTime(current.opened_at.getDisplayValue());\r\n    var resolved = GlideDateTime(current.resolved_at.getDisplayValue());\r\n\r\n    //If the opened and resolved times are present, calculate the duration\r\n    if (opened && resolved) {\r\n        var difference = GlideDateTime.subtract(opened, resolved);\r\n        if (difference.getYearUTC() >= 1970)\r\n            current.calendar_duration.setValue(difference);\r\n        else {\r\n            current.calendar_duration.setValue(null);\r\n            current.resolved_at.setValue(null);\r\n            current.setAbortAction(true);\r\n            gs.addErrorMessage(\"Incident Resolve date/time must be greater than incident Opened date/time\");\r\n        }\r\n    }\r\n\r\n\r\n\r\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Calculate Incident Duration and Validation/Readme.md",
    "content": "Calculate Incident Duration and Validation.\r\n\r\nScript Type : Business Rule Trigger: before update Table: incident Condition: Resolved Changes or Opened Changes\r\n\r\nGoal : To calculate the duration of a particular record and how much time has been spent on a particular ticket.\r\n\r\nWalk through of code :\r\nSo when the Resolved Changes or Opened Changes in a particular record to calculate the duration will this Business rule will pull those values\r\nAnd then check whether the Opened Data/Time is lesser than the Resolved Date/Time the will calculate the duration \r\nElse it will throw the Error Message and then Abort that action and won't save the record and will clear the values.\r\n\r\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Call JavaScript Probe/Call JavaScript Probe.js",
    "content": "var jspr = new global.JavascriptProbe(gs.getProperty('midserver.name.vmd')); //MID Server name\n\t\tjspr.setName('ShammaProbe77');\n\t\tjspr.setJavascript('var vk = new AttachmentSftpUtils(); res = vk.sftpFile();'); //here comes the MID Server Script Include class and function name\n\t\tjspr.addParameter(\"httpDomain\",gs.getProperty('instance.url')); // ServiceNOW Instance URL\n\t\tjspr.addParameter(\"relativeUrl\" ,url);\n\t\tjspr.addParameter(\"SnowMidUsername\" , gs.getProperty('snow.username')); //ServiceNow MID Server username\n\t\tjspr.addParameter(\"SnowMidPassword\" , gs.getProperty('snow.pass'));//ServiceNow MID Server password\n\t\tjspr.addParameter(\"filename\" , file_name); //name of the file which you want to send to SFTP\n\t\tjspr.addParameter(\"midserverpath\" , gs.getProperty('mid.server.pd.path'));\n\t\tjspr.addParameter(\"sftpTargetServer\" , gs.getProperty('sftp.target.server')); //SFTP SErver IP Address\n\t\tjspr.addParameter(\"sftpTargetUsername\" ,gs.getProperty('sftp.target.server.user'));//SFTP Target username \n\t\tjspr.addParameter(\"sftpTargetPassword\" ,gs.getProperty('sftp.target.server.pass')); //SFTP Target Password\n\t\t\tjspr.addParameter(\"sftpFilePath\" ,gs.getProperty('sftp.file.path')); //SFTP File Path\n\t\t\tjspr.addParameter(\"sftpTargetPort\" ,  gs.getProperty('sftp.file.port')); //SFTP Port Number\n\t\t\tjspr.addParameter(\"MidLogs\" ,  \"true\");\n\t\t\tjspr.create();\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Call JavaScript Probe/README.md",
    "content": "With this script you can call MID Server Script Include via JAVASCRIPT probe. As soon as you run this script it will insert the entry in\necc_queue table and output record will be created against that. In that output record you will see the parameters sent to \"SFTP\"\nThen once the file is successfully copied or moved to SFTP and input record in ecc_queue will be inserted and gives the output whether the file\ntransfer was successful or any errors.\n\nYou can call this script from any Server side scritping\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cancel Incomplete Playbooks on Closure/README.md",
    "content": "🛑 Auto-Cancel Playbooks on Incident Deactivation\n\nScript Type: Business Rule (Server-side)\nTrigger: before update\nTable: incident\nCondition: active changes to false\n\n📌 Purpose\n\nAutomatically cancel all active Playbooks (sys_pd_context) records associated with an incident when that incident is deactivated (e.g., closed or canceled). \n\n🧠 Logic\n\n- Finds all active playbooks (not in completed,canceled) where:\n- input_table is incident (or any other table)\n- input_record matches current incident’s sys_id\n- Cancels each playbook using: sn_playbook.PlaybookExperience.cancelPlaybook(playbookGR, 'Canceled due to the incident closure or cancellation.');\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cancel Incomplete Playbooks on Closure/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Find associated playbooks\n\tvar playbookGR = new GlideRecord('sys_pd_context');\n    playbookGR.addQuery('input_table', 'incident');\n    playbookGR.addQuery('input_record', current.sys_id);\n    playbookGR.addQuery('state', 'NOT IN', 'completed,canceled');\n    playbookGR.query();\n\n\t// Cancel them to avoid hanging context\n    while (playbookGR.next()) {\n        sn_playbook.PlaybookExperience.cancelPlaybook(playbookGR, 'Canceled due to the incident closure or cancellation.');\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Capture Implementation Status of Change Request/Implementation Status of Change Request.js",
    "content": "// Create a before update business rule on change_request table with the below conditions\n//Type is one of Standard, Normal, Emergency\n//AND\n//State changes to Implement\n\n(function executeRule(current, previous /*null when async*/){\n\nvar startDate = current.start_date;\nvar nowTime = new GlideDateTime();\n// Find the difference between nowTime and CR startDate\n\nvar diff = gs.dateDiff(nowTime, startDate, true); // returns the difference in seconds\n\ndiff = Math.floor(diff/ 60); // convert seconds to minutes\n\nif(diff >=15){\n//generate an event to trigger a notification to a particular team that CR has implemented early\ngs.eventQueue('event_name', current, current.number);\n\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Capture Implementation Status of Change Request/Readme.js",
    "content": "1. The business rule will find the Change Request Implementation status. \n2. The task is to identify whether the CR is getting implementated before the planned start date or after the planned start date.\n3. If the actuial start date is 15 minutes or more than 15 minutes prior to planned start date then trigger an event.\n4. To meet this requirement, create a before update busienss rule that identifies when the change request state is moving to implement state.\n5. This functionality helps to track whether the CRs are getting implementated in correct time or not.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Captures the time it took to assign a task/README.md",
    "content": "This script tracks the time it took to assign a task (like an Incident, Change, etc.) by calculating the difference\nbetween when the record was created and when it was assigned (assigned_to was set).\nIt checks if the assigned_to field has changed and is not empty.\nIf it's the first time the record is being assigned (u_assignment_time is empty), it captures the current time.\nIt then calculates the time difference between when the record was created and when it was assigned.\nThis time difference (in minutes) is stored in a custom field u_time_to_assign.\nThe goal is to track how long it took for the record to be assigned after creation\n\n\n## While this is possible to do via Metrics in ServiceNow (https://www.servicenow.com/docs/bundle/xanadu-platform-administration/page/use/reporting/concept/c_SampleFieldValueDurationScript.html), \n## the script is being provided to potentially solve some edge cases.\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Captures the time it took to assign a task/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Only proceed if assigned_to changed AND is not empty/null\n    if (current.assigned_to.changes() && !gs.nil(current.assigned_to)) {\n        \n        gs.info(\"Assigned_to changed and assigned_to is: \" + current.assigned_to);\n\n        // Only set u_assigned_time if empty\n        if (!current.u_assigned_time) {\n            \n            var assignedTime = new GlideDateTime();\n            current.u_assigned_time = assignedTime;\n\n            var createdTime = new GlideDateTime(current.sys_created_on);\n\n            var diffMillis = assignedTime.getNumericValue() - createdTime.getNumericValue();\n            var diffMinutes = diffMillis / (1000 * 60);\n\n            gs.info(\"Time difference in minutes: \" + diffMinutes);\n\n            // Assuming u_time_to_assign is a string field\n            current.u_time_to_assign = diffMinutes.toFixed(2) + \" minutes\";\n            \n            gs.info(\"Set u_time_to_assign to: \" + current.u_time_to_assign);\n        } else {\n            gs.info(\"u_assigned_time already set: \" + current.u_assigned_time);\n        }\n    } else {\n        gs.info(\"Assigned_to not changed or is empty.\");\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cascade Priority Change from Parent to Child Incidents/README.md",
    "content": "This code_snippet.js script automatically updates the priority of all child incidents whenever the parent incident’s priority is modified.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cascade Priority Change from Parent to Child Incidents/code_snippet.js",
    "content": "// Below script automatically updates the priority of all child incidents whenever the parent incident’s priority is modified.\n(function executeRule(current, previous /*null when async*/ ) {\n\n    if (current.priority != previous.priority) {\n        var childInc = new GlideRecord('incident');\n        childInc.addQuery('parent_incident', current.sys_id);\n        childInc.query();\n\n        while (childInc.next()) {\n            childInc.impact = current.impact;\n            childInc.urgency = current.urgency;\n            childInc.work_notes = 'Priority updated automatically from parent incident ' + current.number;\n            childInc.update();\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cascade Problem Worknote to Origin Task/Readme.md",
    "content": "Cascade Problem Worknotes to Origin Task\r\n\r\nScript Type : Business Rule Trigger: after update Table: problem Condition: Work Notes Changes\r\n\r\nGoal : To Notify to that particular record(ticket) when Problem record has updated the worknotes.\r\n\r\nWalk through of code :\r\nSo when there is update/change in the worknotes of a problem record this Business rule will cascade the worknotes to that attached Origin Task record so that it will be update in that particular record as well. In this some validation have been used to avoid unnecessary data cascade, this Business will work only for the open problem, mean this it will exclude the Closed & Resolved State Problem which have been already addressed. So once the worknote is posted it will updated respectively to the origin task.\r\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cascade Problem Worknote to Origin Task/cascade.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\r\n\r\n   /*\r\n   Input\r\n   1. Worknotes changes in Problem\r\n\r\n   Validation & Check\r\n   Exclusion of Closed & Resolved  State Problems\r\n\r\n   Output\r\n   Updatation of Origin Task record worknotes\r\n\r\n   */\r\n\r\n    // Getting the Problem current worknotes\r\n    var workNotes = '';\r\n    if (current.work_notes.changes() && (current.state != 107 && current.state != 106)) {\r\n        workNotes = current.work_notes.getJournalEntry(1).match(/\\n.*/gm).join(\" \");\r\n\t\t\r\n        // To update in the Origin Task field directyl by using the getRefRecord() mehtod.\r\n        var inc_rec = current.first_reported_by_task.getRefRecord();\r\n        inc_rec.work_notes = \"Update on : \" + current.number + workNotes;\r\n        inc_rec.update();\r\n    }\r\n   \r\n\r\n\r\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Change Lead Time Calculations/README.md",
    "content": "Before business rule runs on insert/update - you can set the conditions as you require. Script in change_lead_time_calculations.js\n\nSystem property name:change.leadtime.values | type:string | value -> { \"High\": 5, \"Moderate\" : 3, \"Low\" : 1 } \n"
  },
  {
    "path": "Server-Side Components/Business Rules/Change Lead Time Calculations/change_lead_time_calculations.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\n\tvar index = current.risk.getDisplayValue();\n\tvar property = gs.getProperty('change.leadtime.value');\n\tvar hashMap = JSON.parse(property); \n\tvar answer = hashMap[index];\n    var gDate = new GlideDateTime();\n    var sDate = new GlideDateTime(current.start_date);\n\tvar diffSeconds = gs.dateDiff(gDate,sDate,true); \n    var hours = diffSeconds/3600;\n\tvar days = hours/24;\n\tif(days < answer)\n\t{\n\t\tgs.addErrorMessage('Not enough lead time - atleast '+ answer +' day lead time required for '+ index + ' risk changes.');\n\t\tcurrent.setAbortAction(true);\n\t}\n\t\n})(current, previous);\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Change Risk Assesment mandatory before state change/README.md",
    "content": "This is a code snippet which can be used in a Business rule to prevent the state of Change Request Move from New state when there is no risk Assesment attached to the Change Request.\nTable : change_request When: Before Update: True Conditions : state || CHANGESFROM || New AND state || is not || Cancelled AND type || is one of || Normal,Emergency\nNote: This script is helpful for all the tables and their related tables which has assesmenets enabled, The BR should be created on related table and filters can be added accordingly\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Change Risk Assesment mandatory before state change/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n   //  Assesments once submitted are stored in Assesment Instances Table with record value mapped.\n    var assessmentinstance = new GlideRecord('asmt_assessment_instance');\n    assessmentinstance.addQuery('task_id', current.sys_id); //\n    assessmentinstance.setLimit(1);\n    assessmentinstance.query();// Query the record\n    if (!assessmentinstance.hasNext()) //If there are no assesments \n    {\n        gs.addInfoMessage('Please perform risk assesment before requesting for approval');\n        current.setAbortAction(true);\n        action.setRedirectURL(current);\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Check domain of record against user session/README.md",
    "content": "Type: Business Rule\nWhen: onDisplay\nexample Table: sys_script\n\nThis script gets the domain of the user session, and the domain of the record that you call it from, such as an onLoad Client Script.\nHelp to prevent accidental inserts of scripts in the wrong domain\neg:\nTable: sys_script\n\nfunction onLoad() {\n\tvar currentUserDomain = g_scratchpad.currentDomain;\n\tvar currentRecordDomain = g_scratchpad.recordDomain;\n\t\n\tif (currentUserDomain == 'Global') {\n\t\tg_form.addErrorMessage('You are currently in the Global domain. Editing this record won\\'t create another record');\n\t} else if (currentUserDomain == currentRecordDomain) {\n\t\tg_form.addErrorMessage('You are currently in the same domain as the record you are about to edit: ' +currentUserDomain);\n\t} else {\n\t\tg_form.addErrorMessage('Your current domain is: ' +g_scratchpad.currentDomain +', and the record you are editing is in the ' +g_scratchpad.recordDomain +' domain. If you save any edits, the action will create a new record in your current domain');\n\t}\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Check domain of record against user session/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\n\tvar sessionData = gs.getSession().getCurrentDomainID().toString();\n\t\n\t//set the scratchpad value to the domain of current user session\n\tvar cd = new GlideRecord('domain');\n\tcd.get(sessionData);\n\tif (cd.name.nil()) {\n\t\tg_scratchpad.currentDomain = 'Global';\n\t} else {\n\t\tg_scratchpad.currentDomain = cd.sys_domain.getDisplayValue();\n\t}\n\t\n\t//set the scratchpad value to the domain of the record it's called from\n\tg_scratchpad.recordDomain = current.sys_domain.getDisplayValue();\n\t\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Check for active tickets before inactivating user/Check for active tickets before inactivating user.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n    var tables = [\"incident\", \"change_request\", \"problem\", \"sc_task\", \"task\"]; //tables to check for \n    var tickets = []; //empty array\n\n    for (var i = 0; i < tables.length; i++) { //traversing through all the tables\n        var Findtickets = new GlideRecord(tables[i]); //gliding through each table\n        Findtickets.addActiveQuery(); //active query check\n        Findtickets.addQuery(\"assigned_to\", current.sys_id); //check if the current user who is being inactivated is assigned to user\n        Findtickets.query();\n        while (Findtickets.next()) {\n            tickets.push(Findtickets.number.toString()); //if any tickets are found then add them to the array\n        }\n    }\n    if (tickets.length() != 0) { //if tickets array is not empty then abort the action.\n        gs.info(\"Here are the tickets that were assigned to the user you are trying to inactivate : \" + tickets);\n        gs.addErrorMessage(\"Cannot inactivate user as there are active tickets assigned, Please check logs for all the ticket no.'s.\");\n        current.setAbortAction(true);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Check for active tickets before inactivating user/README.md",
    "content": "This BR is designed to identify all active tickets from the \"tables\" array in ServiceNow and if there are any active tickets are found then the user would not be inactivated.\nThis BR helps administrators easily to find all the active tickets that assigned to the user who is being deactivated so that they can notify the management about this inconsistancy.\n\nAdministrators can either notify the user or the assignment group managers so that the active tickets can be transferred to a different user so that this user can be deactivated."
  },
  {
    "path": "Server-Side Components/Business Rules/Close parent RITM when SC Task is Closed/README.md",
    "content": "This BR is created basically to close the parent requested item when ever the sc task is closed \n1. When: After\n2. Update: true\n3. Add filter condition as \n    1. State is one of closed complete or closed incomplete\n    2. Catalog Item needs to be selected\n4. Advanced check box needs to be checked\n5. Refer to closeParentRITMwhenSCTaskisClosed.js\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Close parent RITM when SC Task is Closed/closeParentRITMwhenSCTaskisClosed.js",
    "content": "/*This BR is created basically to close the parent requested item when ever the sc task is closed \n1. When: After\n2. Update: true\n3. Add filter condition as \n    1. State is one of closed complete or closed incomplete\n    2. Catalog Item needs to be selected\n4. Advanced check box needs to be checked*/\n\n(function executeRule(current, previous /*null when async*/) {\n\tvar gr = new GlideRecord('sc_task'); //Pointing towards SC Task table\n\tgr.addQuery('request_item', current.request_item); //Add query to get parent ritm\n\tgr.addQuery('active', true); //Add query for active records\n\tgr.query();\n\tif(!gr.next()){\n\t\tvar ritm = current.request_item.getRefRecord(); \n\t\tritm.state = 3; //Set state to closed incomplete or closed complete\n\t\tritm.update();\n\t}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Compare two date fields/README.md",
    "content": "This Code will show the number of days difference between 2 date like Start Date and End Date.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Compare two date fields/compareTwoDateFields.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    var sTime = current.start_date.getDisplayValue(); \n\tvar eTime = current.end_date.getDisplayValue(); \n\n\tgs.addInfoMessage(\"Start Date=\"+ sTime + \" End Date=\"+eTime);\n\n    var dur = gs.dateDiff(sTime, eTime);\n\n    var dateDiff = new GlideDuration(dur);\n\n    var d = dateDiff.getDayPart();\n\n    gs.addInfoMessage(\"Number of Days :\" + d);\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Attachment INC to Case/README.md",
    "content": "Copy attahements from Sc task  table to case table, using custom function to avaoid duplicate copies.\n\nAttachment file will have one or more entries in the sys_attachment_doc table.\nWhen we upload an attachment file to ServiceNow, a record is created in the Attachments table with some metadata, including the file name,\ncontent type, and the size of the attached file.\nthe sys_attachment record essentially just contains metadata about the attachment. \nThe actual binary data of the file is split into chunks, which are then saved into the Data field of the Attachment Documents table. \nThe Attachment Documents table also contains a reference field (sys_attachment), which points to the parent record in the Attachments table. \n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Attachment INC to Case/copyAttachement.js",
    "content": "\n        if (current.table_name == 'sc_task') \n            var id = current.table_sys_id;\n        var gr = new GlideAggregate('sn_customerservice_technology_services');\n        gr.addQuery('sc_task.sys_id', id);\n        gr.query();\n        if (gr.next()) {\n            var grSA = new GlideAggregate('sys_attachment');\n            grSA.addQuery(\"table_name\", 'sc_task');\n            grSA.addQuery(\"table_sys_id\", id);\n            grSA.addQuery('file_name', current.file_name);\n            grSA.query();\n            if (grSA.next()) {\n            if (grSA.getRowCount() >1) {\n\t\t\tgrSA.deleteRecord('id');\n\t\t\t\t}\n\t\t\t\t\telse {\t\t\t\t\t\n                        var grSAafter = new GlideAggregate('sys_attachment');\n                        grSAafter.initialize();\n                        grSAafter.file_name = current.file_name;\n                        grSAafter.content_type = current.content_type;\n                        grSAafter.compressed = current.compressed;\n                        grSAafter.table_name = 'sn_customerservice_technology_services';\n                        grSAafter.size_bytes = current.size_bytes;\n                        grSAafter.size_compressed = current.size_compressed;\n                        grSAafter.table_sys_id = gr.sys_id;\n                        var grSAafterRec = grSAafter.insert();\n\n                        var grSAafterDoc = new GlideAggregate('sys_attachment_doc');\n                        grSAafterDoc.addQuery('sys_attachment', current.sys_id);\n                        grSAafterDoc.query();\n                        while (grSAafterDoc.next()) {\n                           \n                          var grSAafterDocCopy = new GlideAggregate('sys_attachment_doc');\n                            grSAafterDocCopy.initialize();\n                            grSAafterDocCopy.sys_attachment = grSAafterRec;\n                            grSAafterDocCopy.position = grSAafterDoc.position;\n                            grSAafterDocCopy.length = grSAafterDoc.length;\n                            grSAafterDocCopy.data = grSAafterDoc.data;\n                            grSAafterDocCopy.insert();\n                        }\n                    }\n\t\t\t\t\n\t\t\t\n                }\n            }\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Attachment on Email/README.md",
    "content": "This Business Rule to send physical copy of attachment before insert on given table\n\nEg. Usecase- To send physical copy of attachment on email.\n\nWhen to run - Before Insert\nTable name- sys_email\n\nPlease note that correct Attachment sys_id to be mentioned in the code from Attachment table which need to be attached on email.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Attachment on Email/copyAttachment.js",
    "content": "\nvar grSysAtt = new GlideRecord('sys_attachment');\ngrSysAtt.get(''); //mention your attachment sys_id from sys_attachment table\nvar content = new GlideSysAttachment().getContentStream(grSysAtt.sys_id);\nnew GlideSysAttachment().writeContentStream(current, grSysAtt.getValue('file_name'), grSysAtt.getValue('content_type'), content);\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Comments from RITM to SCTASK Vice versa/README.md",
    "content": " End user recent comments on RITM to be copied as task worknotes to any catalog tasks which is not Closed Complete/Closed Skipped/Cancelled so the assigne aware of recent updates of End users.\n\n \n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Comments from RITM to SCTASK Vice versa/copyCommentsfromRitmToSctask.js",
    "content": "// Sample code to Copy RITM additional Comments /  user visible comments to SCtask worknotes \nvar diff, tcomnt, tcmnt1, ritmcomnt1, ritmcomnt;\n    ritmcomnt = current.comments.getJournalEntry(1);\n    var reg_exp = new RegExp('\\n');\n    var i = ritmcomnt.search(reg_exp);\n    if (i > 0) {\n        ritmcomnt1 = ritmcomnt.substring(i + 1, ritmcomnt.length);\n    }\n\n    // Get the list of open sctasks for the RITM\n\tvar ritm_gr = new GlideRecord('sc_task');\n    ritm_gr.addQuery('request_item', current.sys_id);\n    // Exclude any closed tasks\n\tritm_gr.addQuery('state', '!=', '3');\n    ritm_gr.addQuery('state', '!=', '4');\n    ritm_gr.addQuery('state', '!=', '7');\n    ritm_gr.query();\n\n    while (ritm_gr.next()) {\n        tcmnt1 = ritm_gr.comments.getJournalEntry(1);\n\n        //Remove timestamp and name from additional comment\n        var i1 = tcmnt1.search(reg_exp);\n        if (i1 > 0) {\n            tcomnt = tcmnt1.substring(i1 + 1, tcmnt1.length);\n        }\n        diff = ritmcomnt1.indexOf(tcomnt);\n\n        if (diff == -1) // If No match found\n        {\n\t\t\tritm_gr.work_notes = \"Additional Comment from RITM: \" + ritmcomnt1.trim();\n            ritm_gr.update();\n        }\n    }\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy MRVS to SC Task/readme.md",
    "content": "Use the script as Business Rule script on Catalog Task table which runs After Insert and will copy all MRVS values from RITM to Catalog Task for quick actions at the Catalog Task itself for reviewing and getting the work completed.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy MRVS to SC Task/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // Copies the values from a multi-row variable set into a variable so we can view the values on a catalog task.\n    var variables = [];\n    var catItem = current.request_item.cat_item.toString();\n    var variableSets = [];\n    var getVariableSets = new GlideRecord('io_set_item');\n    getVariableSets.addQuery('sc_cat_item', catItem);\n    getVariableSets.query();\n    while (getVariableSets.next()) {\n        variableSets.push(getVariableSets.getValue('variable_set'));\n    }\n    var getCatalogVariables = new GlideRecord('item_option_new');\n    var qry = 'cat_item=' + catItem +\n        '^active=true' +\n        '^NQvariable_set.sys_idIN' + variableSets.toString() +\n        '^active=true';\n    getCatalogVariables.addQuery(qry);\n    getCatalogVariables.query();\n    while (getCatalogVariables.next()) {\n        variables.push(getCatalogVariables.getValue('sys_id'));\n    }\n    var variablesCount = variables.length;\n    var currentTaskID = current.sys_id.toString();\n    for (var i = 0; i < variablesCount; i++) {\n        var getTaskVars = new GlideRecord('sc_item_variables_task');\n        getTaskVars.addQuery('task', currentTaskID);\n        getTaskVars.addQuery('variable', variables[i]);\n        getTaskVars.setLimit(1);\n        getTaskVars.query();\n        if (!getTaskVars.hasNext()) {\n            getTaskVars.initialize();\n            getTaskVars.setValue('task', currentTaskID);\n            getTaskVars.setValue('variable', variables[i]);\n            getTaskVars.insert();\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Worknotes/read.md",
    "content": "Copy work notes to parent \nChecks if work notes have changed in the current record.\nIf yes, it copies the latest journal entry to the parent’s work notes.\nSets the updated flag to true.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy Worknotes/script.js",
    "content": "/Copy work notes to parent \n/* Checks if work notes have changed in the current record.\nIf yes, it copies the latest journal entry to the parent’s work notes.\nSets the updated flag to true.*//\n(function executeRule(current, previous) {\n    if (!current.parent) return;\n \n    var parent = current.parent.getRefRecord();\n    if (!parent.isValidRecord()) return;\n\n    var updated = false;\n\t\n\t\n    if (current.work_notes.changes()) {\n        parent.work_notes = current.work_notes.getJournalEntry(1);\n        updated = true;\n    }\n\t\n    if (updated) {\n        parent.update();\n    }\n})(current, previous);\t\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy attachments from idea to demand/README.md",
    "content": "Code will copy attachments from idea to demand\n\nName: Copy attachments to demand\n\nTable: Idea\n\nWhen: Async\n\nFilter conditions: demand changes and demand is not empty\n\nUpdate: true\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy attachments from idea to demand/copy attach from idea to demand.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Chris E; 26 Aug 2019;\n\t// Copy the attachments from the idea record to the demand record on creation/change\n\t\t\n\ttry {\n    \n\t\tnew GlideSysAttachment().copy(current.getTableName(), current.getValue('sys_id'), current.demand.getRefRecord().getValue('sys_class_name'), current.getValue('demand'));\n    \n\t} catch(e) {\n\t\tif(gs.isInteractive() && gs.hasRole('admin')) {\n\t\t\tgs.addInfoMessage('Copy attachments to demand - '+ e.message);\n\t\t}\n\t\tgs.error(e.message);\n\t}\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy details to Request/Move Sc_task Assign group and assigne to Request.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    var req = current.request.getRefRecord();\n\n    // Check if the REQ record exists\n    if (req.isValidRecord()) {\n        // Check if this is the first task for the REQ\n        var firstTaskForReq = new GlideRecord('sc_task');\n        firstTaskForReq.addQuery('request', req.getUniqueValue());\n        firstTaskForReq.orderBy('sys_created_on');\n        firstTaskForReq.setLimit(1); // Limit to the first task\n        firstTaskForReq.query();\n\n        if (firstTaskForReq.next() && firstTaskForReq.getUniqueValue() == current.getUniqueValue()) {\n            // Copy the Assignment Group and Assigned To fields\n            req.setValue('assignment_group', current.getValue('assignment_group'));\n            req.setValue('assigned_to', current.getValue('assigned_to'));\n\n            // Update the REQ record\n            req.update();\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy details to Request/README.md",
    "content": "Script: Copy the Assignment group and Assign the details of sc_task to the Request table.\n\nThis script automates the process of assigning values from the first sc_task in a REQ to the parent REQ record. This is useful for keeping the request record in sync with its initial task(If multiple tasks are created for one request), allowing other workflows to use these values directly from the request.\n\nPurpose of the Script:\n\nEnd User Notification on Request Closure: When closing a service request, this script ensures that the Assignment Group and Assigned To details are copied to the REQ record, providing clarity for end users in the request closed notification. This is especially helpful to users who need to know who worked on their request.\n\nTracking in Survey Table: By maintaining the Assignment Group and Assigned To details on the REQ record, these details can be linked to surveys. This helps capture accurate data on the responsible group or individual when processing surveys and feedback, particularly if the survey process involves rewriting or transferring details.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy fields from Employee from/README.md",
    "content": "## Copy response from Employee form to Specific cases/tasks/records\n\n> This BR script can be used when auto field mapping feature on Employee forms is not fitting your requirements\n\n### Usage scenarios\n\n-   There are multiple Active employee forms associated with a single user at a time\n-   Form field to be copied to different cases/tasks/records\n\n### Implementation Guideline\n\n-   Field name on employee form should be same as the field name on the record you want to copy. This is to avoid hard coding of field names/mappings in code and would help to scale the script for any employee form.\n\nex:\n\n-   Case field name: u_employee_name, Employee form field name: u_employee_name\n\n### Important Note\n\nWhen a same survey (Employee form) is to be generated for a user who is already assigned to same employee form, then instead of creating new survey instance, OOB, system attaches/associates the existing active survey to the HR tasks. Update the below mentioned business rule to fix this issue.\n\n**BR Name**: Create survey instance\n\n**TODO**: Replace OOB code on line 9 with below code.\n\n```JS\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tif(!current.survey || current.survey != previous.survey)\n\t\tif(current.survey_instance) {\n\t\t\tcurrent.survey_instance.state = 'canceled';\n\t\t}\n\n\tif (current.survey)\n\t\tcurrent.survey_instance = (new sn_assessment_core.AssessmentCreation()).createOrGetAssessment(current.survey, \"\", current.assigned_to);\n})(current, previous);\n\n```\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy fields from Employee from/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t/**\n\t * This script is user to copy employee field value to any record in case\n\t * This can be used when Employee form auto mapping feature cant be used\n\t * - There are multiple Active employee forms associated with a single user at a time\n\t * - Form field to be copied to different cases/tasks/records\n\t *\n\t */\n\n\tvar surveyResultTable = \"asmt_metric_result\";\n\t// Employee form surveys linked to Tasks using \"survey instance\" field\n\tvar parentGR = new GlideRecord(current.parent.sys_class_name.toString());\n\tparentGR.get(current.parent.sys_id.toString());\n\n\tvar resultGR = new GlideRecord(surveyResultTable);\n\tresultGR.addQuery(\"instance\", current.survey_instance.sys_id.toString());\n\tresultGR.query();\n\twhile (resultGR.next()) {\n\t\tvar dataType = resultGR.metric.datatype.toString();\n\t\tvar fieldName = resultGR.metric.name.toString();\n\n\t\tvar value = \"\";\n\t\tvar stringValue = resultGR.string_value.toString();\n\t\tvar finalValue = \"\";\n\n\t\t// Survey response value is saved in different field depending on metric datatype\n\t\t// This section may need to be updated based on different metric type you add in your survey\n\t\tswitch (dataType) {\n\t\t\tcase \"choice\":\n\t\t\t\tvar tableName = current.parent.sys_class_name.toString();\n\t\t\t\t// Get the exact choice \"value\" to avoid issues with difference in Label and value of field\n\t\t\t\tvalue = \"\";\n\t\t\t\tif (stringValue /*label*/ && fieldName /*element*/ && tableName /*name*/) {\n\t\t\t\t\tvar choiceGR = new GlideRecord(\"sys_choice\");\n\t\t\t\t\tchoiceGR.addQuery(\"name\", tableName);\n\t\t\t\t\tchoiceGR.addQuery(\"element\", fieldName);\n\t\t\t\t\tchoiceGR.addQuery(\"label\", stringValue);\n\t\t\t\t\tchoiceGR.addQuery(\"inactive\", false);\n\t\t\t\t\tchoiceGR.query();\n\t\t\t\t\tif (choiceGR.next()) {\n\t\t\t\t\t\tvalue = choiceGR.value.toString();\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (value !== false) {\n\t\t\t\t\tfinalValue = value;\n\t\t\t\t} else {\n\t\t\t\t\tfinalValue = stringValue;\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t\tcase \"reference\":\n\t\t\t\tfinalValue = resultGR.reference_value.toString();\n\t\t\t\tbreak;\n\t\t\tcase \"date\":\n\t\t\t\t// finalValue = stringValue;\n\t\t\t\tvar gdt = new GlideDateTime(stringValue);\n\t\t\t\tfinalValue = gdt;\n\t\t\t\tbreak;\n\t\t\tcase \"string\":\n\t\t\t\tfinalValue = stringValue;\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tfinalValue = stringValue;\n\t\t\t\tbreak;\n\t\t}\n\n\t\t// Setting defaults values for questions based on Default value scenarios\n\n\t\t/**\n\t\t *\n\t\t * Employee Form: <Employee Form Name>\n\t\t * Question: <Question Label>\n\t\t * Name: <Question name>\n\t\t * Default Values scenario: <Condition when to set this default value>\n\t\t * Default Value: <Default value to set>\n\t\t *\n\t\t * */\n\t\tif (fieldName == \"u_employee_tile\" && finalValue == \"\") {\n\t\t\tparentGR.setValue(\"u_employee_name\", \"Intern\");\n\t\t}\n\n\t\tparentGR.setValue(fieldName, finalValue);\n\t\t// parentGR[fieldName] = finalValue; (this is the same as above)\n\t}\n\n\tparentGR.update();\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy latest comment from RITM to SCTASK/CopyComments.js",
    "content": "//Script to update comments on RITM.\n\nvar ritmGr = new GlideRecord('sc_req_item');\nif(ritmGr.get(current.request_item.sys_id))// Use current sys_id of ritm to pull the comments\n{\n\nritmGr.comments=current.comments.getJournalEntry(1);// This gets the latest comment added.\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy latest comment from RITM to SCTASK/README.md",
    "content": "This simple snap code helps to copy latest comment of RITM to SCTASK.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy worknotes from SCTASK to RITM comments/README.md",
    "content": "Sample code snippet to copy the latest task worknotes comments to additional comments of related RITM record.\nSo the requester aware of recent updates of tasks.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Copy worknotes from SCTASK to RITM comments/sctaskToRitmAdditionalComments.js",
    "content": "var difference, task_wrk_notes, task_add_cmnt, rAddCmnt, ritm_add_cmnt;\n    task_add_cmnt = current.comments.getJournalEntry(1);\n\n    //Remove timestamp and name from additional comment\n    var reg_exp = new RegExp('\\n');\n    var i = task_add_cmnt.search(reg_exp);\n    if (i > 0) {\n        task_wrk_notes = task_add_cmnt.substring(i + 1, task_add_cmnt.length);\n    }\n\n    var grSRI = new GlideAggregate('sc_req_item');\n    grSRI.addQuery('sys_id', current.parent);\n    grSRI.query();\n\n    if (grSRI.next()) {\n        ritm_add_cmnt = grSRI.comments.getJournalEntry(1);\n\n        //Remove timestamp and name from additional comment\n        var i1 = ritm_add_cmnt.search(reg_exp);\n        if (i1 > 0) {\n            rAddCmnt = ritm_add_cmnt.substring(i1 + 1, ritm_add_cmnt.length);\n        }\n        difference = task_wrk_notes.indexOf(rAddCmnt);\n\n        if (difference == -1) // If No match found\n        {\n            //Originally prefixed, but no longer needed:  \"Comment from IT user working on your request: \"\n\t\t\tgrSRI.comments = task_wrk_notes.trim();\n            grSRI.update();\n        }\n    }\n"
  },
  {
    "path": "Server-Side Components/Business Rules/CopyAttachmentsFromApprovalToChange/CopyAttachmentsApprovalToChange.js",
    "content": "// Copy attachments from Approval Record to Change record upon Approval / Rejection\n\n/*\n  This script should be used in an Advanced - After - Insert/Update Business Rule with conditions \n  state :: changes to :: Approved\n  state :: changes to :: Rejected\n*/\n\n(function executeRule(current, previous /*null when async*/ ) {\n    var approval = current.getValue('sysapproval');\n    var chgGr = new GlideRecord('change_request');\n    // Check if the approval record is really a change record or not, if yes copy the Attachments\n    if (chgGr.get(approval)) {\n        var attachment = new GlideSysAttachment();\n        var ids = attachment.copy('sysapproval_approver', current.sys_id, 'change_request', approval);\n    }\n\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/CopyAttachmentsFromApprovalToChange/README.md",
    "content": "Copy attahements from Approval Record to corresponding change record.\n\nThis BR utilizes GlideSysAttachment API to copy all the attachments at a time. And there is no duplicate prevention enabled as Approval record is generally either approved or rejected one time.\n\nTo utilize this script, create an Advanced - After - Insert/Update Business Rule with conditions \n  state :: changes to :: Approved\n  state :: changes to :: Rejected\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Count Associated Incidents in Problem/README.md",
    "content": "<h2> Count the number of associated incidents in Problem Form </h2>\n<h3>Use - Case: </h3>\n<h4>Created a field in the Problem form which shows the number of incident records associated with the same problem. This makes it easy for the helpdesk to find out if there are any linked incidents without scrolling down.</h4>\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Count Associated Incidents in Problem/script.js",
    "content": "(function executeRule(current, previous) {\n\n\t\n\tvar gr = new GlideRecord('problem');\n\tgr.addQuery('sys_id', current.problem_id);\n\tgr.query();\n\tif(gr.next())\n\t\t{\n\t\t\tgr.u_associated_incidents += 1;\n\t\t\tgr.update();\n\t\t\t\n\t\t}\n\t\n\t\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Create a copy of incident in another servicenow instance/README.md",
    "content": "# Create incident copy\n\n**Use case** : Whenever a new incident is created in servicenow production instance, a copy of that incident should be created in backup instance.\n\n*info* : This method is to achieve the above use-case just with business rule and without creating a record in sys_rest_message table.\n\n**Solution** : Create a `After` business rule on incident table with `insert` checkbox checked. Follow the script present in [script.js](script.js) \n"
  },
  {
    "path": "Server-Side Components/Business Rules/Create a copy of incident in another servicenow instance/script.js",
    "content": "//This is after business rule in production instance\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar body = {\"short_description\":current.short_description.toString(),\"description\":current.short_description.toString()}; //more fields can be added to body\n\t\n\tvar req = new sn_ws.RESTMessageV2();\n\treq.setEndpoint(\"https://<your-instance-name>.service-now.com/api/now/table/incident\");    //give backup(non-prod) instance name\n\treq.setHttpMethod(\"POST\");\n\treq.setBasicAuth(\"<username>\",\"<password>\");   //username and password of a user of backup(non-prod) instance. User must have rest_service and itil role\n\treq.setRequestHeader(\"Content-Type\",\"application/json\");\n\treq.setRequestBody(JSON.stringify(body));\n\t\n\tvar response = req.execute();\n\t\n\tvar body = response.getBody();\n\tvar status = response.getStatusCode();\n\n  gs.log(\"Response body is ---- \" +body);  //response body is logged for verification\n  gs.log(\"Status code is ---- \" +status);  //status code is logged. Usually 200 or 201 is success\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Create catalog task for each row of MRVS/README.md",
    "content": "This code snippet will help you to generate tasks automatically for each row of MRVS present on RITM. This can be achieved by using a Before BR on RITM.\nSpecifications for Business rule will be:\n1. When = Before\n2. Insert = true\n3. Advanced = true\n4. Then write the script provided in script.js into the script section of BR.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Create catalog task for each row of MRVS/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\n\nvar mrvs = current.variables.test_var; //get MRVS. Here 'test_var' is my mrvs name.\nvar total = mrvs.getRowCount(); // get the row count of mrvs\nfor(var i =0;i<total;i++)\n{\n  var scTask = new GlideRecord('sc_task');\n  scTask.initialize();\n  scTask.request_item = current.sys_id;\n  scTask.short_description = mrvs[i].name; //set the short description of task with mrvs data. Here 'name' is a variable on my mrvs.\n  scTask.insert();\n}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Create choice sets if required for new choices/README.md",
    "content": "Type: Business Rule\nWhen: Before\nInsert: true\n\nThis script checks whether there is a choice set already created/available for the table:element pairing of a new sys_choice.\nIf there is, it returns without doing anything.\nIf there is not, it creates the choice set. This prevents the DELETE behaviour that occurs when a new choice is created that doesn't have a valid choice set available to be associated to."
  },
  {
    "path": "Server-Side Components/Business Rules/Create choice sets if required for new choices/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\n\tvar table = current.name;\n\tvar el = current.element;\n\t\n\tvar scs = new GlideRecord('sys_choice_set');\n\tscs.addQuery('name', table);\n\tscs.addQuery('element', el);\n\tscs.query();\n\t\n\tif (scs.next()) {\n\t\treturn;\n\t}\n\t\n\tscs.initialize();\n\tscs.name = table;\n\tscs.element = el;\n\tscs.insert();\n\t\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Create comment on referenced record/README.md",
    "content": "**Business Rule**\n\n Script which allows adding comments to referenced records after some insert or update was made. In this example, after changing name on a Configuration Item, all incident which have that Ci  assigned will get comment about that change. You can change the script to different table, query and conditions to fit your needs.\n\n **Example configuration of  Business Rule** \n\n![Coniguration](ScreenShot_1.PNG)\n\n**Example effect of execution**\n\n![Effect](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Create comment on referenced record/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    //Script to create comment on referenced records in case of update\n    //In this example, we are creating a comment on an incident when the name of CI was changed\n\n    //Query all incident which have Configuration item set to current one\n    var grInc = new GlideRecord('incident');\n    grInc.addQuery('cmdb_ci', current.sys_id);\n    grInc.query();\n\n    //Go through all incidents on query list\n    while (grInc.next()) {\n\n        //Add new comment about changed name of CI\n        grInc.work_notes = 'Name was changed from: ' + previous.getValue('name') + ' to: ' + current.getValue('name') + ' on related Configuration Item.';\n        grInc.update();\n\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cross-Table Dependency Analyzer/businessrule.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    var analyzer = new CrossTableDependencyAnalyzer();\n    var deps = analyzer.getDependencies(current);\n\n    if (deps.length > 0) {\n        var messages = deps.map(function(d){ return d.table + ': ' + d.number + ' (' + d.state + ')'; });\n        current.comments = 'Potential impact on related records:\\n' + messages.join('\\n');\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cross-Table Dependency Analyzer/readme.md",
    "content": "The Cross-Table Dependency Analyzer is a custom ServiceNow solution designed to dynamically detect and analyze dependencies across multiple tables such as Incidents, Problems, Changes, and Configuration Items (CIs). This tool ensures that updates to one record do not inadvertently impact related records across the system, providing better visibility, risk mitigation, and proactive management.\n\nUnlike out-of-the-box (OOB) impact analysis, this solution is fully customizable, real-time, and developer-driven, making it suitable for organizations with complex IT processes or interdependent services.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Cross-Table Dependency Analyzer/scriptInclude.js",
    "content": "var CrossTableDependencyAnalyzer = Class.create();\nCrossTableDependencyAnalyzer.prototype = {\n    initialize: function() {},\n\n    // Get related records for a CI or task\n    getDependencies: function(record) {\n        var dependencies = [];\n\n        if (!record) return dependencies;\n\n        var ciId = record.cmdb_ci; // for incidents or changes\n        if (ciId) {\n            // Find active incidents for this CI\n            var inc = new GlideRecord('incident');\n            inc.addQuery('cmdb_ci', ciId);\n            inc.addActiveQuery();\n            inc.query();\n            while (inc.next()) {\n                dependencies.push({\n                    table: 'incident',\n                    number: inc.number.toString(),\n                    state: inc.state.toString()\n                });\n            }\n\n            // Find active changes for this CI\n            var chg = new GlideRecord('change_request');\n            chg.addQuery('cmdb_ci', ciId);\n            chg.addActiveQuery();\n            chg.query();\n            while (chg.next()) {\n                dependencies.push({\n                    table: 'change_request',\n                    number: chg.number.toString(),\n                    state: chg.state.toString()\n                });\n            }\n\n            // Find problems linked to this CI\n            var prb = new GlideRecord('problem');\n            prb.addQuery('cmdb_ci', ciId);\n            prb.addActiveQuery();\n            prb.query();\n            while (prb.next()) {\n                dependencies.push({\n                    table: 'problem',\n                    number: prb.number.toString(),\n                    state: prb.state.toString()\n                });\n            }\n        }\n\n        return dependencies;\n    },\n\n    type: 'CrossTableDependencyAnalyzer'\n};\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Currency conversion to USD/currenct_Converstion_to_USD.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Extract the first 3 characters of the budget currency code (e.g., \"INR\", \"EUR\")\n    var currencyCode = current.budget_currency ? current.budget_currency.toString().substring(0, 3) : '';\n\n    // Convert the annual budget value to a float\n    var amount = parseFloat(current.annual_budget);\n\n    // Validate input: If currency code is missing or amount is not a valid number, clear the USD field and exit\n    if (!currencyCode || isNaN(amount)) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    // If the currency is already USD, no conversion needed — store the original amount\n    if (currencyCode === 'USD') {\n        current.u_annual_budget_usd = amount;\n        return;\n    }\n\n    // Check if the currency exists in the fx_currency table\n    var currencyGR = new GlideRecord('fx_currency');\n    currencyGR.addQuery('code', currencyCode);\n    currencyGR.query();\n\n    // If currency is not found, clear the USD field and exit\n    if (!currencyGR.next()) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    // Get the latest exchange rate for the selected currency from fx_rate table\n    var fxGR = new GlideRecord('fx_rate');\n    fxGR.addQuery('currency.code', currencyCode);\n    fxGR.orderByDesc('sys_updated_on'); // Sort by most recent update\n    fxGR.setLimit(1); // Limit to the latest record\n    fxGR.query();\n\n    // If no exchange rate found, clear the USD field and exit\n    if (!fxGR.next()) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    var rate = parseFloat(fxGR.getValue('rate')); // Exchange rate for selected currency\n\n    // Get the latest exchange rate for USD from fx_rate table\n    var fxGR1 = new GlideRecord('fx_rate');\n    fxGR1.addQuery('currency.code', 'USD');\n    fxGR1.orderByDesc('sys_updated_on'); // Sort by most recent update\n    fxGR1.setLimit(1); // Limit to the latest record\n    fxGR1.query();\n\n    // If no USD exchange rate found, clear the USD field and exit\n    if (!fxGR1.next()) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    var usdRate = parseFloat(fxGR1.getValue('rate')); // USD base rate\n\n    // Perform conversion only if both rates are valid and non-zero\n    if (!isNaN(rate) && !isNaN(usdRate) && rate !== 0) {\n        var convertedAmount = (amount / rate) * usdRate; // Convert to USD\n        current.u_annual_budget_usd = convertedAmount; // Store the converted value\n    } else {\n        gs.info(\"Invalid exchange rate values\");\n        current.u_annual_budget_usd = '';\n    }\n\n})(current, previous);\n``\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Currency conversion to USD/currency conversion usd.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Extract the first 3 characters of the budget currency code (e.g., \"INR\", \"EUR\")\n    var currencyCode = current.budget_currency ? current.budget_currency.toString().substring(0, 3) : '';\n\n    // Convert the annual budget value to a float\n    var amount = parseFloat(current.annual_budget);//annual_budget is filed where we can enter amount\n\n    // Validate input: If currency code is missing or amount is not a valid number, clear the USD field and exit\n    if (!currencyCode || isNaN(amount)) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    // If the currency is already USD, no conversion needed — store the original amount\n    if (currencyCode === 'USD') {\n        current.u_annual_budget_usd = amount;\n        return;\n    }\n\n    // Check if the currency exists in the fx_currency table\n    var currencyGR = new GlideRecord('fx_currency');\n    currencyGR.addQuery('code', currencyCode);\n    currencyGR.query();\n\n    // If currency is not found, clear the USD field and exit\n    if (!currencyGR.next()) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    // Get the latest exchange rate for the selected currency from fx_rate table\n    var fxGR = new GlideRecord('fx_rate');\n    fxGR.addQuery('currency.code', currencyCode);\n    fxGR.orderByDesc('sys_updated_on'); // Sort by most recent update\n    fxGR.setLimit(1); // Limit to the latest record\n    fxGR.query();\n\n    // If no exchange rate found, clear the USD field and exit\n    if (!fxGR.next()) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    var rate = parseFloat(fxGR.getValue('rate')); // Exchange rate for selected currency\n\n    // Get the latest exchange rate for USD from fx_rate table\n    var fxGR1 = new GlideRecord('fx_rate');\n    fxGR1.addQuery('currency.code', 'USD');\n    fxGR1.orderByDesc('sys_updated_on'); // Sort by most recent update\n    fxGR1.setLimit(1); // Limit to the latest record\n    fxGR1.query();\n\n    // If no USD exchange rate found, clear the USD field and exit\n    if (!fxGR1.next()) {\n        current.u_annual_budget_usd = '';\n        return;\n    }\n\n    var usdRate = parseFloat(fxGR1.getValue('rate')); // USD base rate\n\n    // Perform conversion only if both rates are valid and non-zero\n    if (!isNaN(rate) && !isNaN(usdRate) && rate !== 0) {\n        var convertedAmount = (amount / rate) * usdRate; // Convert to USD\n        current.u_annual_budget_usd = convertedAmount; // Store the converted value\n    } else {\n        gs.info(\"Invalid exchange rate values\");\n        current.u_annual_budget_usd = '';\n    }\n\n})(current, previous);\n``\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Currency conversion to USD/readme.md",
    "content": "This script is designed to automatically convert the value of the Annual Budget field (annual_budget) from its original currency to USD. It uses the fx_currency and fx_rate tables to fetch the latest exchange rates and performs the conversion only when valid data is available. 🔍 Key Features:\n\nField Focus: Converts the annual_budget field based on the currency specified in budget_currency. Validation: Ensures both the currency code and amount are valid before proceeding. Currency Check: If the currency is already USD, it bypasses conversion. Exchange Rate Lookup: Retrieves the most recent exchange rates for both the source currency and USD. Conversion Logic: Applies the formula USD Amount=(Original AmountSource Rate)×USD Rate\\text{USD Amount} = \\left(\\frac{\\text{Original Amount}}{\\text{Source Rate}}\\right) \\times \\text{USD Rate}USD Amount=(Source RateOriginal Amount​)×USD Rate. Error Handling: Clears the USD field if any required data is missing or invalid.\n\nThis script ensures accurate and up-to-date currency conversion for budgeting purposes and is well-commented for maintainability and clarity\n"
  },
  {
    "path": "Server-Side Components/Business Rules/DeleteUserRole/README.md",
    "content": "Bussienss Rule to delete the User Role from table like 'Service Category User Roles [service_category_user_role]'\nSteps:\n - Navigate to your instance \n - Open Business Rule Table [sys_script] and click on New\n - Create After BR and provide below condition\n - When to Run\n        - AFter\n        - Order: 100\n        - Operation: Delete\n  - Provide the condition as:\n   !current.user_role.nil()\n  - Select Advance option and use the script\n"
  },
  {
    "path": "Server-Side Components/Business Rules/DeleteUserRole/script.js",
    "content": "/*\nCreate after BR to delete the user role from table. In this example table is referenced as 'Service Category User Role[service Category User Role]'\n\n>>>>When to Run:\nWhen: AFter\nOperation:Delete\norder: 100\n\n>>>> Condition: \n  !current.user_role.nil()\n*/\ndeleteUserRole();\n\nfunction deleteUserRole() {\n\tvar ur = new GlideRecord(\"sys_user_has_role\");\n\tif (ur.get(current.user_role))\n\t\tur.deleteRecord();\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display BR to get groupInfo of logged in User/README.md",
    "content": "This code snippet will help you to make fields read only/hide if a the logged in user belongs to a specific group.\nTo achieve this, we need to create a display Business rule and an onLoad client script.\nDisplay BR will look for the group of logged in user and return true or false in a scratchpad variable.\nClient script will validate the value and make the field read only/hide.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display BR to get groupInfo of logged in User/displayBr.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\n\tg_scratchpad.isMemberOf = gs.getUser().isMemberOf('ServiceNow QA team'); // this will return true if the user is member of 'ServiceNow QA team' and false otherwise.\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display BR to get groupInfo of logged in User/onLoadClientScript.js",
    "content": "function onLoad() {\n    //Type appropriate comment here, and begin script below\n    if (g_scratchpad.isMemberOf == true) {\n        g_form.setReadOnly('u_choice', true); // to make the 'u_choice' read only\n        g_form.setDisplay('u_work_item', false); // to hide the 'u_work_item' field\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display current user display name on top of form/README.md",
    "content": "**Use case :**\n Display info message with current user display name on top of a form\n \n **Soulution :**\n Write a \"Display business rule\" on any table using `GlideSystem - getUserDisplayName()` API <br/>\n Check script.js file for example\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display current user display name on top of form/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    var user = gs.getUserDisplayName();\n    gs.addInfoMessage(\"Welcome \" + user); //This adds info message on top of the form\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display warning message when peer reviewer and Requested by are same person/README.md",
    "content": "This business rule is used in the Change Management module to display a warning message when requested by and Peer reviewer are the same person. You need to run this business rule \"before\" you \"insert\" under the filter conditions.\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Display warning message when peer reviewer and Requested by are same person/Warningmessage_Business rule.jss",
    "content": "if (current.sysapproval.sys_class_name =='change_request' && current.approver == current.sysapproval.requested_by)\n{\ncurrent.setAbortAction('true');\ngs.addInfoMessage('The Requester and Peer Reviewer cannot be the same person.  The change request has been sent to the Peer Reviewer group specified and someone else from your team will need to perform the peer review.');\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Due date calculation based on priority/README.md",
    "content": "# Objective\nThis ServiceNow business rule script is designed to automatically calculate the due date for a task based on its priority. It executes before the record is saved (Before Business Rule) and calculates the due date in hours, depending on the priority level of the task.\n\n# Priority-to-Due-Date Mapping\n```\nvar priorityToDueDate = {\n    1: 4,    // High priority: Due in 4 hours\n    2: 24,   // Medium priority: Due in 24 hours\n    3: 72    // Low priority: Due in 72 hours\n};\n```\nThis section defines a JavaScript object called priorityToDueDate that maps priority values to due date intervals in hours. For example, if the task has a priority of 1 (High), its due date will be set to 4 hours from the current date and time.\nAlternatively, we can store these mapping values in a custom table, allowing us to update them as necessary.\n\n# Get Priority Value\n```\nvar priority = current.priority;\n```\nThis line retrieves the priority value from the current record and stores it in a variable called priority.\n\n# Check Priority Validity and Mapping\n```\nif (priority && priorityToDueDate.hasOwnProperty(priority)) {\n    // Code goes here\n}\n```\nThis if statement checks if the priority variable is defined (not null or undefined) and if it exists as a key in the priorityToDueDate mapping. This ensures that the priority value is valid and has a corresponding due date interval.\n\n# Calculate Due Date\n```\nvar dueDate = new GlideDateTime();\ndueDate.addHours(priorityToDueDate[priority]);\n```\nIf the priority is valid, a new GlideDateTime object is created, and the addHours method is used to add the appropriate number of hours (based on the priority) to the current date and time. This calculates the due date.\n\n# Update Due Date Field\n```\ncurrent.due_date = dueDate;\n```\nFinally, the due_date field in the current record is updated with the calculated due date.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Due date calculation based on priority/code.js",
    "content": "// Auto-calculate Due Date based on priority Business Rule (Before BR)\n// This business rule calculates the due date based on task priority.\n\n(function executeRule(current, previous /*, display*/) {\n\n    // Define priority-to-due-date mapping (in hours)\n    var priorityToDueDate = {\n        1: 4,    // High priority: Due in 4 hours\n        2: 24,   // Medium priority: Due in 24 hours\n        3: 72    // Low priority: Due in 72 hours\n    };\n\n    // Get the priority value from the task\n    var priority = current.priority;\n\n    // Check if priority is valid and mapping exists\n    if (priority && priorityToDueDate.hasOwnProperty(priority)) {\n        // Calculate the due date\n        var dueDate = new GlideDateTime();\n        dueDate.addHours(priorityToDueDate[priority]);\n\n        // Update the due date field\n        current.due_date = dueDate;\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic Business Rule to Update User Roles Based on Department Changes/Dynamic Business Rule to Update User Roles Based on Department Changes.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n        \n        var newDepartment = current.department.getDisplayValue();\n\n        // Clear existing roles\n        var roleGR = new GlideRecord('sys_user_has_role');\n        roleGR.addQuery('user', current.sys_id);\n        roleGR.deleteMultiple(); // Remove existing roles\n\n        // Assign new roles based on the new department\n        var newRoleGR = new GlideRecord('sys_role');\n        newRoleGR.addQuery('name', 'LIKE', newDepartment); // Assuming role names follow department names\n        newRoleGR.query();\n\n        while (newRoleGR.next()) {\n            var userRoleGR = new GlideRecord('sys_user_has_role');\n            userRoleGR.initialize();\n            userRoleGR.user = current.sys_id;\n            userRoleGR.role = newRoleGR.sys_id;\n            userRoleGR.insert();\n        }\n\n        gs.info('Updated roles for user ' + current.user_name + ' based on new department: ' + newDepartment);\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic Business Rule to Update User Roles Based on Department Changes/README.md",
    "content": "**Purpose**\n\nThis business rule automatically updates user roles in ServiceNow whenever a user's department changes. By ensuring that users have the appropriate roles based on their current department, the rule helps maintain data integrity and enhances access control within the organization.\n\n\n\n**Complete Business Rule Configuration**\n\n**Name**: \"Update User Roles Based on Department Changes\"\n\n**Table**: sys_user\n\n**When**: Before\n\n**Insert**: Checked\n\n**Update**: Checked\n\n\n**Condition**:\n\n**javascript**\n\n'''current.department.changes();'''\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic Field Population from CMDB/beforeBusinessRule.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Call the Script Include\n    var populator = new DynamicFieldPopulator();\n    populator.populateFields(current, current.getTableName());\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic Field Population from CMDB/readme.md",
    "content": "Project Description\n\nDynamic Field Population from CMDB is a reusable ServiceNow solution that automatically fills key fields on incidents, tasks, or change requests based on the selected Configuration Item (CI). By fetching data such as Owner, Assignment Group, Location, Department, and Business Service directly from the CMDB, this project reduces manual effort, ensures data consistency, and improves ITSM efficiency.\n\nIt leverages a Business Rule that triggers on record insert or update, combined with a Script Include that handles dynamic mapping of CI fields to target fields. The solution can be extended for multiple tables, integrated with the Service Portal via GlideAjax, and configured using a JSON or mapping table for maximum flexibility.\n\nKey Benefits:\n\nSaves time for agents by auto-filling fields.\n\nReduces errors and ensures standardized data.\n\nReusable across multiple tables (Incident, Change, Problem).\n\nEasily configurable for different CI-to-field mappings.\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic Field Population from CMDB/scriptinclude.js",
    "content": "var DynamicFieldPopulator = Class.create();\nDynamicFieldPopulator.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    populateFields: function(currentRecord, tableName) {\n\n        // Ensure CI is selected\n        if (!currentRecord.cmdb_ci) return;\n\n        // Fetch CI record\n        var ciGR = new GlideRecord('cmdb_ci');\n        if (!ciGR.get(currentRecord.cmdb_ci)) return;\n\n        // Mapping: CI field -> Target record field\n        var mapping = {\n            \"owned_by\": \"assigned_to\",\n            \"assignment_group\": \"assignment_group\",\n            \"location\": \"location\",\n            \"department\": \"u_department\", // custom field example\n            \"business_service\": \"cmdb_ci_service\"\n        };\n\n        // Loop through mapping and populate fields if empty\n        for (var ciField in mapping) {\n            var targetField = mapping[ciField];\n            if (!currentRecord[targetField] || currentRecord[targetField] == '') {\n                currentRecord[targetField] = ciGR.getValue(ciField);\n            }\n        }\n\n        return currentRecord; // optional, for reusability\n    }\n\n});\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic GlideList Field Sync/README.md",
    "content": "This code_snippet.js script sync all related assignment groups when a record’s department changes.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Dynamic GlideList Field Sync/code_snippet.js",
    "content": "// This script sync all related assignment groups when a record’s department changes and this business rule runs After Update\n(function executeRule(current, previous) {\n    if (current.department.changes()) {\n        var grp = new GlideRecord('sys_user_group');\n        grp.addQuery('u_department', current.department);\n        grp.query();\n        var list = [];\n        while (grp.next()) list.push(grp.sys_id.toString());\n        current.assignment_group = list.join(',');\n        current.update();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Emergency Change Cannot be closed without AttachedIncident/README.md",
    "content": "1. This is a Before-Busines rule created on Change Request Table\n2. I used GlideAggregate API.\n3. only update is checked\n4. conditions were Type is Emergency AND State changes to Close.\n5. For emergency change Request, if there are no attached incident's to it then we don't let the user to move the state to close.\n6. We use glideAggregate to glide Incident table and if we find any incident's user can move the state to close, But if there is no records then user action will be aborted.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Emergency Change Cannot be closed without AttachedIncident/Review to Close Without Incident.js",
    "content": "//before business rule is used and update only checked\n//conditions were type is emergency AND State changes to closed\n// if these conditions met and there is no attached incidents to this emergency change request then, we don't let the user to close the Change request\n\n(function executeRule(current, previous /*null when async*/ ) {\n\n    var inc = new GlideAggregate('incident');//we glide the incident table to get the value of current change request having any incidents or not\n    inc.addQuery(\"rfc\", current.getUniqueValue()); //instead of sys_id we used getUniqueValue\n    inc.query();\n\t//if any incident found then its fine and we can move it to close state\n  ..if No incident found then we will abort the action and send's a pop-up message to the user\n    if (!inc.hasNext()){\n        gs.addErrorMessage('Emergency change cannot be moved from review to close without an attached incident in Incidents Fixed by change');\n        current.setAbortAction(true);\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce CI maintenance window on Change schedule/README.md",
    "content": "# Enforce CI maintenance window on Change schedule\n\n## What this solves\nChange requests are sometimes scheduled outside the maintenance windows of the affected CIs, causing risky or blocked implementations. This rule validates the planned start and end times of a Change against the maintenance schedules of its related CIs and blocks the update if none of the CIs allow that window.\n\n## Where to use\n- Table: `change_request`\n- When: before insert and before update\n- Order: early (for example 50)\n\n## How it works\n- Looks up CIs related to the Change via `task_ci`\n- For each CI with a defined `maintenance_schedule` (reference to `cmn_schedule`), uses `GlideSchedule.isInSchedule` to verify the planned start and end are inside the window\n- If at least one CI’s maintenance schedule permits the window, the Change is allowed\n- If no related CI permits the window, the rule aborts the action with a clear message\n- Behaviour for CIs without a defined maintenance schedule is configurable\n\n## Configure\nAt the top of the script:\n- `BLOCK_WHEN_NO_SCHEDULE`: if true, treat CIs without a maintenance schedule as non-compliant\n- `REQUIRE_BOTH_BOUNDARIES`: if true, both planned start and planned end must be inside a permitted window\n- `TIMEZONE`: optional IANA time zone string (for example `Europe/London`); leave blank to use the schedule or instance default\n\n## Notes\n- If your process requires all CIs to permit the window, change `anyPass` logic to `allPass`\n- This rule checks only maintenance windows defined on the CI record. If you store schedules at the Business Service level, adapt the CI lookup accordingly\n\n## References\n- GlideSchedule API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideSchedule/concept/c_GlideScheduleAPI.html\n- GlideDateTime API  \n  https://www.servicenow.com/docs/bundle/zurich-api-reference/page/app-store/dev_portal/API_reference/GlideDateTime/concept/c_GlideDateTimeAPI.html\n- Change Management fields  \n  https://www.servicenow.com/docs/bundle/zurich-it-service-management/page/product/change-management/concept/change-management-overview.html\n- Task CI relationship (`task_ci`)  \n  https://www.servicenow.com/docs/bundle/zurich-servicenow-platform/page/product/configuration-management/reference/task-ci.html\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce CI maintenance window on Change schedule/br_enforce_ci_maintenance_window.js",
    "content": "// Business Rule: Enforce CI maintenance window on Change schedule\n// Table: change_request | When: before insert, before update\n\n(function executeRule(current, previous /*null*/) {\n  // ===== Configuration =====\n  var BLOCK_WHEN_NO_SCHEDULE = false;  // if true, a CI without maintenance_schedule causes failure\n  var REQUIRE_BOTH_BOUNDARIES = true;  // if true, both planned start and end must be inside maintenance window\n  var TIMEZONE = 'Europe/London';      // optional; '' to use schedule/instance default\n  // =========================\n\n  try {\n    // Only run when dates are meaningful\n    if (!current.planned_start_date || !current.planned_end_date) return;\n\n    // Build GDTs once\n    var psd = new GlideDateTime(current.planned_start_date.getDisplayValue());\n    var ped = new GlideDateTime(current.planned_end_date.getDisplayValue());\n    if (psd.after(ped)) {\n      gs.addErrorMessage('Planned start is after planned end. Please correct the schedule.');\n      current.setAbortAction(true);\n      return;\n    }\n\n    // Collect related CIs for this Change\n    var ciIds = [];\n    var tci = new GlideRecord('task_ci');\n    tci.addQuery('task', current.getUniqueValue());\n    tci.query();\n    while (tci.next()) ciIds.push(String(tci.getValue('ci_item')));\n\n    if (ciIds.length === 0) {\n      // No CIs; nothing to validate\n      return;\n    }\n\n    var anyPass = false;\n    var missingScheduleCount = 0;\n    var evaluated = 0;\n\n    // Evaluate each CI's maintenance schedule\n    var ci = new GlideRecord('cmdb_ci');\n    ci.addQuery('sys_id', 'IN', ciIds.join(','));\n    ci.query();\n\n    while (ci.next()) {\n      evaluated++;\n\n      var schedRef = ci.getValue('maintenance_schedule');\n      if (!schedRef) {\n        missingScheduleCount++;\n        continue;\n      }\n\n      var sched = new GlideSchedule(schedRef, TIMEZONE || '');\n      var startOK = sched.isInSchedule(psd);\n      var endOK = sched.isInSchedule(ped);\n\n      var pass = REQUIRE_BOTH_BOUNDARIES ? (startOK && endOK) : (startOK || endOK);\n      if (pass) {\n        anyPass = true;\n        break; // at least one CI permits this window\n      }\n    }\n\n    // Handle missing schedules according to policy\n    if (!anyPass) {\n      var hasBlockingNoSchedule = BLOCK_WHEN_NO_SCHEDULE && missingScheduleCount > 0;\n      if (hasBlockingNoSchedule || evaluated > 0) {\n        gs.addErrorMessage(buildMessage());\n        current.setAbortAction(true);\n      }\n    }\n\n    function buildMessage() {\n      var parts = [];\n      parts.push('Planned window does not fall inside any related CI maintenance schedules.');\n      if (REQUIRE_BOTH_BOUNDARIES) parts.push('Both start and end must be inside a permitted window.');\n      if (missingScheduleCount > 0) {\n        parts.push((BLOCK_WHEN_NO_SCHEDULE ? 'Blocking' : 'Ignoring') + ' ' + missingScheduleCount + ' CI(s) with no maintenance schedule.');\n      }\n      return parts.join(' ');\n    }\n  } catch (e) {\n    gs.error('Maintenance window validation failed: ' + e.message);\n    // Be safe: do not block due to a runtime error\n  }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce File Upload Restrictions for HR Document Submission/Code.js",
    "content": "var hrTask = new GlideRecord('sn_hr_core_task');\n\nif (hrTask.get(current.table_sys_id) && \n    hrTask.hr_Task_type == 'upload_documents' && \n    hrTask.short_description == 'submit photo identification') {\n\n    var fileName = current.file_name.toString();\n    var fileSize = current.size_bytes.toString();\n    var fileType = fileName.split('.').pop().toLowerCase();\n\n    // Check file size (must not exceed 2MB)\n    if (parseInt(fileSize) > 2000000) { // 2MB in bytes\n        gs.addErrorMessage('Maximum file size is 2 MB');\n        current.setAbortAction(true); // Abort if file size exceeds 2 MB\n        return;\n    }\n\n    // Check file type (must be JPG or JPEG)\n    if (fileType !== 'jpg' && fileType !== 'jpeg') {\n        gs.addErrorMessage('File must be in JPG or JPEG format');\n        current.setAbortAction(true); // Abort if not JPG or JPEG\n        return;\n    }\n        }\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce File Upload Restrictions for HR Document Submission/README.md",
    "content": "This code ensures that when a user uploads a document related to a specific HR task, the uploaded file meets\ncertain criteria: it must be in JPG or JPEG format and must not exceed 2 MB in size. If either condition is violated,\nthe upload is halted, and an appropriate error message is displayed to the user, maintaining the integrity of the data \nbeing processed.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce Percentage/README.md",
    "content": "# Objective\nA Business Entity has multiple owners who are individuals. \nEach indivisual owns a percentage of the business.\nThis business rule insures that the total of all ownership percentage does not exceed 100%.\n\n# Challenge\nThe aggregate function calculates the sum by using the values that are stored in the database. However we need to calculate the sum using the values that are in the database for all owners and use the value that the user is trying to update for the owner that is currently being udpated.\n\n# Solution\n1. Use the sum function from the [Calculator Script Include](https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Script%20Includes/Calculator).\n2. Calculate the sum using the values in the database\n3. Substract the previous value and then add the current value to calculate what the future sum would be\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce Percentage/enforce_percentage.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    var tableName = current.getTableName();\n    gs.info('Validate Ownership Percentage');\n    var individual = current.individual;\n    gs.info(' current individual  = ' + current.individual.getDisplayValue());\n    gs.info(' current ownership % = ' + current.ownership_percentage.getDisplayValue());\n    gs.info('previous ownership % = ' + previous.ownership_percentage.getDisplayValue());\n\n    /** \n     * The aggregate function calculates the sum by using the values that are stored in the database. \n     * However we need to calculate the sum using the values that are in the database for all owners \n     * and use the value that the user is trying to update for the owner that is currently being \n     * udpated.\n      */\n\n    var query = 'business_entity=' + current.business_entity;\n    var sum_with_previous_values = new x_snc_ecms.Calculator().getSum(tableName, 'ownership_percentage', query);\n    gs.info('SUM ownership % with previous value = ' + sum_with_previous_values);\n    var sum_with_current_values = sum_with_previous_values - previous.ownership_percentage + current.ownership_percentage;\n    gs.info('SUM ownership % CURRENT= ' + sum_with_current_values);\n\n    if (sum_with_current_values > 100) {\n        gs.addErrorMessage(\"Sum of all ownerships can not be greater than 100%\");\n        current.setAbortAction(true);\n    }\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce Single Attachment Rule for HR Core Tasks/README.md",
    "content": " This code checks if there is more than one attachment for a specific task in ServiceNow. If there are multiple attachments, \n it displays an error message and prevents further processing of the current record to ensure compliance with the rule that\n only one attachment is permitted.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce Single Attachment Rule for HR Core Tasks/codingfile.js",
    "content": "var attachmentRecord = new GlideRecord('sys_attachment');\nattachmentRecord.addQuery('table_name', 'sn_hr_core_task');\nattachmentRecord.addQuery('table_sys_id', current.sys_id);\nattachmentRecord.query();\n\nif (attachmentRecord.getRowCount() > 1) { // Check if more than one attachment exists\n    gs.addErrorMessage('Only one attachment is permitted for this task: ' + current.sys_id); // Specific error message\n    current.setAbortAction(true); // Abort the action\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce Unique Rank/README.md",
    "content": "Before Business rule for keeping a rank field unique.\n\nConditions: current.rank.changes() && !current.rank.nil()"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce Unique Rank/UniqueRank.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t// Prevent submission if a record already exists with this rank\n\tvar grCheckRank = new GlideRecord(current.getTableName()); \n\tgrCheckRank.addQuery('rank', current.rank); \n\tgrCheckRank.query(); \n\tif (grCheckRank.hasNext()) {\n\t\tcurrent.setAbortAction(true);\n\t\tgs.addErrorMessage('Another record exists with this rank. Set the rank to a value above or below ' + current.rank + '.'); \n\t}\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce a 1-1 relationship/README.md",
    "content": "# Enforce a 1:1 relationship between two tables\n\nServiceNow does not provide a direct means of establishing a one-to-one relationship between two tables. Even so, you can enforce a 1:1 relationship with a remote table using business rules.\nWhen the local reference field is changed, add the current record reference to the new remote record if applicable and remove the current record reference from the old/previous remote record if applicable.  \n\nNOTE: Don't forget to:  \n    1) Add this BR to both local and remote tables and   \n    2) Swap the local and remote field names on the opposing table.  \n\nNOTE: The table names are not required as they are defined by the table on which this BR runs and the table defined in the reference field.  \n\nAttribution: This function is adapted from the example described in Tim \"@TheProfessor\" Woodruff's book \"Learning ServiceNow (Second Edition)\" available from Pakt Publishing.  \n  \n\n## Business Rule\nWhen: BEFORE INSERT or UPDATE\nAdvanced Script:\n```javascript\n( function( current, previous /*null when async*/ ) {\n\n    var remote_record,\n        local_field_name = 'u_my_local_field',\n        remote_field_name = 'u_the_remote_field';\n\n    // Set the reference on the current remote record.\n    if ( !current[ local_field_name ].nil() ){\n        remote_record = current[ local_field_name ].getRefRecord();\n        remote_record.setValue( remote_field_name, current.getUniqueValue() );\n        remote_record.update();\n    }\n\n    // Clear the reference on the previous remote record.\n    if ( !previous[ local_field_name ].nil() ) {\n        remote_record = previous[ local_field_name ].getRefRecord();\n        remote_record.setValue( remote_field_name, '' );\n        remote_record.update();\n    }\n    \n} )( current, previous );\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Enforce a 1-1 relationship/enforce_1_1.js",
    "content": "( function( current, previous /*null when async*/ ) {\n\n    var remote_record,\n        local_field_name = 'u_my_local_field',\n        remote_field_name = 'u_the_remote_field';\n\n    // Set the reference on the current remote record.\n    if ( !current[ local_field_name ].nil() ){\n        remote_record = current[ local_field_name ].getRefRecord();\n        remote_record.setValue( remote_field_name, current.getUniqueValue() );\n        remote_record.update();\n    }\n\n    // Clear the reference on the previous remote record.\n    if ( !previous[ local_field_name ].nil() ) {\n        remote_record = previous[ local_field_name ].getRefRecord();\n        remote_record.setValue( remote_field_name, '' );\n        remote_record.update();\n    }\n    \n} )( current, previous );\n"
  },
  {
    "path": "Server-Side Components/Business Rules/EnhanceIncidentWithProblem/EnhanceIncidentWithProblem.js",
    "content": "function executeRule(current, previous /*null when async*/) {\n    try {\n        // Check if problem_id was added or changed\n        var isNewLink = !previous || current.problem_id.changes();\n\n        if (isNewLink && current.problem_id) {\n            var problemGR = new GlideRecord(\"problem\");\n\n            if (problemGR.get(current.problem_id)) {\n                var problemStatement = problemGR.short_description;\n                var problemNumber = problemGR.number;\n\n                // Append to description\n                current.description = (current.description || '') +\n                    \"\\n\\n[Linked Problem] \" + problemNumber + \": \" + problemStatement;\n            }\n        }\n    } catch (ex) {\n        gs.error(\"An unexpected error occurred while enhancing the Incident with Problem details.\");\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/EnhanceIncidentWithProblem/README.md",
    "content": "# Enhance Incident Description with Linked Problem Statement\n\n## Overview\nThis ServiceNow Business Rule enhances Incident records by automatically appending the short description of a linked Problem record. It improves visibility and context for support teams working on related incidents.\n\n## Features\n- Triggered when a Problem ID is newly linked or changed on an Incident.\n- Fetches the Problem's short description and number.\n- Appends the Problem Statement to both the Incident's short description and description fields.\n- Includes general error handling to ensure stability.\n\n## Business Rule Configuration\n- Table: `incident`\n- When to Run: `before insert` and `before update`\n- Condition: \n  ```javascript\n  current.problem_id.changes() || !previous\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Exclude Redundant Email Recipients/README.md",
    "content": "# Exclude Redundant Email Recipients\n### This business rule is designed to intercept new comment notifications and modify the recipients to mitigate redundant notifications in response to email replies that users were previously included on as a direct or copied recipient.\n\nInitial Settings (modify as needed for your purposes)\n- Table: sys_email\n- Advanced: true\n- When: before\n- Insert: true\n- Update: true\n- Conditions:\n  - Type > changes to > send-ready\n  - Subject > contains > comment\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Exclude Redundant Email Recipients/exclude_redundant_email_recipients.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // This inspects each of the recipient fields (To, CC, BCC) and strips out any\n    // email addresses that were present on the most recently recieved email reply\n\n    // stripRecipients() returns an object with two key/value pairs:\n    // newList - List of \"safe\" addresses, checked against the prevRecipients\n    // badAddress - True/false, true if any \"bad\" addresses were removed. - (not currently used)\n    function stripRecipients(targetField, blackList) {\n\n        // Establish an array of current recipients:\n        var arrRecipients = [];\n        arrRecipients = targetField.toLowerCase().split(',');\n\n        // Establish an array of revised recipients:\n        var revisedRecipients = new ArrayUtil();\n        revisedRecipients = revisedRecipients.diff(arrRecipients, blackList);\n\n        // Flag if we have \"bad\" recipients:\n        var foundBad = false; // Not currently used\n\n        // Return the results as an object:\n        var objResults = {\n            \"newList\": revisedRecipients,\n            \"badAddress\": foundBad // Not currently used\n        };\n\n        return objResults;\n    }\n\n    // Check if current email is triggered from an email reply:\n    var table = current.getValue('target_table');\n    var ticket = new GlideRecord(table);\n    ticket.get(current.getValue('instance'));\n\n    var notes = ticket.comments.getJournalEntry(1).split('\\n'); // gather most recent comment and split each new line into a new array\n    if (notes[1].indexOf('reply from:') == 0) { // check first new line and if it starts with 'reply from:', do the following...\n\n        // Get the previous recipients we want to exclude:\n        var prevRecipients = [];\n        var inboundEmail = new GlideRecord('sys_email');\n        inboundEmail.addQuery('instance', current.getValue('instance'));\n        inboundEmail.addQuery('type', 'received');\n        inboundEmail.orderByDesc('sys_created_on');\n        inboundEmail.setLimit(1);\n        inboundEmail.query();\n        if (inboundEmail.next()) {\n            prevRecipients = inboundEmail.getValue('recipients').toLowerCase().split(',');\n        }\n\n        // Take a copy of the original recipients for use later:\n        var curRecipients = 'TO:' + current.getValue('direct') +\n            ', CC:' + current.getValue('copied') +\n            ', BCC:' + current.getValue('blind_copied');\n\n        // Check each recipient field, and strip out duplicate addresses:\n        var toResults;\n        var ccResults;\n        var bccResults;\n        var recipientResults;\n\n        if (current.direct) {\n            toResults = stripRecipients(current.getValue('direct'), prevRecipients);\n            current.setValue('direct', toResults.newList.toString());\n        }\n        if (current.copied) {\n            ccResults = stripRecipients(current.getValue('copied'), prevRecipients);\n            current.setValue('copied', ccResults.newList.toString());\n        }\n        if (current.blind_copied) {\n            bccResults = stripRecipients(current.getValue('blind_copied'), prevRecipients);\n            current.setValue('blind_copied', bccResults.newList.toString());\n        }\n        if (current.recipients) {\n            recipientResults = stripRecipients(current.getValue('recipients'), prevRecipients);\n            current.setValue('recipients', recipientResults.newList.toString());\n        }\n\n        // If no recipients remain, don't send the email:\n        if (!current.recipients && !current.blind_copied && !current.copied && !current.direct) {\n\t    current.setValue('state', 'ignored');\n            current.setValue('type', 'send-ignored');\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Fetching reference field value from higher-level parents/Fetching reference field value from higher-level parents.js",
    "content": "// In this before insert or update Business Rule, we are fetching a reference field value from higher-level parents in hierarchy when there is a field containing the parent record in the children and our use-case reference field is present in all the tables in hierarchy\n// I would be referring to \"reference field name we want to populate\" as \"r1\"\n// I would be referring to \"reference field containing parent record\" as \"parent\"\n\n\n(function executeRule(current, previous /*null when async*/ ) {\n    if (current.r1 == \"\" && !JSUtil.nil(current.parent.r1)) // Populate 'use-case reference field' from parent's value for the reference field'\n        current.r1 = current.parent.r1;\n    else if (current.< reference field name we want to populate > == \"\" && !JSUtil.nil(current.parent.parent.r1)) // Populate 'use-case reference field' from 'parent of parent' \n        current.r1 = current.parent.parent.r1;\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Fetching reference field value from higher-level parents/README.md",
    "content": "This is a \"**before insert/update**\" Business Rule\nWe are fetching a reference field value from higher-level parents in hierarchy \nwhen there is a field containing the parent record in the children and \nour use-case reference field is present in all the tables in hierarchy\n\nIn the code, we are referring to \"reference field name we want to populate\" as \"_r1_\"\nIn the code, we are referring to \"reference field containing parent record\" as \"_parent_\"\n\nThe \"**JSUtil.nil**\" is being used to check for empty/null value for the field.\n\n\nThrough the code we are checking the empty value of the use-case reference field and dot walking to parents and fetching the value from them if it exists\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Field Validation based on form view in Server side/README.md",
    "content": "ServiceNow business rule for server-side field validation based on form views.\n\nThis ServiceNow business rule provides comprehensive server-side validation for multiple form fields when users access specific form views. The script ensures data integrity by validating that critical fields contain expected values before allowing record submission, making it perfect for enforcing business rules and data consistency across your ServiceNow instance.\n\nWhat This Script Does:\n\nThe business rule automatically validates multiple fields against predefined expected values when a specific form view is accessed. Key features include:\n\nView-Based Validation: Only triggers when accessing a specified form view\nMultiple Field Support: Validates multiple fields simultaneously with customizable criteria\nRequired Field Checking: Ensures mandatory fields are not empty or null\nValue Validation: Confirms fields contain expected values according to business rules\nUser-Friendly Messaging: Provides clear, consolidated error messages explaining all validation failures\nServer-Side Security: Performs validation on the server to prevent client-side bypassing\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Field Validation based on form view in Server side/fieldValidationinBR.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    var TARGET_VIEW_NAME = 'your_target_view_name';\n    var FIELD_VALIDATIONS = [\n        {field: 'field1', expectedValue: 'value1', errorMsg: 'Field 1 validation failed'},\n        {field: 'field2', expectedValue: 'value2', errorMsg: 'Field 2 validation failed'},\n        {field: 'field3', expectedValue: 'value3', errorMsg: 'Field 3 validation failed'}\n    ];\n    \n    var gURI = \"\";\n    try {\n        gURI = gs.action.getGlideURI();\n    } catch(e) {\n        return;\n    }\n    \n    var view_name = gURI.get('sysparm_view');\n    \n    if (view_name == TARGET_VIEW_NAME) {\n        var validationErrors = [];\n        \n        for (var i = 0; i < FIELD_VALIDATIONS.length; i++) {\n            var validation = FIELD_VALIDATIONS[i];\n            var fieldValue = current.getValue(validation.field);\n            \n            if (gs.nil(fieldValue)) {\n                validationErrors.push(validation.field + ' is required');\n                continue;\n            }\n            \n            if (fieldValue != validation.expectedValue) {\n                validationErrors.push(validation.errorMsg);\n            }\n        }\n        \n        if (validationErrors.length > 0) {\n            gs.addErrorMessage(gs.getMessage(\"Validation failed: {0}\", validationErrors.join(', ')));\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Find MRVS Total/mrvs_total_sum.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    // --- Configuration ---\n    var VARIABLE_NAME_TO_POPULATE_WITH_SUM = 'total_estimate'; // Variable to store the total\n    var MRVS_INTERNAL_NAME = 'item_details'; // Internal name of your multi-row variable set\n    var MRVS_VARIABLE_NAME_TO_SUM = 'quoted_price'; // Variable name containing the value to sum\n\n    // --- Don't change below ---\n    var total_value = 0;\n\n    // Get the MRVS object\n    var mrvs = current.variables[MRVS_INTERNAL_NAME];\n\n    // Get the number of rows\n    var rowCount = mrvs.getRowCount();\n\n    // Loop through the parsed array of rows\n    for (var i = 0; i < rowCount; i++) {\n        var row = mrvs.getRow(i);\n\t\tvar line_price = parseFloat(row[MRVS_VARIABLE_NAME_TO_SUM]) || 0;\n\t\ttotal_value += line_price;\n    }\n\n    current.variables[VARIABLE_NAME_TO_POPULATE_WITH_SUM] = total_value.toFixed(2);\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Find MRVS Total/readme.md",
    "content": "# Server-Side MRVS Total Calculator\nThis ServiceNow Business Rule automatically calculates the sum of a numeric field from all rows within a Multi-Row Variable Set (MRVS) after a catalog item has been submitted.\nIt populates a separate variable with the calculated total, making the value easily accessible for flows, reports, and integrations without needing to parse the MRVS JSON every time. This script is designed to run on the back-end, ensuring the total is accurate and persistent.\n\n## How to Implement\nFollow these steps to configure the Business Rule in your instance.\n\n### 1. Prerequisites\nBefore creating the rule, make sure you have the following variables on your Catalog Item:\n\n- A Multi-Row Variable Set (e.g., named item_details).\n- A variable inside the MRVS that will contain a number (e.g., named quoted_price).\n- A single variable outside the MRVS to store the final sum (e.g., a Single Line Text variable named total_estimate).\n\n### 2. Business Rule Configuration\nCreate a new Business Rule with the following settings:\n- Name: A descriptive name like Calculate MRVS Total on RITM.\n- Table: Requested Item [sc_req_item].\n- Advanced: Check this box to reveal the script field.\n- When to run:\n  - When: Before\n  - Insert: true\n  - Update: true\n\nCopy and paste the script from `mrvs_total_sum.js` into the Script field within the Advanced tab of your Business Rule.\n\nBefore saving, you must update the three configuration variables at the top of the script to match your specific setup. \nYou will need to set the following:\n- `VARIABLE_NAME_TO_POPULATE_WITH_SUM` to the internal name of your total variable\n- `MRVS_INTERNAL_NAME` to the internal name of your Multi-Row Variable Set\n- `MRVS_VARIABLE_NAME_TO_SUM` to the internal name of the numeric variable inside the MRVS that you want to sum.\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/README.md",
    "content": "GRC Policy Retirement Guard with Control Objective Check\nOverview\nThis Business Rule enhances data integrity and process governance within ServiceNow's GRC module. It prevents a sn_compliance_policy record from being marked with the \"Retired\" state if it is still associated with any active Control Objectives. The rule enforces a proper decommissioning process, ensuring that all dependent Control Objectives are either made inactive or delinked before the policy itself can be retired.\nDetails\nScript Name: Prevent Retire of Policy with Active Control Objectives\nTarget Table: sn_compliance_policy\nRun Time: before update\nCondition: State changes to Retired\nAction: Prevents a policy from being retired if it has active, linked Control Objectives. It displays an error message to the user and aborts the update action.\nLogic:\nEfficient Counting: Uses GlideAggregate for a highly performant query on the many-to-many (m2m) table (sn_compliance_m2m_policy_policy_statement), which links policies to control statements (in this case, acting as Control Objectives).\nQuery Filtering: The query targets the m2m table and filters records where:\nThe document field matches the sys_id of the policy being updated.\nThe related content record (the Control Objective) has its active field set to true.\nAborts Action: If the count of active Control Objectives is greater than zero, the script:\nDisplays an informative error message to the user.\nAborts the update process using current.setAbortAction(true), preventing the policy from being set to Retired.\nBusiness Rule Configuration\nTo implement this functionality, configure the following settings in the Business Rule record:\nName: Prevent Retire of Policy with Active Control Objectives\nTable: sn_compliance_policy\nWhen to run:\nWhen: before\nUpdate: checked\nCondition: [State] [changes to] [Retired]\nAdvanced: checked\n\n\nPurpose and Benefits\nThis Business Rule provides the following benefits to the GRC application:\nProcess Governance: Enforces a controlled process for policy retirement, ensuring that all dependent Control Objectives are handled appropriately before the policy is decommissioned.\nData Integrity: Prevents the creation of orphaned Control Objectives or inconsistencies in compliance reporting.\nCompliance: Ensures that compliance teams maintain an accurate and up-to-date record of active policies and their underlying Control Objectives.\nUser Feedback: Provides immediate and clear feedback to the user, explaining why the retirement action was denied and outlining the necessary steps to proceed.\nPerformance: Utilizes the efficient GlideAggregate method, which is best practice for performing counts on large tables.\nUsage\nThis script is a core part of GRC data governance. If a user attempts to set a policy's State to Retired while active Control Objectives are still linked, they will see an error message and the update will be stopped. The user must first either inactivate or delink all related Control Objectives before attempting to retire the policy again.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/GRC Policy Retirement Gaurd/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    // This Business Rule runs 'before' a record is updated on the 'sn_compliance_policy' table.\n    // Its purpose is to prevent a policy from being retired if it is currently linked to any active Control Objective.\n    // This enforces a proper decommissioning process, ensuring that Control Objective are delinked.\n    // before the policy that governs them, thereby preventing compliance gaps.\n    // The condition for this rule would be: 'State' changes to 'Retired'.\n\n    // Instantiate a GlideAggregate object on the many-to-many (m2m) table\n    // 'sn_compliance_m2m_policy_policy_statement'. This table links policies (via the 'document' field)\n    // to control statements (via the 'content' field). Using GlideAggregate is more\n    // performant than GlideRecord for counting records, as it performs the aggregation\n    // directly in the database.\n    var grControlAggregate = new GlideAggregate('sn_compliance_m2m_policy_policy_statement');\n    \n    // Add a query to filter for records in the m2m table where the 'document' field matches\n    // the sys_id of the policy record currently being retired.\n    grControlAggregate.addQuery('document', current.getUniqueValue());\n    \n    // Add a second query using 'dot-walking' to filter for records where the related\n    // control statement ('content' field) is currently active. This ensures only active\n    // Control Objective are considered.\n    grControlAggregate.addQuery('content.active', true);\n    \n    // Set the aggregate function to COUNT. This tells the database to return the total\n    // number of records that match the query conditions.\n    grControlAggregate.addAggregate('COUNT');\n    \n    // Execute the database query.\n    grControlAggregate.query();\n\n    // Initialize a variable to store the count of active Control Objective.\n    var activeControlCount = 0;\n    \n    // Check if the query returned any results. If it did, retrieve the count.\n    // Note: GlideAggregate.next() returns a row even if the count is zero.\n    if (grControlAggregate.next()) {\n        // Retrieve the aggregated count result and assign it to the variable.\n        activeControlCount = grControlAggregate.getAggregate('COUNT');\n    }\n\n    // Check if the count of active Control Objective is greater than zero.\n    if (activeControlCount > 0) {\n        // If active Control Objective were found, add an error message to display to the user.\n        // The message includes the count for better clarity.\n        gs.addErrorMessage('Cannot retire this policy because it has ' + activeControlCount + ' active Control Objective linked to it. All Control Objective must be delinked first.');\n        \n        // This crucial line aborts the current database transaction (the update operation).\n        // It prevents the policy record from being marked as 'Retired'.\n        current.setAbortAction(true);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Generate event/README.md",
    "content": "**Business Rule**\n\nScript to *generate new custom event*, which can be used on any conditions, on insert/update/delete of a record. In this example Business Rule is configured to generate custom event when any user get 'admin' role. \n\n**Event Registration**\n\nBefore you can generate custom event from script, you need to add Event Registration record *(sysevent_register table)*. Example Event Registration configuration:\n![Event Registration](ScreenShot_0.PNG)\n\n\n**Example configuration of Business Rule**\n\n![Coniguration](ScreenShot_1.PNG)\n\n**Example execution effect**\n\n![Event Log](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Generate event/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    //Business rule to generate new event\n\n    //Adding new event to queue\n    //Parameters: Event name, GlideRecord Object, parm1, parm2\n    gs.eventQueue('custom_event.adminRoleAssigned', current, current.user, current.sys_created_by);\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Group Manager changes, remove old manager & add new manager/Code.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Run only when the Manager field changes\n    if (current.manager.changes()) {\n\n        var groupID = current.sys_id.toString();\n\n        // Remove old manager from group membership\n        if (!gs.nil(previous.manager)) { \n            var oldMember = new GlideRecord('sys_user_grmember');\n            oldMember.addQuery('group', groupID);\n            oldMember.addQuery('user', previous.manager);\n            oldMember.query();\n            while (oldMember.next()) {\n                oldMember.deleteRecord();\n            }\n        }\n\n        // Add new manager to group membership\n        if (!gs.nil(current.manager)) {\n            var newMember = new GlideRecord('sys_user_grmember');\n            newMember.initialize();\n            newMember.group = groupID;\n            newMember.user = current.manager;\n            newMember.insert();\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Group Manager changes, remove old manager & add new manager/ReadMe.md",
    "content": "When the Manager field changes on the Group table (sys_user_group), the system should automatically:\n\nRemove the previous manager from the group’s membership.\n\nAdd the new manager as a member of the same group.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Hide from Interceptor/readme.md",
    "content": "How often you come across cases where 'Private Task' or any other task available from the list needs to be disabled for handful of users (users who are ITIL users but from XYZ department) from the interceptor that shows up when 'New' button is clicked on the Task table.\nThis can be controlled by creating a 'Query' Business Rule on Answer (sys_wizard_answer) table. Script will be used in Business Rule to achieve the same.\nPrivate Task is just an example as the point here is to help understand how to control something that shows up in the interceptors needs to be made available/disabled.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Hide from Interceptor/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\nvar userDept = (gs.getUser().getRecord().getValue('department'));//check for logged in user department\n    if (userDept != 'XYZ') { //is not XYZ. replace XYZ with relevant sys_id\n        current.addQuery('name', '!=', 'Private Task'); //This will remove Private Task from interceptor for all users with department other than XYZ\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/If Conflicts are there restrict change resquest to move further/README.md",
    "content": "This is a before Business rule with update(checked)\nWhen we try to move the record from new to assess state it will check for the possible conficts which are available in the record. even if it finds a single match. It restricts the record to move further and it pops an error message to the user indicating that there are conficts to resolve before moving to the assess State\n\nThis code helps in restricting the records to move further especially for change requests if there are some conflicts in the records it also shows an error message to the user.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/If Conflicts are there restrict change resquest to move further/ifConflictStopChangeRequestToAssessState.js",
    "content": "//This code helps in restricting the change from moving new to assess state in case of any conflicts are there in the new state.\n//This is a business rule in which, i used before business rules and update(checked)\n//when to run condition is state changes to Assess\n//change_request Table\n\n(function executeRule(current, previous /*null when async*/) {\n  var conflictStop = new GlideRecord(\"conflict\"); //gliding conflict table\n  //get any existing conflicts from the current record\n  conflictStop.addQuery(\"change\", current.getUniqueValue());\n  conflictStop.addQuery(\"configuration_item\", current.cmdb_ci);\n  conflictStop.query();\n  // if any conflict is found from the current record then will stop the record from moving further and will show an error message to the user.\n  if (conflictStop.hasNext()) {\n    gs.addErrorMessage(\n      \"state cannot be moved from new to assess without clearing the conflicts, conflicts should not crossed but need to scheduled.\"\n    );\n    current.setAbortAction(true);\n  }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Incident Root Cause Suggestion/business rule.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Only run if short_description or description changes\n    if (current.short_description.changes() || current.description.changes()) {\n        \n        var helper = new IncidentRootCauseHelper();\n        var suggestions = helper.getRootCauseSuggestions(current.short_description + \" \" + current.description);\n\n        if (suggestions.length > 0) {\n            // Store suggestions in a custom field (multi-line text)\n            current.u_root_cause_suggestions = suggestions.join(\"\\n\");\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Incident Root Cause Suggestion/readme.md",
    "content": "Automatically suggests potential root causes for incidents by analyzing past incidents with similar short descriptions or descriptions. This helps agents resolve incidents faster and reduces repetitive investigation effort.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Incident Root Cause Suggestion/script include.js",
    "content": "var IncidentRootCauseHelper = Class.create();\nIncidentRootCauseHelper.prototype = {\n    initialize: function() {},\n\n    // Method to find potential root causes based on keywords\n    getRootCauseSuggestions: function(description) {\n        if (!description) return [];\n\n        var suggestions = [];\n        var gr = new GlideRecord('incident');\n        gr.addActiveQuery(); // Only active incidents\n        gr.addNotNullQuery('u_root_cause'); // Custom field storing root cause\n        gr.query();\n\n        while (gr.next()) {\n            var pastDesc = gr.short_description + \" \" + gr.description;\n            if (description.toLowerCase().indexOf(gr.short_description.toLowerCase()) != -1 ||\n                description.toLowerCase().indexOf(gr.description.toLowerCase()) != -1) {\n                suggestions.push(gr.u_root_cause.toString());\n            }\n        }\n\n        // Remove duplicates and limit to top 5 suggestions\n        suggestions = Array.from(new Set(suggestions)).slice(0, 5);\n\n        return suggestions;\n    },\n\n    type: 'IncidentRootCauseHelper'\n};\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Make Attachment Mandatory/MakeAttachmentMandatory.js",
    "content": "// Add below code to Before BR\nvar attach = new GlideRecord('sys_attachment');\nattach.addQuery('table_sys_id', current.sys_id);\nattach.addQuery('table_name', current.getTableName());\nattach.query();\nif (!attach.next()) {\n\t\tgs.addErrorMessage(\"Cannot submit without an attachment.\");\n\t\tcurrent.setAbortAction(true);\n\t}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Make Attachment Mandatory/README.md",
    "content": "This simple code snippet will help in making attachments mandotary.\nCan be used in Before BR/Script Inculde.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Mandatory Attachment/README.md",
    "content": "Example code to make attachement mandatory. \n\nIt can be reusabel on any table uisng before Business rule,on addition we can add addAggregate to  make two attachements mandatory.\n\n* [GlideAggregate](https://developer.servicenow.com/dev.do#!/reference/api/tokyo/server/no-namespace/c_GlideAggregateScopedAPI) \n* [GLIDEAGGREGATE Use case](https://developer.servicenow.com/blog.do?p=/post/glideaggregate/)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Mandatory Attachment/threeAttachementsMandatory.js",
    "content": "    // Add below code to Before BR\n    var grSA = new GlideAggregate('sys_attachment');\n    grSA.addAggregate('COUNT');\n    grSA.addQuery('table_name', current.getTableName());\n    grSA.addQuery('table_sys_id', current.sys_id);\n    grSA.query();\n    var attachementCount= 0;\n    if (grSA.next()) {\n        attachementCount = grSA.getAggregate('COUNT');\n        if (attachementCount <2) {\n              gs.addErrorMessage(\"Please attach the Approvals and Invoice details to submit the request. \");\n            current.setAbortAction(true);\n        }\n    }\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Manipulating system properties values/README.md",
    "content": "**Business Rule**\n\nScript which allows *getting value of specific system property and manipulate these values*. In this example, true/false property is got to determine if script should be executed. Then value of system property which is keeping last created user which gets 'admin' role is updated.\n\n**Create new system property**\n\nBefore you can get or manipulate custom system property you need to create it in [sys_properties] table. Below you can see example of created property:\n\n![Creating System Property](ScreenShot_0.PNG)\n\n**Example configuration of Business Rule**\n\n![Configuration](ScreenShot_1.PNG)\n\n**Example execution effect**\n\n\n![Execution effect](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Manipulating system properties values/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    //Script to read and update specific system properties (in this example to update last created user name with role admin)\n\n    //Get value of property which you would like to verify\n    var updateAdminUser = gs.getProperty('user.updateNewAdminUsers');\n\n    //Verify if property is set to true\n    if (updateAdminUser) {\n\n        //Get property which you would like to update\n        var property = new GlideRecord('sys_properties');\n        property.addQuery('name', 'user.lastCreatedAdmin');\n        property.query();\n        if (property.next()) {\n\n            //Update value of choosed property\n            property.value = current.user.getDisplayValue();\n            property.update();\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Mark an Email High Importance initiated from Email Client/Mark an Email High Importance",
    "content": "// In Email client, agents don't have a way to mark an email high importance. This is an alternate method using which when the agent add an exclamation '!' ot the start of the email subject, email will be sent as High Importance Email\n\n(function executeRule(current, previous /*null when async*/) {\n\tif (current.subject.startsWith('!'))\n  {\n\t  current.importance = \"high\";\n\t  current.subject = current.subject.substring(1);\n  }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Mark an Email High Importance initiated from Email Client/README.md",
    "content": "This Business Rule can help agent mark an email High importance from Email Client. Out of Box there is no way an agent can mark an email High importance while drafting an email from email client. This Business rule can mark an email High importance when an exclamation (!) is added to the start of the subject in the Email Client\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Mask Sensitive Data in Description Field/maskData.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n   \n\n    // Only run if description has a value\n    if (current.description) {\n        var desc = current.description.toString();\n      \n\n        // Regex patterns for sensitive data\n        var ccRegex = /\\b\\d{13,16}\\b/g;                     // 13–16 continuous digits\n        var ccSpaced = /\\b(\\d{4}[- ]?){3}\\d{4}\\b/g;         // 4-4-4-4 with spaces/dashes\n        var ssnRegex = /\\b\\d{3}-\\d{2}-\\d{4}\\b/g;            // US SSN\n        var phoneRegex = /(\\+?\\d{1,2}[- ]?)?\\(?\\d{3}\\)?[- ]?\\d{3}[- ]?\\d{4}/g; // phone\n\n        var masked = desc;\n\n        // Apply masking with messages\n        if (ccRegex.test(desc)) {\n            gs.addInfoMessage(\"Credit card pattern found → masking\");\n            masked = masked.replace(ccRegex, \"****-****-****-****\");\n        }\n\n        if (ccSpaced.test(desc)) {\n            gs.addInfoMessage(\"Spaced/dashed credit card pattern found → masking\");\n            masked = masked.replace(ccSpaced, \"****-****-****-****\");\n        }\n\n        if (ssnRegex.test(desc)) {\n            gs.addInfoMessage(\"SSN pattern found → masking\");\n            masked = masked.replace(ssnRegex, \"***-**-****\");\n        }\n\n        if (phoneRegex.test(desc)) {\n            gs.addInfoMessage(\"Phone number pattern found → masking\");\n            masked = masked.replace(phoneRegex, \"**********\");\n        }\n\n        // If changes were made, update the description\n        if (masked !== desc) {\n            current.description = masked;\n            gs.addInfoMessage(\"Final masked description: \" + masked);\n            gs.log(\"Masking rule triggered on record: \" + current.number, \"MaskingRule\");\n        } else {\n            gs.addInfoMessage(\"No sensitive data detected, nothing masked.\");\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Mask Sensitive Data in Description Field/readme.md",
    "content": "This script scans the description field of a record for patterns that resemble sensitive personal data and masks them to ensure privacy and compliance. It targets the following data types using regular expressions:\n\nCredit Card Numbers: Detects both continuous digits (13–16 digits) and spaced/dashed formats (e.g., 1234-5678-9012-3456).\nSocial Security Numbers (SSNs): Matches the standard US format (XXX-XX-XXXX).\nPhone Numbers: Identifies various formats including international and local styles.\n\nIf any of these patterns are found, the script replaces them with masked placeholders (e.g., ****-****-****-**** for credit cards) and updates the description field accordingly. It also logs messages to the system and displays info messages to notify users of the masking actions taken.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Move attachment from variable to record/README.md",
    "content": "**Scenario**:\n\nIn some catalog items, we might want to make attachments mandatory based on certain conditions.\nTo achieve this, we typically use an Attachment variable on the catalog item.\n\nWhen a user submits the catalog item, any attachments uploaded through this variable are stored in the sys_attachment\ntable with the table name set to the variable’s source — usually ZZ_YYsc_cat_item_producer.\nHowever, in certain cases, we might want these attachments to be associated directly with the RITM (sc_req_item) record instead of staying linked\nto the variable.\n\n**Solution**:\n\nWe can create an After Insert Business Rule on the sc_req_item table that automatically reassigns such attachments to the corresponding RITM.\n\nThis rule will run only for RITMs created from specific catalog items, as defined in the filter condition of BR, and retrieve the attachment record from the sys_attachment table using the attachment variable value. It will then update the table_name to 'sc_req_item'.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Move attachment from variable to record/Script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // List of variable names for which the attachment has to be moved to the record level.\n    var attachmentVars = ['attachment1', 'attachment2']; \n    \n    for (var i = 0; i < attachmentVars.length; i++) {\n        var varName = attachmentVars[i];\n\n        // Get the attachment sys_id from variable value\n        var attachmentSysId = current.variables[varName];\n\n        if (!attachmentSysId) {\n            gs.info(\"No attachment found in variable: \" + varName);\n            continue;\n        }\n\n        // Get attachment record using sys_id\n        var attGR = new GlideRecord('sys_attachment');\n        if (attGR.get(attachmentSysId)) {\n            gs.info('Moving attachment: ' + attGR.file_name);\n\n            // Update reference to link to the RITM\n            attGR.table_name = 'sc_req_item';\n            attGR.table_sys_id = current.sys_id;\n            attGR.update();\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Name Change Profile Update/README.md",
    "content": "This business rule is being used to update the live_profile record for a user if their first or last name changes. \nWhen a sys_user record is updated it runs a query against the live_profile table to match the document field with the sys ID of the user and then updates the name\nfield of the live_profile record with the name value from the sys_user record.\t\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Name Change Profile Update/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\n\n\t// This business rule is being used to update the live_profile record\n\t// for a user if their first or last name changes. When a sys_user record\n\t// is updated it runs a query against the live_profile table to match the\n\t// document field with the sys ID of the user and then updates the name\n\t// field of the live_profile record with the name value from the sys_user\n\t// record.\t\n\t\n\tvar userLiveProfile = new GlideRecord(\"live_profile\");\n\tuserLiveProfile.addQuery(\"document\", current.sys_id);\n\tuserLiveProfile.query();\n\tif (userLiveProfile.next()) {\n\t\tuserLiveProfile.name = current.name;\n\t\tuserLiveProfile.update();\n\t}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Notification/README.md",
    "content": "Trigger: This business rule runs after a new incident is inserted into the Incident table.\nCheck: It checks if the record is new using current.isNewRecord().\nUser Lookup: It retrieves the assigned user’s record using GlideRecord.\nMessage Preparation: It constructs a message indicating a new incident has been assigned.\nEvent Queue: Finally, it uses gs.eventQueue to send the notification event.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Notification/Send Notification on New Incident Creation.js",
    "content": "// Business Rule: Send Notification on New Incident Creation\n// When: After Insert and Table: Incident\n(function executeRule(current, previous /*null when async*/) {\n    // Only send notification for newly created incidents\n    if (current.isNewRecord()) {\n        var gr = new GlideRecord('sys_user');\n        gr.get(current.assigned_to);\n        // Prepare the notification message\n        var message = 'A new incident has been assigned to you: ' + current.number;\n        // Send the notification\n        gs.eventQueue('incident.new', current, gr.sys_id, message);\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Pass Attachments/read.md",
    "content": "This script copies attachments from a source record in another table to the current record (likely a case or task). \nIt ensures that attachments are preserved when creating or linking records across tables.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Pass Attachments/script.js",
    "content": "//This script copies attachments from a source record in another table to the current record (likely a case or task). It ensures that attachments are preserved when creating or linking records across tables.\n\n\n(function executeRule(current, previous /*null when async*/) {\n\nif(!current.source_record || !current.source_table) {\n\tgs.info('No source record found for' + current.number);\n\treturn;\n}\nvar sourceId= current.source_record.toString();\nvar sourceTable = current.source_table.toString();\n\ngs.info('coping attachments from' + sourceTable + '[' + sourceId +'] case' + current.number);\n\ntry{\nvar gsa = new GlideSysAttachment();\nvar copiedCount = gsa.copy(sourceTable, sourceId, current.getTableName(), current.getUniqueValue());\n\n\ngs.info('Attachments copied from' + copiedCount +'attachment from' + sourceTable + ' to Case' +  current.number);\n}catch (e) {\n\tgs.error('Attachment copied from case' +current.number + ': ' + e.message);\n}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Pass server info to client/README.md",
    "content": "This BR can be used to pass any server information to the client side using the g_scratchpad variable.\n\nBR Type:  Display business rule\n\nUsage Scenario: We want to apply some logic based\n    - on some property value stored at the server side.\n    - on the department/manager associated with the user.\n\n "
  },
  {
    "path": "Server-Side Components/Business Rules/Pass server info to client/passServerInfo.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n\t/* \n\t * All of the below variables will be avialable in the client script\n\t * under the g_scratchpad variable for use.\n\t */\n    g_scratchpad.css = gs.getProperty('css.base.color');\n    g_scratchpad.hasAttachments = current.hasAttachments();\n    g_scratchpad.managerName = current.caller_id.manager.getDisplayValue();\n\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Pdf Letter create/README.md",
    "content": "#Contribution\n\nThe Business Rule is triggered after an update on the (HR case)\"sn_hr_core_case\" table, specifically when the case state is set to \"Work in Progress\". This rule generates a PDF letter based on the trigerred conditions.\n\nDocument Template created seperately. Document Template Name - PDF Letter Employee.The Document Template Sys ID is passed within the script, and the corresponding document template has been created separately (refer to the attached screenshot for reference).\nDocument Template -> All Document Templates - > New\nAs per the script, the PDF letter is generated and named using the HR case subject's name — for example:\n\"Letter: \" + empName + \".pdf\".\n\nFunctionality -\nWhen a fulfiller changes the case state to \"Work in Progress\", the PDF letter is automatically generated and attached to the HR case record.\n\nBusiness Rule Description -\n\nName - pdf Letter generation\nTable - sn_hr_core_case\nCondition - state is \"work in Progress\"\nUpdate - Check the box\nWhen -select after\n\nThis BR will prevent the duplicate letter generation for multiple updates in work in Progress state.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Pdf Letter create/letter.js",
    "content": "\n(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n    var recordId = current.sys_id.toString();  \n    var empName = current.subject_person;\n\n    var templateId1 = gs.getProperty(\"sn_hr_core.letter\"); // Document Template sysid\n\n\n    var pdfFileName1 = 'Letter:' +empName+ '.pdf'; //letter  name\n\n\n    gs.info('[PDF Generation] HRC Number ' + recordId);\n\n    try {\n\n      var attachmentGR = new GlideRecord('sys_attachment');             //if any pdf letter attached\n        attachmentGR.addQuery('table_name', 'sn_hr_core_case');\n        attachmentGR.addQuery('table_sys_id', recordId);\n        attachmentGR.addQuery('file_name', pdfFileName1);\n        attachmentGR.query();\n\n        if (!attachmentGR.hasNext()) {               //check for new letter\n            var docGen1 = new sn_doc.GenerateDocumentAPI();\n            docGen1.generateDocumentForTask(recordId, templateId1, pdfFileName1); // genereate pdf letter\n\n            gs.info('[PDF Generation] PDF attached to HRC: ' + recordId);\n        }\n    }\n\n\n     catch (ex) {\n        gs.error('[PDF Generation] Failed: ' + ex.message);\n    }\n    current.setWorkflow(false);\n}\n\n\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Preserve enhancement when deleting project/Preserve enhancement when deleting project.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\t\n\t\t// Chris E; 4 Oct 2019\n\t\t// If child enhancement(s) of a project originated from a demand record, set the enhancement parent to NULL from project record\n\t\t// so that the enhancement is not cascade deleted when it's parent project is deleted and is still available via the original demand record\n\t\t\n\t\ttry {\n\t\t\tvar enhGR = new GlideRecord('rm_enhancement');\n\t\t\tenhGR.addQuery('parent', current.getValue('sys_id'));\n\t\t\tenhGR.query();\n\t\t\twhile(enhGR.next()){\n\t\t\t\tenhGR.setValue('parent', 'NULL');\n\t\t\t\tenhGR.update();\n\t\t\t}\n\t\t} catch (e){\n\t\t\tif(gs.isInteractive() && gs.hasRole('admin')){\n\t\t\t\tgs.addInfoMessage('Preserve enhancement on delete - '+ e.message);\n\t\t\t}\n\t\t\tgs.error(e.message);\n\t\t}\n\t\t\n\t})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Preserve enhancement when deleting project/README.md",
    "content": "Stops the related enhancement records being cascade deleted from a project record when they also belong to a demand (i.e. we're deleting the project, but want to re-evaluate the original demand with it's associated enhancements). IF you want all enhancements to be preserved then just adjust the filter conditions to suit.\n\nTable: pm_project\n\nWhen: before\n\nDelete: true\n\nFilter: Demand is not empty AND Demand is not \"\"\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent Duplicate CI (Configuration Item) Names/README.md",
    "content": "This Script under code_snippet.js block users from saving a CI with a duplicate name but different class.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent Duplicate CI (Configuration Item) Names/code_snippets.js",
    "content": "// Business Rule to Block users from saving a CI with a duplicate name but different class\n// Before Insert Business Rule on cmdb_ci table\n(function executeRule(current, previous /*null when async*/) {\n\n    var ci = new GlideRecord('cmdb_ci');\n    ci.addQuery('name', current.name);\n    ci.addQuery('sys_id', '!=', current.sys_id);\n    ci.query();\n\n    if (ci.next()) {\n        gs.addErrorMessage('A Configuration Item with this name already exists!');\n        current.setAbortAction(true);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent Duplicate Incident Creation within 24 Hours/BeforeBRScript.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Only run for active incidents\n    if (!current.caller_id || !current.short_description)\n        return;\n\n    // Create a GlideRecord on the incident table\n    var gr = new GlideRecord('incident');\n    gr.addQuery('caller_id', current.caller_id);  // Same caller\n    gr.addQuery('short_description', current.short_description);  // Same issue text\n    gr.addQuery('sys_created_on', '>=', gs.hoursAgoStart(24));  // Within last 24 hours\n    gr.addQuery('state', '!=', 7);  // Exclude closed incidents\n    gr.query();\n\n    if (gr.next()) {\n        // Stop insert and show an error\n        gs.addErrorMessage(\"A similar incident has already been raised within the last 24 hours: \" + gr.number);\n        current.setAbortAction(true);  // Prevent record creation\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent Duplicate Incident Creation within 24 Hours/README.md",
    "content": "Prevent Duplicate Incident Creation within 24 Hours \n\n1. Write a Business Rule - Before Insert\n2. Select the Incident Table\n3. Only run/execute for all the active incidents\n4. By Gliding the Incident Table will get the caller_id, short_description for checking the current caller and text provided for the short description\n5. Querying the Incident Table as created within 24 Hours and excluding the closed incidents\n6. Stop insert and show an error message\n7. Prevent Incident record creation\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent Invalid User ID/README.md",
    "content": "\n#  Prevent Invalid User ID\n\n## Overview\nThis **ServiceNow Business Rule** prevents inserting or updating a record when:\n- `user_name` is missing or invalid.\n- Both `first_name` and `last_name` are missing or invalid.\n\n## Functionality Breakdown\n\n### 1. `isInvalid(value)`\n- Detects invalid values in user fields.\n- Returns `true` if:\n  - Value is `null`, `undefined`, or empty (`\"\"`)\n  - Value (after trimming spaces and lowering case) equals `\"null\"`\n\nExample:\n```javascript\nisInvalid(null);        // true\nisInvalid(\"\");          // true\nisInvalid(\"NULL\");      // true\nisInvalid(\"john\");      // false\n```\n\n### 2. `current.setAbortAction(true)`\n- Stops the record from being inserted or updated.\n- Used inside **Before Business Rules**.\n- Prevents saving invalid data to the database.\n\n### 3. `gs.addErrorMessage(\"...\")`\n- Displays a user-friendly error message at the top of the form.\n- Helps users understand *why* the save was blocked.\n\n\n##  Notes\n- Case-insensitive — handles \"null\", \"NULL\", \"Null\", etc.  \n- Works best in **Before Business Rules** to stop invalid data before saving.  \n- Adding `gs.addErrorMessage()` helps users understand the validation reason.\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent Invalid User ID/script.js",
    "content": "(function executeRule(current, previous) {\n    // Utility function to validate if a field is null, empty, or contains \"null\"/\"NULL\"\n    function isInvalid(value) {\n        return !value || value.toString().trim().toLowerCase() === \"null\";\n    }\n\n    // Abort action if the user_name field is invalid\n    if (isInvalid(current.user_name)) {\n        gs.addErrorMessage(\"User name is invalid\");\n        current.setAbortAction(true);\n    }\n\n    // Abort action if both first_name and last_name are invalid\n    if (isInvalid(current.first_name) && isInvalid(current.last_name)) {\n        gs.addErrorMessage(\"Either first name or last name must be provided\");\n        current.setAbortAction(true);\n    }\n\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent RITM to get closed/README.md",
    "content": "This is a code snippet which can be used in a Business rule to prevent the closure of RITM when there is any active catalog task attached to that RITM.\nBelow are the conditions of Business rule mostly suited.\nWhen: Before\nUpdate: True\n\nNote: This script is helpful for all the tables and their related tables. It can work same way by editing the table names for other parent child ralations as well.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent RITM to get closed/script.js",
    "content": "//This script is used to prevent an RITM to get closed if there is any active catalog task.\n(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\nvar scTask = new GlideRecord('sc_task');\n\tscTask.addActiveQuery();\n\tscTask.addQuery('request_item',current.sys_id);\n\tscTask.query();\n\tif(scTask.next()){\n\t\tcurrent.setAbortAction(true);\n\t}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent adding user to group if manager is inactive/README.md",
    "content": "\n# Prevent adding user to group\n\n**Use case** : Whenever any user is getting added to any group, if the group manager is inactive then it should prevent the adding of user to the group\n\n*info* : This method is to achieve the above use-case just with business rule\n\n**Solution** : Create a `Before` business rule on `sys_user_grmember` table with `insert` checkbox checked. Follow the script present in [Script.js](https://github.com/ServiceNowDevProgram/code-snippets/blob/main/Business%20Rules/Prevent%20adding%20user%20to%20group%20if%20manager%20is%20inactive/Script.js)\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent adding user to group if manager is inactive/Script.js",
    "content": "var user = new GlideRecord('sys_user');\nuser.addQuery('sys_id', current.group.manager.sys_id);\nuser.addQuery('active', 'false');\nuser.query();\nif(user.next())\n{\n    gs.info(\"Group Manager is inactive\");\n    current.setAbortAction(true);\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent closure if change tasks are open/Before_BR.js",
    "content": "function executeRule(current, previous /*null when async*/){\n  if(current.close_code){\n    var taskGR = new GlideRecord('change_task');\n    taskGR.addQuery('change_request', current.sys_id);\n    taskGR.addQuery('state', '!=', '3') // Adjust as needed\n    taskGR.query();\n\n  if (taskGR.hasNext()){\n    gs.addErrorMessage('You cannot close this change request until all change tasks are closed.');\n    current.setAbortAction(true); // Prevent saving the form\n   }\n }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent closure if change tasks are open/README.md",
    "content": "Prevent Closure if the change tasks are open\n\n1. Create a Before Business Rule.\n2. Applicable to Change Request Table.\n3. Use Before - Update Business Rule.\n4. Add filter conditions if required.\n5. Apply the Business Rule script.\n6. If the change request have the active change tasks are still open then we can't proceed with the submission.\n7. If required we can add few more query conditions.\n8. \n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent duplicate update sets/README.md",
    "content": "This Business Rule will help to prevent duplicate names for update set. And this will display error message when there is duplicate name found for update set name.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent duplicate update sets/preventDuplcateUpdateSets.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    \n    var updateSetName = current.name.toString().trim();\n    var grUpdateSet = new GlideRecord('sys_update_set');\n    grUpdateSet.addQuery('name', updateSetName);\n    grUpdateSet.addQuery('sys_id', '!=', current.sys_id);\n    grUpdateSet.query();\n    \n    if (grUpdateSet.hasNext()) {\n        gs.addErrorMessage('An update set with this name already exists. Please choose a different name.');\n        current.setAbortAction(true);\n    }\n    \n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent invalid fiscal period in cost plan breakdown/Prevent invalid fiscal period on cost plan breakdown.js",
    "content": "//before update\n//table cost_plan_breakdown\n//condition:\nfiscal_periodVALCHANGES^fiscal_period.fiscal_end_date_timeMORETHANcost_plan.end_fiscal_period.fiscal_end_date_time@hour@after@0^ORfiscal_period.fiscal_start_date_timeMORETHANcost_plan.start_fiscal_period.fiscal_start_date_time@hour@before@0^EQ\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Prevent invalid fiscal period in cost plan breakdown/README.md",
    "content": "Simple business rule that will ensure a cost plan breakdowns fiscal period will remain within the start and end period of the parent cost plan. If a cost plan breakdown is added ousdide of the start/end fiscal periods then rollups do not work correctly.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Preventing Recursive Updates from Integrations/README.md",
    "content": "**Business Rule** \n\nTable : Any Custom Table or (Incident,Problem , Change)...\nActive : true\nWhen : before \nUpdate : true\norder : 100 \n\ncondition : gs.getUser().getUserName() == 'integration.user/Web services User'\n\nThe rule must only run when the update is coming from the dedicated integration user to avoid impacting manual user updates, Can Update Fields to check as Required \n\nThis business rule is designed to prevent unnecessary and redundant updates to records that are synchronized with external systems (e.g., Jira, Azure DevOps, or a custom application) via integration / E bonding \n\nThis rule executes a check to ensure that the fields critical to the integration (Work Notes, Comments, State) have genuinely changed before allowing the update to process.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Preventing Recursive Updates from Integrations/prevent_continous_update.js",
    "content": "(function executeRule(current, previous) {\n\n    if (current.isNewRecord() || !current.isValidRecord() || current.operation() == 'delete') {\n        return;\n    }\n    var fieldsToCheck = ['work_notes', 'comments', 'state'];\n    var allFieldsSame = true;\n    for (var i = 0; i < fieldsToCheck.length; i++) {\n        var field = fieldsToCheck[i];\n        if (current.changes.call(current, field) && current[field].toString() !== previous[field].toString()) {\n            allFieldsSame = false;\n            break; \n        }\n    }\n    if (allFieldsSame) {\n        gs.info('BR skipped redundant integration update for ' + current.sys_class_name + ': ' + current.number);\n        current.setAbortAction(true);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Previous Approval Check/README.md",
    "content": "# Previous Approval Check\n### Business rule to check for any approval requests by previous approvers and auto approve them.  If an approver had already approved a request, and is later asked to approve the same request again, usually in a different capacity, then the approval will be set as approved by the system automatically. \n\nBusiness rule settings:\n- Table: sc_req_item\n- Advanced: True (Checked)\n- When: async\n- Order: 1000\n- Insert: False (Unchecked)\n- Update: True (Checked)\n- Delete: False\n- Query: True\n- Conditions: \n  - Approval is Requested\n  - Approval history changes\n\n![image](https://user-images.githubusercontent.com/25243029/136676161-0a842f97-0721-4f28-8b20-a83f52bed949.png)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Previous Approval Check/previous_approval_check.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n\n    var appr = []; // initiate an array for use later\n    var gra = new GlideRecord(\"sysapproval_approver\"); // check if any approved approval records exist for the current request\n    gra.addQuery('sysapproval', current.getValue('sys_id'));\n    gra.addQuery('state', 'approved'); \n    gra.query();\n    if (!gra.hasNext()) { // if no approved records, exit script\n        return;\n    } else {\n        while (gra.next()) { // if there are approved records, add them to an array\n            appr.push(' ' + gra.approver.getDisplayValue());\n        }\n    }\n    appr = appr.toString(); // convert the array to a string for indexing\n    var grr = new GlideRecord(\"sysapproval_approver\"); // check for any pending approval requests\n    grr.addQuery('sysapproval', gra.getValue('sysapproval'));\n    grr.addQuery('state', 'requested');\n    grr.query();\n    while (grr.next()) { // compare the requested approvals against the list of approved records\n        if (appr.indexOf(grr.approver.getDisplayValue()) > -1) {  // if one exists, set it's state to approved and add a comment\n            grr.comments = grr.approver.getDisplayValue() + ' already approved this request during a previous approval step.  System will now auto approve on their behalf.';\n            grr.setValue('state', 'approved');\n            grr.update();\n            break;\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/QueryBR-restrict users to see their company records/README.md",
    "content": "This script is used to restrict logged in users to see only their company records. This is a generic script and can be used in any table where there is a Company field present\nwhich is associated to the User table.\nQuery BR contains below specifications:\n1. Advanced = true\n2. When = Before\n3. Query = true\n"
  },
  {
    "path": "Server-Side Components/Business Rules/QueryBR-restrict users to see their company records/script.js",
    "content": "// This is a before Query BR script.\n(function executeRule(current, previous /*null when async*/ ) {\n\n    // Add your code here\n    var enq = 'company=javascript:gs.getUser().getCompanyID()'; // query to get the records of logged in user's company. \n    current.addEncodedQuery(enq); // add the encoded query so that it can be applied in the query BR\n    \n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RITM Assignment Sync/Populate_Assigned _To_on_RITMs_for_Specific_Catalog_Item.js",
    "content": "// create business rule on sc_request table \n(function executeRule(current, previous /*null when async*/ ) {\n\n    var grRITM = new GlideRecord('sc_req_item');\n    grRITM.addQuery('request', current.sys_id);\n    grRITM.addQuery(\"cat_item.sc_catalogs\", \"791b6df48798d9100072960d3fbb35be\"); //sys_id of catalog \n    grRITM.query();\n    while (grRITM.next()) {\n        grRITM.assigned_to = current.assigned_to;\n        grRITM.update();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RITM Assignment Sync/README.md",
    "content": "# Script Explanation:\nThis script is written for a Business Rule in ServiceNow. The purpose of this rule is to assign the same assigned_to value (typically a user) from the request to all related requested items (RITMs)\nin the same catalog for a specific catalog item.\nThis script could be used in scenarios where, once a request is assigned to a particular user (or group), you want all the individual requested items (RITMs) tied to that request to also automatically be assigned to the same user. \nThis ensures consistency in assignment across the items in a request.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RITM state change/README.md",
    "content": "# Create before insert/update business rule\nbusiness rule on sc_req_item table and give condition if state changes to 2 OR cat_item.sc.catalogs is \"yourchoice\"  and run this script.\n\nThis business is to set state on sc_task table when state changes on sc_req_item based on condition. \n"
  },
  {
    "path": "Server-Side Components/Business Rules/RITM state change/Related_task_state_update.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n    var OPEN_STATE = 1;\n    var WORK_IN_PROGRESS_STATE = 2;\n\n    var gr = new GlideRecord(\"sc_task\");\n    gr.addQuery('request_item', current.sys_id);\n    gr.addQuery('state', OPEN_STATE);  // Only fetch tasks in Open state\n    gr.query();\n\n    while (gr.next()) {\n        gr.setValue('state', WORK_IN_PROGRESS_STATE);  // Update the task to Work in Progress\n        gr.update();\n        gs.log('Task ' + gr.number + ' updated from Open to Work in Progress');\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RITM_to_SCTASK/README.md",
    "content": "This business rule script copies the additional comments from RITM to all associated SCTASKs when additional comments are changed on the RITM.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RITM_to_SCTASK/RITM_to_SCTASK.js",
    "content": "/*\nTable: sc_req_item\nWhen to run: after update\nCondition: current.comments.changes() && gs.isInteractive() \n*/\n\nvar sc_task_gr = new GlideRecord('sc_task');\n\n//Retrieve all the SCTASKs with the current RITM is aprent.\nsc_task_gr.addQuery('request_item',current.sys_id);\nsc_task_gr.query();\n\nwhile(sc_task_gr.next()){\n  sc_task_gr.comments = current.comments;\n  sc_task_gr.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Randomly distrubite events between custom queues/DistrubuteEvents.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\t\n  //define the queues array\n\tvar event_queues = ['custom_queue_1', 'custom_queue_2','custom_queue_3','custom_queue_4']\n\n  //initialize glide record and do some filtering if necessary\n\tvar grSomeContainer = new GlideRecord('container_table');\n\tgrSomeContainer.addQuery('column', 'value'); //do some filtering if necessary\n\tgrSomeContainer.query();\n\n  //for each one of the elements log an event with 5th parameter distributing it a specific queue from the array above\n\twhile (grSomeContainer.next()) {\n\t\tgs.eventQueue('scope.event_name', grSomeContainer,  null, null, event_queues[Math.floor(Math.random()*event_queues.length)]);\n\t}\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Randomly distrubite events between custom queues/README.md",
    "content": "This business rule helps generate multiple events, which are randomly and almost perfetly even distributed by a specified number of custom queues. \n"
  },
  {
    "path": "Server-Side Components/Business Rules/ReRank item/README.md",
    "content": "Business rule for on after to re-rank a field spaced by 10 in each entry\nit updates itself and any larger numbered rank item to keep them all in sync\n"
  },
  {
    "path": "Server-Side Components/Business Rules/ReRank item/rerank.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t\n\treRank('rank', current.rank); \n\t\n})(current, previous);\n\nfunction reRank(rank, currentRank) {\n    var roundedRank Math.round(parseInt(currentRank) / 10) * 10; \n\n\tvar grRank = new GlideRecord(current.getTableName()); \n\tgrRank.addNotNullQuery(rank); \n    grRank.addQuery(rank, '>=', currentRank);\n\tgrRank.orderBy(rank); \n\tgrRank.query();\n\t\n\twhile (grRank.next()) {\n\t\tgrRank.autoSysFields(false); \n\t\tgrRank.setWorkflow(false); \n\t\tgrRank.rank = currentRank; \n\t\tgrRank.update(); \n\t\tcurrentRank += 10; \n\t}\n}"
  },
  {
    "path": "Server-Side Components/Business Rules/Read Workspace URL/README.md",
    "content": "Display BR that reads the caller_id parameter from the Workspace URL (agent or sow) for creating a new record/interaction and seearches \nfor the corresponding user to set it in the g_scratchpad to be used as default value in the new Interaction form. \neg url https://instance-name.service-now.com/now/sow/record/interaction/-1_uid_1/params/query/active%3Dtrue%5Ecaller_id=<email>\n\nThe scratchpad can be used in an onLoad client script like so:\n   if (g_form.isNewRecord())\n\t\tg_form.setValue('opened_for', g_scratchpad.caller_id);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Read Workspace URL/ReadWS_URL.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\t// for new record\n    if (current.isNewRecord()) {\n        // read the URL\n\t\tvar txn = GlideTransaction.get();\n\t\tif (!txn)\n\t\t\treturn;\n\t\tvar request = txn.getRequest();\n\t\tif (!request)\n\t\t\treturn;\n\t\tvar referer = decodeURIComponent(request.getHeader(\"Referer\"));\n\t\t\n\t\tif (GlideStringUtil.nil(referer))\n\t\t\treturn;\n\t\t// use regular expression to read the parameters from the referer - we just need the last group\t\n\t\tvar matches = referer.match(/\\/(agent|sow)\\/(chat|record\\/interaction)\\/-1_uid_1\\/params\\/query\\/(.*)/);\n\t\tif (!matches || matches.length < 4)\n\t\t\treturn;\t\t\n\t\t// if there is no caller_id in the query parameters, dont do anything\n\t\tif (matches[3].indexOf('caller_id') < 0)\n\t\t\treturn;\n\t\t// parse the parameters and when encountering caller_id, get the user's unique identifier - in this case the email address - and set it in the scratchpad\n\t\tvar params = matches[3].split('^');\n\t\tfor (var i=0; i<params.length;i++) {\n\t\t\t// the parameters are key=name value pairs\n\t\t\tvar param = params[i].split('=');\t\t\t\n\t\t\tif (param.length == 2 && param[0] == 'caller_id') {\n\t\t\t\tvar grUser = new GlideRecord('sys_user');\n\t\t\t\tif (grUser.get('email', param[1])) {\n\t\t\t\t\tg_scratchpad.caller_id = grUser.getUniqueValue();\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\t}\n})(current, previous);\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Reassign Tasks When Assigned User is Inactive/readme.md",
    "content": "## Purpose\nAutomatically reassigns tasks or incidents when the currently assigned user becomes inactive.\nThis ensures that no work item stays unattended due to user deactivation, termination, or role changes, maintaining operational continuity and SLA compliance.\n## Tables Applicable:\nAny task-based table, such as incident, problem, change_request, etc.\n## Implementation Details\nTable: sys_user\nTrigger: Business Rule – After Update\nCondition: current.active == false && previous.active == true\nPurpose: Trigger logic only when a user becomes inactive.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Reassign Tasks When Assigned User is Inactive/script.js",
    "content": "(function executeRule(current, previous) {\n\n    // Trigger only when user becomes inactive\n    if (previous.active && !current.active) {\n        gs.info(\"User \" + current.name + \" became inactive. Reassigning their open tasks...\");\n\n        // GlideRecord to find open tasks assigned to the user\n        var taskGR = new GlideRecord('task');\n        taskGR.addQuery('assigned_to', current.sys_id);\n        taskGR.addQuery('state', '!=', 3); // Exclude closed tasks\n        taskGR.query();\n\n        while (taskGR.next()) {\n            // Add a work note to notify assignment group\n            taskGR.work_notes = \"Assigned user '\" + current.name + \"' is inactive. Please take necessary action.\";\n            taskGR.update();\n\n            gs.info(\"Work note added to task \" + taskGR.number);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Recursive logic/README.md",
    "content": "Using this Recursive BR, when you select the CMDB CI on incident then it would automatically add all level of affected CIs and when you change the new cmdb_ci then its corresponding affected CIs will get added and old affected CIs will get deleted.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Recursive logic/Recursive BR.js",
    "content": "removePreviousCI();\n\nvar ci = [];\nvar ret;\nvar array = new ArrayUtil();\n\n//call checkingCi funciton\ncheckingCI(current.cmdb_ci);\n\n//••••••••••••••••••••••••••••••••••• Adding Infected CI•••••••••••••••••••••••••••••••\nfunction checkingCI(id) {\n  //we push the first value\n  ci.push(id);\n  var gr = new GlideRecord('cmdb_rel_ci');\n  gr.addQuery('child', id);\n  gr.query();\n  while (gr.next()) {\n    ret = array.unique(recursive(gr.parent.toString()));\n  }\n  if (ret) {\n    callInfectedCI(ret, current);\n  } else {\n    var rec = new GlideRecord('task_ci');\n    rec.addQuery('task', current.sys_id);\n    rec.addQuery('ci_item', current.cmdb_ci);\n    rec.query();\n    if (!rec.next()) {\n      rec.initialize();\n      rec.task = current.sys_id;\n      rec.ci_item = current.cmdb_ci;\n      rec.insert();\n    }\n  }\n}\n\nfunction recursive(id) {\n  ci.push(id);\n\n  var gr = new GlideRecord('cmdb_rel_ci');\n  gr.addQuery('child', id);\n  gr.query();\n  while (gr.next()) {\n    recursive(gr.parent.toString());\n  }\n\n  return ci;\n}\nfunction callInfectedCI(cmdb, incident) {\n  for (var x in cmdb) {\n    var gr = new GlideRecord('task_ci');\n    gr.newRecord();\n    gr.task = current.sys_id.toString();\n    gr.ci_item = cmdb[x];\n    gr.insert();\n\n    gs.info('Infected CI ' + gr.sys_id());\n  }\n}\n\n//•••••••••••••••••••••••Removal of infected CI••••••••••••••••••••••••••••••••••\n\nfunction removePreviousCI() {\n  // Delete Affected CI records for this task and previous CI\n  var rec = new GlideRecord('task_ci');\n  rec.addQuery('task', current.sys_id);\n  rec.query();\n  while (rec.next()) {\n    rec.deleteRecord();\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Reduce syslog query/README.md",
    "content": "# Description\n\nUnexperienced users open large tables like `syslog`, `sysevent` or any CMDB table with several millions of records just by entering TABLENAME.list into the application navigator - wondering why it takes minutes to load the results.\n\nThe reason: Without any reduction to a time window a so-called \"full table scan\" is performed behin the scenes to determine the the number of records in that table. \n\nTo protect the user from having their session locked for several minutes, my business rule adds a portion to the query that restricts search results to records created in the last 15 minutes.\n\nIf the user already added any condition for the `sys_created_on` field then no query changes are done.\n\n# Business Rule\n\n**Table:** `syslog`\n\n**When:** Query\n\n**Condition:** `gs.getSession().isInteractive()`\n\n# Result\n\n![Result](./business_rule.png)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Reduce syslog query/reduce_syslog_query.js",
    "content": "(function executeRule(current) {\n\n\tvar _strCurrentTable = (current.getTableName() || '').toString();\n\tvar _strCurrentURL   = (gs.action.getGlideURI() || '').toString();\n\t\n\t//only continue with list views in the Classic UI\n\tif (!_strCurrentURL.startsWith(_strCurrentTable + '_list.do')) {\n\t\treturn;\n\t}\n\t\n\tvar strEncodedQuery  = current.getEncodedQuery() || '';\n\tvar numStartPosition = \n\t\tstrEncodedQuery.endsWith('ORDERBYDESCsys_created_on') || \n\t\tstrEncodedQuery.endsWith('ORDERBYsys_created_on')  ? \n\t\t\tstrEncodedQuery.length - 15 : \n\t\t\tstrEncodedQuery.length;\n\t\n\tif (strEncodedQuery.lastIndexOf('sys_created_on', numStartPosition) === -1) {\n\t\tcurrent.addEncodedQuery(\n\t\t\t'sys_created_onONLast 15 minutes@javascript:gs.beginningOfLast15Minutes()' +\n\t\t\t'@javascript:gs.endOfLast15Minutes()'\n\t\t);\n\t\t\t\n\t\tgs.addInfoMessage(\n\t\t\t'For performance reasons your query was extended by an expression to narrow ' +\n\t\t\t'down the result list to entries which have been created in the last 15 min. ' +\n\t\t\t'You can override this setting by using a dedicated query expression for the ' +\n\t\t\t'\"Created on\" field. But be careful: If the query result set is very large, ' +\n\t\t\t'it will cause a performance decrease!'\n\t\t);\n\t}\n\n})(current);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Replace KB Author with Manager/README.md",
    "content": "This business rule help to replace the KB author of knowledge article to Knowledge base manager, if the author of that article leaves the organization. This is a kind of logic which helps to assign the knowledge article to knowledge base manager.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Replace KB Author with Manager/Replace Script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    var test_kb_manager;\n    var test_arr_kb_manager;\n    if (active == false) {  // This will check if user is inactive.\n        var kb_gr = new GlideRecord(\"kb_knowledge\");\n        kb_gr.addEncodedQuery(\"author=\" + current.sys_id);  // This will query the knowledge article.\n        kb_gr.query();\n        while (kb_gr.next()) {\n            if (test_kb_manager != \"\") {\n                test_kb_manager = kb_gr.kb_knowledge_base.kb_manager;\n                test_arr_kb_manage = test_kb_manager.split(\",\");  // This will seperate the list of manager by (,).\n                kb_gr.author = test_arr_kb_manager[0];  // This will assign the article author to knowledge manager.\n                kb_gr.update();\n\n            }\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Restrict Service Account to Query data/README.md",
    "content": "//The business rule is on [incident] table and 'when to run' -> before + query\n//The table could be replaced with other Servicenow tables according to the requirements.\n\nvar restrictedUserSysId = gs.getProperty('restricted.service.account.id');\n//The script retrieves the sys_id of the restricted user from a system property named 'restricted.service.account.id' and type is String and value contains the sysid of user account. This property stores the unique sys_id of the service account user whose access is being limited.\n\nif (gs.getUserID() === restrictedUserSysId):\n//The code checks whether the currently logged-in user (gs.getUserID()) matches the restricted user’s sys_id. If the logged-in user is the restricted service account, the script proceeds to limit the records they can access.\nCalculating the Date 10 Years Ago:\n\nvar tenYearsAgoFromToday = new GlideDateTime();\ntenYearsAgoFromToday.addYears(-10);\n//A new GlideDateTime object is created to get the current date and time. The method addYears(-10) is used to calculate the date exactly 10 years ago from today.\nLimiting the Query to the Last 10 Years:\n\ncurrent.addQuery('sys_created_on', '>=', tenYearsAgoFromToday);\n//The script adds a condition to the query, restricting the results to records where the sys_created_on date is greater than or equal to the calculated date (10 years ago). This effectively limits the records to those created in the last 10 years.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Restrict Service Account to Query data/restrictedDataQuery.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // Calling the property which contains the sysId of restricted user account\n    var restrictedUserSysId = gs.getProperty('restricted.service.account.id'); //Propery 'restricted.service.account.id' is having the userId of Service Account\n\n    // Checking if the user who is querying is same as the restricted user\n    if (gs.getUserID() === restrictedUserSysId) {\n\t\t\n        // Calculating date 10 years ago from current/today date\n        var tenYearsAgoFromToday = new GlideDateTime();\n        tenYearsAgoFromToday.addYears(-10);\n\n        //Limit records to the last 10 years\n        current.addQuery('sys_created_on', '>=', tenYearsAgoFromToday);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RoleUpdateOnGroupManagerChange/README.md",
    "content": "This business rule will update the group manager role on group manager change on Insert, Update, Delete\nSteps:\n  1. When new manager is updated from for the current group\n  2. Current role of the manager will be updated and removed\n  3. New Manager detail provided will be validated and it will assign the role to new manager \n  4. Configuration Details:\n    - When to run:\n        - After\n        - Order:100\n        - Operation: Insert, Update, Delete\n     - Condition:\n          current.manager.changes() || current.operation() == \"delete\"\n     - Table: Group [sys_user_group]\n"
  },
  {
    "path": "Server-Side Components/Business Rules/RoleUpdateOnGroupManagerChange/script.js",
    "content": "/**\n HACKTOBERFEST2022\n Description: Adds the role_delegator role to a user when made the manager of a group.\n              Removes the role when removed as manager of a group.\n**/ \n//On Change of CUrrent Group Manager\nif (current.operation() == \"delete\") { \n  removeRoleFromCurrentManager();\n} else {\n  processGroupManagerRole();\n}\n\nfunction removeRoleFromCurrentManager() {\n  var role = new GlideRecord(\"sys_user_role\");\n  if (!role.get(\"name\",\"role_delegator\")) {\n    gs.log(\"Cannot grant or remove role_delegator role because it does not exist\");\n    return;\n  }\n\n  var gr = newGlideRecord(\"sys_user_has_role\");\n  if (!current.manager.nil()) {\n    gr.initialize();\n    gr.addQuery(\"user\", current.manager);\n    gr.addQuery(\"role\", role.sys_id);\n    gr.addQuery(\"granted_by\", current.sys_id);\n    gr.query();\n    if (!gr.hasNext()) {\n      gs.log(current.manager.getDisplayValue() + \" did not have the role_delegator role for the \" + current.name + \" group - not removing\");\n    } else {\n      while (gr.next()) {\n        gr.deleteRecord();\n      }      \n    }\n  }\n}\n\nfunction processGroupManagerRole(user) {\n  var role = new GlideRecord(\"sys_user_role\");\n  if (!role.get(\"name\",\"role_delegator\")) {\n    gs.log(\"Cannot grant or remove role_delegator role because it does not exist\");\n    return;\n  }\n\n  // add role to new manager\n  var gr = new GlideRecord(\"sys_user_has_role\");\n  if (!current.manager.nil()) {\n    gr.addQuery(\"user\", current.manager);\n    gr.addQuery(\"role\", role.sys_id);\n    gr.addQuery(\"granted_by\", current.sys_id);\n    gr.query();\n    if (gr.next())\n      gs.log(current.manager.getDisplayValue() + \" already has the role_delegator role for the \" + current.name + \" group - not adding\");\n    else {\n      gr.initialize();\n      gr.user = current.manager;\n      gr.role = role.sys_id;\n      gr.granted_by = current.sys_id;\n      gr.inherited = false;\n      gr.insert();\n      gs.addInfoMessage(gs.getMessage(\"role_delegator role granted to\") + \" \" + \n         current.manager.getDisplayValue() + \" \" + gs.getMessage(\"in\") + \" \" + current.name + \n         \" \" + gs.getMessage(\"group\"));\n    }\n  }\n\n  // remove role from old manager\n  if (!previous.manager.nil()) {\n    gr.initialize();\n    gr.addQuery(\"user\", previous.manager);\n    gr.addQuery(\"role\", role.sys_id);\n    gr.addQuery(\"granted_by\", current.sys_id);\n    gr.query();\n    if (!gr.hasNext())\n      gs.log(previous.manager.getDisplayValue() + \" did not have the role_delegator role for the \" + current.name + \" group - not removing\");\n    else {\n      while (gr.next())\n        gr.deleteRecord();\n      gs.addInfoMessage(gs.getMessage(\"role_delegator role removed from\") + \" \" + \n         previous.manager.getDisplayValue() + \" \" + gs.getMessage(\"in\") + \" \" + current.name + \n         \" \" + gs.getMessage(\"group\"));\n    }\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Round Robin for Incident Assignment/README.md",
    "content": "UseCase - \n\n\n\nWhen a new Incident is created, the system should automatically assign it to the next available member of the assignment group — in a Round Robin fashion (i.e., distributing incidents evenly among all group members).\n\nIt will also work for New addition of new user. and distribute the incident equally to all the users.\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Round Robin for Incident Assignment/Round Robin method for Incident Assignment.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\tvar agg = new GlideAggregate('incident');\n    \tagg.addQuery('assignment_group', current.assignment_group);\n    \tagg.addAggregate('count');\n    \tagg.query();\n\n\tvar ga = new GlideAggregate('sys_user_grmember');\n    \tga.addQuery('group', current.assignment_group);\n    \tga.addAggregate('count');\n    \tga.query();\n\n\tif (agg.next() && ga.next()) {\n        \tif (Number(agg.getAggregate('count')) <= Number(ga.getAggregate('count')) {\n\n            \t\tvar grmem = new GlideRecord('sys_user_grmember');\n            \t\tgrmem.addQuery('group', current.assignment_group);\n            \t\tgrmem.query();\n\t\n            \t\twhile (grmem.next()) {\n\n                \t\tvar inc = new GlideRecord('incident');\n                \t\tinc.addQuery('assignment_group', grmem.group);\n                \t\tinc.addQuery('assigned_to', grmem.user);\n                \t\tinc.query();\n\n                \t\tif (!inc.hasNext()) {\n                    \t\t\tcurrent.assigned_to = grmem.user;\n                    \t\t\tcurrent.update();\n                    \t\t\treturn;\n                \t\t}\n            \t\t}\n\n        \t} else {\n            \t\tvar min_assignement_group_count, rec_sys_id = null;\n            \t\tvar count = new GlideAggregate('incident');\n            \t\tcount.addQuery('assignment_group', current.assignment_group);\n            \t\tcount.groupBy('assigned_to');\n\t\t        count.addNotNullQuery('assigned_to');\n            \t\tcount.addAggregate('count');\n            \t\tcount.query();\n\n            \t\tif (count.next()) {\n                \t\tmin_assignement_group_count = Number(count.getAggregate('count'));\n                \t\trec_sys_id = count.getValue('assigned_to');\n            \t\t}\n\n            \t\tcount.query();\n\n            \t\twhile (count.next()) {\n                \t\tvar countfinal = Number(count.getAggregate('count'));\n                \t\tif (countfinal < min_assignement_group_count) {\n                    \t\tmin_assignement_group_count = countfinal;\n                    \t\trec_sys_id = count.getValue('assigned_to');\n                \t}\n            }\n\n            if (rec_sys_id == null) {\n                var gr_inc = new GlideRecord('sys_user_grmember');\n                gr_inc.addQuery('group', current.assignment_group);\n                gr_inc.query();\n                if (gr_inc.next()) {\n                    rec_sys_id = gr_inc.user;\n                }\n            }\n\n            current.assigned_to = rec_sys_id;\n            current.update();\n        }\n\n    }\n\t\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/SCTASK_to_RITM/README.md",
    "content": "This business rule script copies the additional comments from SCTASK to all associated RITM when additional comments are changed on the SCTASK.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/SCTASK_to_RITM/SCTASK_to_RITM.js",
    "content": "/*\nTable: sc_task\nWhen to Run: after update\nCondition: current.comments.changes() && gs.isInteractive()\n*/\n\n//Script to update comments on RITM.\n\nvar ritmGr = new GlideRecord('sc_req_item');\nif(ritmGr.get(current.request_item.sys_id)){\n  ritmGr.comments = current.comments;\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Set program business duration/PPM_Utils.js",
    "content": "/***\n     * getProgramDate      - Finds min/max planned start for a program from child projects and program tasks and sets planned start/end on program\n     *\n     * @param recordGr     - gliderecord for passed in record that has link to a program\n     * @param pgmGetField  - the field that relates recordGr to a program\n     * @return             - NA\n     ***/\n    setProgramDates: function(recordGr, pgmGetField) {\n\n        var updated = false;\n        var pgmGr = new GlideRecord('pm_program');\n        pgmGr.get(recordGr.getValue(pgmGetField));\n\n        // find out of the program has child records that set it's planned start/end dates\n        if (pgmGr.getValue('sys_class_name') == 'pm_program') {\n            var children = this._isProgramChildren(pgmGr.getValue('sys_id'));\n        }\n\n        // find the minimum start date for the program\n        var minDate = this._getProgramDate(pgmGr.getValue('sys_id'), 'start_date');\n        if (minDate.compareTo(pgmGr.getDisplayValue('start_date')) != 0) { // the new task date is different to current program start\n            pgmGr.setDisplayValue('start_date', minDate);\n            updated = true;\n        }\n\n        // if it has changed then check if it is the lowest program date\n        var maxDate = this._getProgramDate(pgmGr.getValue('sys_id'), 'end_date');\n        if (maxDate.compareTo(pgmGr.getDisplayValue('end_date')) != 0) { // the new project end date is different to current program end date\n            pgmGr.setDisplayValue('end_date', maxDate);\n            updated = true;\n        }\n\n        // if min or max date has changed then update the program record\n        if (updated) {\n            // if the dates have changed, re-calculate the business duration and update program record\n            var pgmBusDuration = this._getBusinessDuration(pgmGr.start_date, pgmGr.end_date);\n            if (pgmBusDuration) {\n                pgmGr.business_duration = pgmBusDuration;\n            }\n            pgmGr.update();\n        }\n    },\n\n/***\n     * _getProgramDate - gets either min or max start/end date for a program\n     *\n     * @param pgmSysId - sys_id of a program record\n     * @param orderBy  - the field on the planned task table you want to order the records by - must be a date/time field\n     * @return         - glide date time of the order by field\n     ***/\n    _getProgramDate: function(pgmSysId, orderBy) {\n        var taskGr = new GlideRecord('planned_task');\n        taskGr.addEncodedQuery('sys_class_name=pm_project^ORsys_class_name=pm_program_task^parent=' + pgmSysId + '^ORtop_program=' + pgmSysId);\n        if (orderBy == 'start_date') {\n            taskGr.orderBy(orderBy);\n        } else taskGr.orderByDesc(orderBy);\n        taskGr.setLimit(1);\n        taskGr.query();\n        if (taskGr.next()) {\n            return taskGr.getDisplayValue(orderBy);\n        }\n    }, \n      \n /***\n     * _isProgramChildren - returns true if a program has child projects or program tasks\n     *\n     * @param programId - sys id of a program record\n     * @return          - duration in date time format from 1970\n     ***/\n    _isProgramChildren: function(programId) {\n\n        var taskGr = new GlideRecord('planned_task');\n        taskGr.getEncodedQuery('sys_class_name=pm_project^ORsys_class_name=pm_program_task^top_program=' + programId);\n        taskGr.setLimit(1);\n        taskGr.query();\n        if (taskGr.next()) {\n            return true;\n        } else return false;\n\n    },     \n      \n    /***\n     * _getBusinessDuration - uses min/max program dates to calculate business duration of a record\n     *\n     * @param startDate - date in glide date time format\n     * @param endDate   - date in glide date time format\n     * @return          - duration in date time format from 1970\n     ***/\n    _getBusinessDuration: function(startDate, endDate) {\n\n        var start = startDate.toString();\n        var end = endDate.toString();\n\n        /*excluding weekends date diff calculations*/\n        var dc = new DurationCalculator();\n        var sched = gs.getProperty('sn_hr_core.schedule_8_to_5_weekdays');\n        dc.setSchedule(sched);\n\n        // get the duration\n        var dur = Number(dc.calcScheduleDuration(start, end));\n        dur = Math.round(dur / 60 / 60 / 9);\n\n        // convert duration into days\n        var totalSeconds = dur * 86400;\n        var durationInMs = totalSeconds * 1000;\n        var gDuration = new GlideDuration(durationInMs);\n\n        return gDuration.getDurationValue();\n    },      \n"
  },
  {
    "path": "Server-Side Components/Business Rules/Set program business duration/README.md",
    "content": "OOTB the project records exclude weekends and public holidays when calculating the duration, whereas program does not exclude these things. \n\nThis business rule and associated script include address this issue and ensure that the duration field on the program record are calculated in the same way as projects.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Set program business duration/set pgm business duration.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // Set the program duration if it's got no child records\n\n    try {\n      \n        new PPM_Utils().setProgramDates(current, 'sys_id');\n\n    } catch (e) {\n        if (gs.isInteractive() && gs.hasRole('admin')) {\n            gs.addInfoMessage('Business rule: Set pgm business duration - ' + e.message);\n        }\n        gs.error(e.message);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Smart Attachment Size Limiter/Readme.md",
    "content": "Smart Attachment Size Limiter\nThis Business Rule limits attachment sizes uploaded to ServiceNow records by enforcing a maximum size configured via the system property com.glide.attachment.max_size (in bytes). If the attachment exceeds the configured limit, the upload is blocked with an error message shown to the user. You can create or modify this system property to change the max size and update the property name in the script accordingly.\n\nScoped Application Note:\nIf deploying in a scoped application, configure Cross-Scope Access under System Applications > Application Cross-Scope Access to allow your app permission to access the sys_attachment table and related resources, avoiding security restrictions.\n\nThis approach keeps your instance performant by managing attachment size transparently without hardcoded limits.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Smart Attachment Size Limiter/Smart Attachment Size Limiter.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    if (current.table_name == 'incident') { //here multiple tables can be looped I am just using incident table\n        var maxSize = gs.getProperty('com.glide.attachment.max_size');\n        maxSize = parseInt(maxSize, 10);\n        if (current.size_bytes > maxSize) {\n            var maxSizeMB = (maxSize / (1024 * 1024)).toFixed(2);\n            var attachmentSizeMB = (current.size_bytes / (1024 * 1024)).toFixed(2);\n            // Prevent insert by setting error message\n            gs.addErrorMessage(\"Attachment '\" + current.file_name + \"' size (\" + attachmentSizeMB + \" MB) exceeds the max allowed size of \" + maxSizeMB + \" MB. Please reduce the file size.\");\n            // Cancel the insert operation\n            current.setAbortAction(true);\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Sync CI operational status with child related CIs/README.md",
    "content": "\n# Sync CI operational status with child related CIs\n\n**Use case** : Whenever any configuration item becomes operational or non-operational, then all the CIs which are related to the current CI as a child in cmdb_rel_ci table will also update their operational status\n\n*info* : This method is to achieve the above use-case just with business rule\n\n**Solution** : Create a `Async` business rule on `cmdb_ci` table with `update` checkbox checked. \n\n*condition* : operational status CHANGES\n\nFollow the script present in [script.js](https://github.com/ServiceNowDevProgram/code-snippets/blob/patch-1/Business%20Rules/Sync%20CI%20operational%20status%20with%20child%20related%20CIs/script.js)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Sync CI operational status with child related CIs/script.js",
    "content": "var ciRel = new GlideRecord('cmdb_rel_ci');\nciRel.addQuery('parent', current.sys_id);\nciRel.query();\nwhile(ciRel.next())\n{\n  var ci = new GlideRecord('cmdb_ci');\n  ci.addQuery('sys_id', ciRel.child);\n  ci.query();\n  if(ci.next())\n  {\n    ci.operational_status = current.operational_status;\n    ci.update();\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Sync Fields for two tables/README.md",
    "content": "**Scenario**: Synchronize fields between two different tables.\n\n**Example**: Any changes made to the fields on the New Hire HR Case for a user, where the same field also exists on the HR Profile, will automatically be updated on the HR Profile when the field is modified on the case.\nFields on the case that are derived from the HR Profile are excluded from this synchronization.\n\n**Script Logic**: An after-update business rule that checks the updated fields in the current table (HR case) exist in the other table (HR Profile) and updates them accordingly.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Sync Fields for two tables/script.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    // Check if the Subject Person of case is empty\n    if (gs.nil(current.subject_person))\n        return;\n    // Subject person field reference to User Table. Check if the HR profile is present for the user.\n    var profileGR = new GlideRecord('sn_hr_core_profile');\n    if (!profileGR.get(current.subject_person))\n        return;\n\n    // Get all fields from the current record (case)\n    var elements = current.getElements();\n\n    // Loop through each field on the case and get the name\n    for (var i = 0; i < elements.size(); i++) {\n        var field = elements.get(i);\n        var fieldName = field.getName();\n\n        // Skip system fields and derived fields of hr profile (If any)\n        if (fieldName.startsWith('sys_') || fieldName === 'hr_profile')\n            continue;\n\n        var newValue = current.getValue(fieldName);\n        var oldValue = previous.getValue(fieldName);\n\n        // Only act if value changed\n        if (newValue != oldValue) {\n            // Check if the same field exists in HR profile and is accessible\n            if (profileGR.isValidField(fieldName)) {\n                profileGR.setValue(fieldName, newValue);\n            }\n        }\n    }\n\tprofileGR.update();\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Sync Integer Field/README.md",
    "content": "Scenario: Synchronize integer field that will put records in sequential order.\n\nExample: When you have an integer field on a table and want to be able to add records and have it rearrange order its order sequentially.\n\nScript Logic: An before-update business rule that grabs all the records that currently have an integer field value and puts them into an array. It then adds the new value and puts it into the correct position in the array and updates value so they are in a sequence order.\n\n**You will have to create a custom integer field to be able to use this business rule.**\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Sync Integer Field/sync_integer_field.js",
    "content": "// Business Rule: 'Before' Update on any table that will have a integer field you want to keep in sync\n(function executeRule(current, previous /*null when async*/) {\n  var tableName = current.getTableName()\n \n  //This array will contain the list of records with their current value before rearranging them.\n  var recordList = [];\n\n  var tableRecord = new GlideRecord(tableName);\n  tableRecord.addNotNullQuery({{insert name of integer field you want to sync}}); //Going to exclude records that don't have a value\n  tableRecord.addQuery('sys_id', '!=', current.sys_id); //Need to exclude the current record\n  tableRecord.orderBy({{insert name of integer field you want to sync}});\n  tableRecord.query();\n\n  while(tableRecord.next()) {\n    recordList.push(tableRecord.sys_id.toString());\n  }\n\n  //Condition check to make sure the record has a value to add to the arry in the correct spot, otherwise no reason to splice the array\n  if (current.{{insert name of integer field you want to sync}} != '') {\n    var index = current.{{insert name of integer field you want to sync}} - 1; //Making this one less so it will get added first\n      recordList.splice(index, 0, current.sys_id.toString()); //This will insert our record into the correct position it needs to be in the list\n  }\n\n  //Reassigning the integer sequentially\n  for (var i = 0; i < recordList.length; i++) {\n    if(recordList[i] == current.sys_id.toString()) {\n      current.{{insert name of integer field you want to sync}} = i + 1;\n    } else {\n        var updatedTableRecord = new GlideRecord(tableName);\n        if (updatedTableRecord.get(recordList[i])) {\n          updatedTableRecord.{{insert name of integer field you want to sync}} = i + 1;\n          updatedTableRecord.setWorkflow(false); //Setting the workflow false since we dont want the flow to get triggered since all we are doing is updating the integer field\n          updatedTableRecord.update()\n        }\n    }\n\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Synchronize RITM comments to Active tasks/README.md",
    "content": "This is an Async Update Business rule written on the Requested Item table.\nThis code works as a comment synchronization mechanism.\n\nWhat the code does:\n-Comment is added to the parent record(which in this case is a Requested Item)\n-Creates an HTML link to the parent record using its number.\n-Queries and finds all active SCTASKs associated with the Reuqested item.\n-Loops through each SCTASK, adds the copied comment along with a link to the Requested Item.\n\nLet's explain with an example:\n\nRITM: RITM001001\nTask 1: SCTASK001001\nTask 2: SCTASK001002\n-User adds new comment into the RITM: 'This is a test comment.'\n-Record link is created and stored inside a clickable RITM number.\n-The RITM comment is updated on both the SCTASKs with the comment looking like:\n \n    Updated comments on: [RITM001001]\n    This is a test comment.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Synchronize RITM comments to Active tasks/synccommentfromritmtotask.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    var ritmLink = '[code]<a href=\"nav_to.do?uri=' + current.getLink() + '\" target=\"_blank\">' + current.number.toString() + '</a>[/code]';\n\n    var scTask = new GlideRecord('sc_task');\n    scTask.addQuery('request_item', current.sys_id);\n    scTask.addActiveQuery();\n    scTask.query();\n    while (scTask.next()) {\n        scTask.comments = 'Updated comments on: ' + ritmLink + '\\n' + current.comments.getJournalEntry(1);\n        scTask.update();\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Track Tag Removal Using Delete Business Rule/BusinessRule.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n    /*\n\tFire this BR when a tag is removed/deleted from a record.\n\tRetrieve the tag name, the user who removed the tag, and the tag removal date.\n\tUpdate the above information onto the tag-referenced record in this example, In this example its incident record\n\t*/\n    var updateRecord = new GlideRecord(current.table);\n    if (updateRecord.get(current.table_key)) {\n        var notes = \"Tag Name:\" + \" \" + current.label.getDisplayValue() + \"\\n\" + \"Tag Removed By:\" + \" \" + current.sys_updated_by + \"\\n\" + \"Tag Removed On:\" + \" \" + current.sys_updated_on;\n        //updateRecord.setValue(\"work_notes\", notes);\n        updateRecord.work_notes = notes;\n        updateRecord.update();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Track Tag Removal Using Delete Business Rule/readme.md",
    "content": "**Configure the following business rule on the label_entry table:**\n\n1.Ensure the \"Delete\" operation is selected and the rule runs onBefore.\n\n2.Retrieve the table name where the tag is removed.\n\n3.Update the worknotes to track who removed the tag and when it was removed.\n<img width=\"952\" height=\"460\" alt=\"image\" src=\"https://github.com/user-attachments/assets/020525dc-c98d-4ae8-94f9-d77cca6680e7\" />\n<img width=\"1665\" height=\"739\" alt=\"image\" src=\"https://github.com/user-attachments/assets/46c930eb-c031-4e1e-b1e6-31a3c64debb0\" />\n\n\n**Output:**\n\n<img width=\"1139\" height=\"818\" alt=\"image\" src=\"https://github.com/user-attachments/assets/ed07f380-f279-4eca-82a5-208ae54054e7\" />\n\n<img width=\"780\" height=\"401\" alt=\"image\" src=\"https://github.com/user-attachments/assets/283b40e1-1d7c-42aa-85f8-b8e9c6ccc3d0\" />\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Transform Data from Attachment/README.md",
    "content": "Loading data into ServiceNow without needing admin privileges\n\nThis BR script enables any user to process data from an excel file by attaching that file to a record.\n\nPre-requisites:\nA sys admin needs to create the following objects in SN first. The easiest way to do this is to load sample data using an excel file in the same format as the one that will be used by the user:\n1. Importset table\n2. Transform Map\n3. Data Source\n4. A table to house all the files that will be loaded\n\nAll the other explanations on the use of the code is in the script itself\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Transform Data from Attachment/data_xform_from_attachment.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\t\n\n\tvar file_load_gr = new GlideRecord('name_of_table_to_house_loaded_file');\n\tfile_load_gr.addQuery('sys_id', current.sys_id);\n\tfile_load_gr.query();\n\tif (file_load_gr.next()) {\n\t\t//found the record\n\t} else {\n\t\treturn;\n\t}\n\n\t//update filename field on custom table with name of attached file\n\t\n\tvar attachment = new GlideSysAttachment();\n\tvar agr = attachment.getAttachments('name_of_table_to_house_loaded_file', file_load_gr.sys_id.toString());\n\tif (agr.next()) {\n\t\t//has an attachment and found it\n\t\tvar LoadFileName = agr.getValue('file_name');\n\n\t\tfile_load_gr.file_name = LoadFileName;\n\t\tfile_load_gr.update();\n\n\t\tvar importSetTableName = \"name_of_importset_table\";\n\t\tvar transformMapIDs = \"sys_id of transform map\"; //Amazon weekly DSP file load\n\t\tvar applicatonScope = \"name of application scope\"; //if creating this inside a specific scope or else Global\n\t\tvar dataSourceName = 'name_of_dataource';\n\t\tvar dataSourceID = null;\n\n\t\tvar dataSource = new GlideRecord('sys_data_source');\n\t\tdataSource.addQuery('name', dataSourceName);\n\t\tdataSource.query();\n\t\tif (dataSource.next()) {\n\t\t\tvar attach = new GlideSysAttachment();\n\t\t\tvar attachList = attach.getAttachments('sys_data_source', dataSource.sys_id.toString());\n\t\t\twhile (attachList.next()) {\n\t\t\t\tattach.deleteAttachment(attachList.getValue('sys_id'));\n\t\t\t}\n\t\t\tattach.copy('name_of_table_to_house_loaded_file', file_load_gr.sys_id.toString(), 'sys_data_source', dataSource.sys_id.toString());\n\t\t\tdataSourceID = dataSource.sys_id;\n\t\t}\n\n\t\t/*\n\t\t// Schedule Load of Attachment\n\t\t// This Script Include below initiates the data load from the attachment and is provided by SN\n\t\t*/\n\n\t\tnew global.EmailFileImportUtil().scheduleImport(dataSourceID, transformMapIDs);\n\t} else {\n\t\treturn;\n\t}\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Trigger Event when a member is added to a list/README.md",
    "content": "Scenario: You are wanting to send a notification to someone whenever they are added to a list of people. \n\nApplication: Create an event in the event registry. Then, create a business rule. Set the name and table, advanced = true. \n\nWhen to run: \nWhen = after, update = true, conditions = watch list (replace with applicable field name) | changes\n\nAdvanced tab: insert the snippet"
  },
  {
    "path": "Server-Side Components/Business Rules/Trigger Event when a member is added to a list/Trigger an event when user is added to field.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n   \n    // Ensure the 'watch_list' (replace with applicable field name) field has been modified\n    if (current.watch_list != previous.watch_list) {\n        \n        // Split the current and previous watch lists into arrays\n        var newWatchers = current.watch_list.split(',');\n        var oldWatchers = previous.watch_list ? previous.watch_list.split(',') : [];\n\n        // Identify the newly added users to the watch list\n        var addedUsers = newWatchers.filter(function (user) {\n            return oldWatchers.indexOf(user) === -1;\n        });\n\n        // Loop through the added users to trigger the event for each\n        addedUsers.forEach(function(userID) {\n            var email;\n            var firstName;\n\n            // Try to get the user record by user ID (sys_id)\n            var userGr = new GlideRecord('sys_user');\n            if (userGr.get(userID)) {\n                firstName = userGr.first_name;\n                email = userGr.email;\n            } else {\n                \n                // If no user record is found, assume the userID is an email address\n                email = userID;\n                firstName = \"Team\";\n            }\n\n            // Trigger the event (replace \"new_member\") with the current case and user information\n            gs.eventQueue('new_member', current, email, firstName);\n        });\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update CI status on Change Request closure/README.md",
    "content": "Update CI status on Change Request Closure \n\n1. Create a Business Rule - After Update\n2. Select the Change Request Table.\n3. Add a condition as when Change state = \"Closed\"\n4. Run only when Change is moving to Closed\n5. Query all CI relationships for this Change Request\n6. Update CI status based on the condition\n7. The relationship table that links a change (task) to CIs (ci_item).\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update CI status on Change Request closure/Script.js",
    "content": "(function executeRule(current, previous) {\n    // Run only when Change is moving to Closed\n    if (previous.state != current.state && current.state == 'closed') {\n\n        gs.info('Change ' + current.number + ' closed — updating related CI statuses.');\n\n        // Query all CI relationships for this Change\n        var ciRel = new GlideRecord('task_ci');\n        ciRel.addQuery('task', current.sys_id);\n        ciRel.query();\n\n        while (ciRel.next()) {\n            if (ciRel.ci_item) {\n                var ci = new GlideRecord('cmdb_ci');\n                if (ci.get(ciRel.ci_item)) {\n\n                    // Example: Update CI status\n                    ci.install_status = 1; // 1 = In Service (Active)\n                    ci.update();\n\n                    gs.info('CI ' + ci.name + ' status updated to In Service for Change ' + current.number);\n                }\n            }\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Child Incident based on Parent Incident/README.md",
    "content": "UseCase : Update child incident when parent incident is updated.\n\nWhat do we want to achieve ? --> Real-time update of child incident based on parent incident update\nWhat fields do we want to update? --> We can dynamically configure it out in my script I have created an array called 'monitorFields' which will hold all the fields which should get updated when it gets updated on the parent record (Inn the ideal case you can put these values on the system property)\nHow we will get all the changed values on the record? --> These values will be extracted as a part of the array 'changedFields' which will be derived by comparing the current and previous values for that field\n\nSolution :\nCreate an On After Business rule on the incident table\nDefine the trigger condition based on the fields you wanna monitor. In our case monitored fields are as given: 'caller_id','state','impact','description' (You can add or remove as per your use case)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Child Incident based on Parent Incident/Update_Child_Incident.js",
    "content": "/**\n* @Description : On After Business Rule to Update Child incident based on parent incident\n* @monitorFields : Fields to be moniter to be updated on the child record\n* @changedFields : Fields which are getting updated\n* @changedMnitorFields : Array of monitor fields which got changed\n**/\n\n(function executeRule(current, previous /*null when async*/) {\n\n\tvar monitorFields = ['caller_id','state','impact','description']; // Fields to be moniter to be updated on the child record\n\t\n\tvar changedFields = []; // Fields which are getting updated\n\tfor (var x in current){\n\t\tif (current[x] != previous[x]) {\n\t\t\tchangedFields.push(x);\n\t\t}\n\t}\n\n\tvar changedMnitorFields = changedFields.filter(function (ele) { // Get the array of monitor fields which got changed\n\t\treturn monitorFields.indexOf(ele) != -1;\n\t});\n\n\tvar grIncident = new GlideRecord('incident');\n\tvar query = gs.getMessage('parent_incident={0}^active=true',[current.getUniqueValue()]); // Get all the Active child incident\n\tgrIncident.addEncodedQuery(query);\n\tgrIncident.query();\n\twhile (grIncident.next()) {\n\t\tchangedFields.forEach (function (ele) {\n\t\t\tgrIncident[ele] = current[ele];\n\t\t});\n\t\tgrIncident.update();\n\t} \n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Incident Description on Insert with Telephone Icon Concatenated/README.md",
    "content": "This script basically updates the Description in Incident table which includes some text but added telephone icon using the script, so that it looks fascinating.\nThis includes screenshot as well where it shows telephone icon concatenated with specific meaningful string.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Incident Description on Insert with Telephone Icon Concatenated/code_snippet.js",
    "content": "// Update description with telephone icon with specific meaningful description concatenated\n(function executeRule(current, previous /*null when async*/) {\n\n    try {\n        //code to execute goes here\n        var myTelephone = \"\\u260E\"; // Create the telephone icon\n        if (current.description == \"\") {\n            current.setValue('description', \"You really should \" + myTelephone + \" home more often.\"); // Update the description in concatenation with telephone icon script \n        }\n    } catch (err) {\n        //code to handle error goes here\n        gs.error(\"A runtime error has occurred: \" + err);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Locked Out field when Active field changes on User record/README.md",
    "content": "# Use Case <br/>\nAs per OOB, the \"Locked out\" field will be set to true if Active field set to false. But, the vice-versa case is not implemented. For example, If Employee is on long leave like maternity leave etc., then the user account will be set inactive temporarily and reactivated upon his/arrival to office. This causes, user account to be Active and Locked out as true which makes the login to fail\n# Business Rule <br/>\nName: Update Locked Out field<br/>\nTable: User [sys_user] <br/>\nAdvanced: true <br /><br/>\n\n**When to run section:**<br/>\nWhen: Before <br/>\nInsert: true <br/>\nUpdate: true <br/>\nFiler Conditions: Active -- Changes to -- true [AND] Locked out -- is -- true<br/><br/>\n![image](https://github.com/user-attachments/assets/835f6d9c-8d60-4b1a-9159-bda5576fe088)\n\n**Advanced section:**<br/>\nScript:<br/>\n(function executeRule(current, previous ) {\n\n\tcurrent.locked_out = !current.active; \n\n})(current, previous);\n\n![image](https://github.com/user-attachments/assets/0fd67e77-38f3-449d-9647-047406f8d23e)\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Locked Out field when Active field changes on User record/update_locked_out_field.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\tcurrent.locked_out = !current.active; \n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Related Tasks When Parent Changes/Readme.md",
    "content": "This Business Rule ensures that whenever the priority of a Change Request is updated, all related Change Tasks automatically inherit the same priority.  \n\nThis helps maintain data consistency and ensures that task prioritization aligns with the parent change.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Related Tasks When Parent Changes/script.js",
    "content": "(function executeRule(current, previous) {\n  //when the priority field changes\n    if (current.priority.changes()) {\n        var task = new GlideRecord('change_task');\n        task.addQuery('change_request', current.sys_id);//Find all tasks related to this Change Request\n        task.query();\n        while (task.next()) {\n            task.priority = current.priority; //Update the task priority\n            task.update();\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Set Scope Validation/README.md",
    "content": "\n# Business Rule: Validate Update Set Scope\n\n\n## Challenge\n\nWhen working with update sets, mismatched scopes can lead to issues during the preview process in the target instance. This often results in errors that require manual intervention to resolve. Such errors can be time-consuming, especially for minor mismatches that could have been identified and prevented earlier. This challenge highlights the need for a mechanism to validate update set scopes before completion, saving time and effort during deployment.\n\n\n## Overview\nThis business rule ensures that all customer updates within an update set match the application scope of the update set. If any updates have mismatched scopes, the business rule restricts the completion of the update set and displays a message listing the files with mismatched scopes.\n\n## Features\n- Validates the scope of all updates in an update set.\n- Prevents the completion of an update set if scope mismatches are detected.\n- Displays a detailed message listing the files with mismatched scopes.\n\n## Use Case\nThis business rule is useful for ensuring that updates in an update set adhere to the correct application scope, preventing potential issues caused by scope mismatches.\n\n## Implementation\n\n### 1. Create the Business Rule\n1. Navigate to **System Definition > Business Rules** in your ServiceNow instance.\n2. Click **New** to create a new business rule.\n3. Configure the business rule as follows:\n    - **Name**: Validate Update Set Scope\n    - **Table**: `sys_update_set`\n    - **When**: Before\n    - **Insert**: False\n    - **Update**: True\n    - **Delete**: False\n\n### 2. Add the Script\nUse the following script in the **Script** field:\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n\n\t// Add attached code here\n \n})(current, previous);\n\n```\n\n### 3. Test the Business Rule\n1. Create an update set and add updates with different scopes.\n2. Attempt to complete the update set.\n3. Verify that the business rule prevents completion and displays the appropriate error message.\n\n\n## Screenshots\n![Output Result](error.png)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update Set Scope Validation/ValidateScopesForUpdateSet.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\n\n\tvar updateSetSysId = current.sys_id; // Get the sys_id of the current update set\n\tvar updateSetScope = current.application; // Get the scope of the current update set\n\tvar gr = new GlideRecord('sys_update_xml');\n\tgr.addQuery('update_set', updateSetSysId); // Query for records in the current update set\n\tgr.query();\n\n\tvar misMatchedUpdates = [];\n\twhile (gr.next()) {\n\t\tif (gr.application != updateSetScope) { // Check if the scope matches the update set scope\n\t\t\tmisMatchedUpdates.push( gr.target_name.toString() + ' (' + gr.type.toString() + ')'); // Collect the file names with mismatched scope\n\t\t}\n\t}\n\n\tif (misMatchedUpdates.length > 0) {\n\t\tgs.addErrorMessage('The following files have a different scope than the update set scope: \\n' + misMatchedUpdates.join(', '));\n\t\tcurrent.setAbortAction(true); // Prevent the update set from being completed\n\t}\n\n\t\n\n})(current, previous);"
  },
  {
    "path": "Server-Side Components/Business Rules/Update worknotes of Ptask to parent Problem record/README.md",
    "content": "If user update work notes of problem task then it should update the same worknotes in parent problem record as well with highlighting problem task number.\nthis business rule run after updation when worknote changes.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Update worknotes of Ptask to parent Problem record/script.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n\tvar workNotes = current.work_notes.getJournalEntry(1);\n\n\tvar parentProblem = new GlideRecord('problem'); \n\tparentProblem.get(current.getValue('problem')); //fetching current problem record from the problem record\n\tif(parentProblem) {\n\tparentProblem.work_notes = 'This worknote is updated in' + current.getValue('number') + '\\n' + workNotes; //updating worknotes from problem task to parent problem\n\t}\n\tparentProblem.update();\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/UpdateFavouritedKnowledgeArticleWhenItIsUpgradedToNewVersion/README.md",
    "content": "Problem Statement\nWhen a knowledge article saved in a user’s favorites is updated to a new version, the favorites section continues to display the older version, leading to potential misinformation and inefficiencies. To address this, a solution has been implemented that automatically updates the knowledge articles in users' favorites to the latest version whenever a new one is published. This removes the need for users to manually update their saved articles, ensuring they always have access to the most accurate and up-to-date information, thereby enhancing both user experience and operational efficiency.\nHow It Works\nTriggering Event:\n\nThe Business Rule is configured to trigger when a knowledge article’s state changes to draft. This ensures that when the article enters the draft stage, the system will execute the rule to update any bookmarks saved by users.\nScript Logic:\n\nThe script retrieves the sys_id and version information of the latest and the previous versions of the knowledge article from the kb_knowledge table.\nIt then searches the sys_ui_bookmark table for any URLs containing the sys_id of the previous version of the knowledge article.\nIf a match is found, the script updates the bookmark to point to the latest version of the article.\nURL Construction:\n\nThe script constructs a new URL for the latest version using the sys_id of the latest article. This new URL is then updated in the bookmark record.\nThe updated URL ensures that when users access their favorites, they are redirected to the most recent version of the article.\nConfiguration\nBusiness Rule Configuration:\n\nThe Business Rule should be created on the kb_knowledge table and configured to trigger when a knowledge article’s state is changed to draft.\nThis rule runs in the after phase, ensuring that once the state is set to draft, the rule checks for bookmarks that need to be updated.\nScript Configuration:\n\nThe script retrieves the sys_id of both the latest and previous versions of the knowledge article.\nIt then queries the sys_ui_bookmark table for any bookmarks with a URL containing the second version's sys_id.\nIf a match is found, the bookmark's URL is updated to point to the latest version.\nTesting\nTest Scenario:\n\nCreate two versions of a knowledge article (i.e., KB001 V1 and KB001 V2).\nAdd the first version (V1) to your favorites in the Service Portal.\nEnsure the sys_ui_bookmark table contains a bookmark with the sys_id of the first version.\nBusiness Rule Trigger:\n\nUpdate the second version of the article and transition it to draft state.\nThis triggers the Business Rule and executes the script.\nVerify Bookmark Update:\n\nCheck the sys_ui_bookmark table to confirm that the URL has been updated from V1’s sys_id to V2’s sys_id.\nIn the Service Portal, verify that the saved favorite now points to the latest version (V2) of the article.\nEdge Case Testing:\n\nTest the scenario when there’s no previous version of the knowledge article (i.e., the article is on its first version) to ensure the script doesn’t fail.\nTest when no bookmark exists to ensure the script completes without errors.\nBenefits\nAutomatic Version Control:\n\nUsers are automatically directed to the latest version of a knowledge article, ensuring they always have access to up-to-date information.\nImproved Knowledge Accuracy:\n\nPrevents users from accessing and relying on outdated information, reducing the risk of misinformation and errors.\nEnhanced User Experience:\n\nThis functionality improves the user experience by keeping their saved favorites always up to date, without requiring manual updates.\nOperational Efficiency:\n\nAdministrators and content managers save time by eliminating the need for manual bookmark updates when a new version of an article is published.\nScalability:\n\nThe solution is scalable, meaning it can be applied across all knowledge articles in the system, and can also be adapted to other record types or systems that rely on version control.\nConclusion\nThis solution ensures that knowledge articles saved as favorites by users in the Service Portal are always updated to reflect the most recent version. By implementing this automatic update process, the organization can improve the reliability of its knowledge base, enhance user satisfaction, and reduce errors stemming from outdated information.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/UpdateFavouritedKnowledgeArticleWhenItIsUpgradedToNewVersion/update_favourited_knowledge_artice.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\nvar firstKbSysId = \"\"; // Store the sys_id of the first KB article\nvar secondKbSysId = \"\"; // Store the sys_id of the second KB article\n\n// Query the kb_knowledge table for articles with the same number as the current record, ordered by version in descending order\nvar kbRecord = new GlideRecord(\"kb_knowledge\");\nkbRecord.addEncodedQuery(\"numberSTARTSWITH\" + current.number); // Filter based on KB number\nkbRecord.orderByDesc(\"version\"); // Order by version in descending order\nkbRecord.setLimit(2); // Limit to 2 records (first and second version)\nkbRecord.query();\n\nvar count = 0;\nwhile (kbRecord.next()) {\n    count++;\n    if (count == 1) {\n        // Get the sys_id of the first KB article (latest version)\n        firstKbSysId = kbRecord.getValue(\"sys_id\");\n    }\n    if (count == 2) {\n        // Get the version number and sys_id of the second KB article (previous version)\n        secondVersionNumber = kbRecord.getValue(\"version\");\n        secondKbSysId = kbRecord.getValue(\"sys_id\");\n        break; // Stop the loop after fetching the second record\n    }\n}\n\n// Get the instance URL from system properties\nvar instanceURL = gs.getProperty('glide.servlet.uri');\n\n// Query the sys_ui_bookmark table to check if a bookmark exists with a URL containing the second KB article's sys_id\nvar bookmarkRecord = new GlideRecord(\"sys_ui_bookmark\");\nbookmarkRecord.addEncodedQuery(\"urlLIKE\" + secondKbSysId); // Filter for URLs containing the second KB sys_id\nbookmarkRecord.query();\n\n\nif (bookmarkRecord.next()) {\n    // If a bookmark is found, update the URL to point to the first KB article (latest version)\n    var newUrl = instanceURL + \"kb_knowledge.do?sys_id=\" + firstKbSysId +\n                 \"&sysparm_record_target=kb_knowledge&sysparm_record_row=2\" +\n                 \"&sysparm_record_rows=5&sysparm_record_list=numberSTARTSWITHKB99999999%5EORDERBYDESCversion\";\n    \n    // Set the new URL in the bookmark record\n    bookmarkRecord.setValue('url', newUrl);\n    \n    // Update the bookmark record\n    bookmarkRecord.update(); \n}\n\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Use_case_Base64-Encode-Before-Save-And-Decode-on-Display/Before_BR_script.js",
    "content": "\n\n//Encode Before Saving Trigger: Before Insert/Update\n\n(function executeRule(current, previous) {\n    if (current.fieldName.changes()) {\n        var plainText = current.fieldName + '';\n        current.fieldName = GlideStringUtil.base64Encode(plainText);\n    }\n})(current, previous);\n//If the field is new or updated, encodes its value to Base64. Saves the encoded string to the database.\n\n// For Example User enters Hello World in fieldName.\n//Before saving : System encodes and stores SGVsbG8gV29ybGQ= in the database.\n//When the record is viewed : Display rule decodes it back to Hello World for the user.\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Use_case_Base64-Encode-Before-Save-And-Decode-on-Display/Display_BR_script.js",
    "content": "// Decode on Display Trigger: Before Display\n////Decodes fieldName from Base64. Shows decoded value on the form.\n\n(function executeRule(current) {\n    if (current.fieldName) { // field name can be anything\n        try {\n            var decoded = GlideStringUtil.base64Decode(current.fieldName);\n            current.setDisplayValue('fieldName', decoded);\n        } catch (ex) {\n            current.setDisplayValue('fieldName', '[Invalid Base64]');\n        }\n    }\n})(current);   \n\n//If the field is new or updated, encodes its value to Base64. Saves the encoded string to the database.\n\n// For Example User enters Hello World in fieldName.\n//Before saving : System encodes and stores SGVsbG8gV29ybGQ= in the database.\n//When the record is viewed : Display rule decodes it back to Hello World for the user.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Use_case_Base64-Encode-Before-Save-And-Decode-on-Display/Readme.md",
    "content": "Base64 Encode/Decode Business Rule\nOverview\n\nThis setup demonstrates how to store field data securely in Base64 format in the database, while still displaying it as human-readable text to end users in ServiceNow.\n\nIt uses two Business Rules:\n\nBefore Insert/Update Rule : Encodes plain text into Base64 before saving to the database.\n\nDisplay Rule  : Decodes the Base64 value back into readable text when loading the record form.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/User Activity Log Tracking/README.md",
    "content": "# Overview\nThis script logs specific user actions (e.g: record updates and approvals) in ServiceNow into a custom table `u_user_activity_log`. \n\nThis provides audit capabilities and allowing developers to track user actions for compliance or analytics.\n\n# How It Works\nThe script is triggered by a Business Rule on record updates and checks for changes in specified critical fields (e.g., `state`, `approval`). When a change occurs, it logs relevant details in the `u_user_activity_log` table, including:\n- `u_user`: User ID\n- `u_action`: Type of action performed\n- `u_record_id`: ID of the updated record\n- `u_record_table`: Name of the table where the change occurred\n- `u_description`: Brief description of the action\n\n# Implementation\n- Create Custom Table: Ensure `u_user_activity_log` table exists with fields like `u_user`, `u_action`, `u_record_id`, `u_record_table`, `u_description`, etc.\n- Configure Business Rule: Set the Business Rule to run on update and add conditions for monitoring fields (`state`, `approval`)."
  },
  {
    "path": "Server-Side Components/Business Rules/User Activity Log Tracking/userActivityLog.js",
    "content": "// Script to log user actions (updates, approvals) into a custom table\n(function executeRule(current, previous /*null when async*/) {\n    // Check if key fields are modified (customize as needed)\n    if (current.state.changes() || current.approval.changes()) {\n        var logEntry = new GlideRecord('u_user_activity_log');\n        logEntry.initialize();\n\n        // Populate log details\n        logEntry.u_user = gs.getUserID();\n        logEntry.u_action = current.state.changes() ? \"State Change\" : \"Approval Change\";\n        logEntry.u_record_id = current.sys_id;\n        logEntry.u_record_table = current.getTableName();\n        logEntry.u_description = \"User \" + gs.getUserDisplayName() + \" updated \" + logEntry.u_action;\n\n        logEntry.insert();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/User Impersonation Activity Logger/README.md",
    "content": "# User Impersonation Activity Logger\n\nA ServiceNow server-side utility that automatically creates a log when an action is performed under impersonation, helping distinguish between admin-added and user-added notes.\n\n# Challenge\n\nThe challenge lies in distinguishing between actions performed by administrators impersonating users and those performed by the users themselves. Without a reliable way to track impersonation activity, it becomes difficult to ensure transparency and accountability in ticket histories. This lack of clarity can lead to confusion during audits, misinterpretation of updates, and potential compliance risks. Addressing this issue is critical to maintaining trust and operational efficiency.\n\n## Description\n\nThis script identifies if the current user session is under impersonation (e.g., an admin impersonating another user).  \nIf true, it automatically appends a message in the **Logs** indicating that the note was added during impersonation.  \nThis improves auditability and clarity when reviewing ticket histories.\n\n## Functionality\n\nThe User Impersonation Activity Logger provides the following capabilities:\n- Detects if the current user is impersonating another user\n- Automatically appends a log message stating the impersonation context\n- Works in **Business Rule** and Global Scoped Tables\n- Logs both actual user and impersonated user details\n- Provides clear distinction for audit and tracking\n\n## Usage Instructions\n\n### Add as Business Rule\n\n```javascript\n// When: before update\n// Table: incident \n// Script:\n(function executeRule(current, previous /*null when async*/) {\n\n//Add the logic here\n\n})(current, previous);\n```\n\n\n## Prerequisites\n\n- Need admin access to check the impersonation logs later\n\n\n## Dependencies\n\n- GlideSystem API\n- GlideImpersonate API\n- gs.getSession()\n\n\n## Category\n\nServer-Side Components / Business Rules / User Impersonation Activity Logger\n\n\n## Screenshots\n<img width=\"3024\" height=\"536\" alt=\"image\" src=\"https://github.com/user-attachments/assets/3ae408db-175f-4281-a9d7-f21df16314e7\" />\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/User Impersonation Activity Logger/UserImpersonationActiityLogger.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n  if (new GlideImpersonate().isImpersonating()) {\n    // Check if the user is impersonating\n    if (current.comments.changes() || current.work_notes.changes()) {\n      // Check if comments or work notes have changed\n      let actualUserName = gs.getImpersonatingUserDisplayName();\n      let impersonatedUserName = gs.getUserDisplayName();\n      let logMessage = `User Impersonation Activity Detected:\n        Timestamp : ${new GlideDateTime()}\n        Actual User: ${actualUserName}\n        Impersonated User: ${impersonatedUserName}\n        Comments added: ${current.comments || \"NA\"}\n        Work Notes added: ${current.work_notes || \"NA\"}`;\n      gs.info(logMessage);\n    }\n  }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/User Profile Field Validation/README.md",
    "content": "# Overview\nThis JavaScript code snippet is designed to validate user profile fields in ServiceNow before submission. It is particularly useful for ServiceNow developers looking to implement robust data validation mechanisms in user profiles.\n\n# How It Works\nThe snippet uses a business rule that executes on form submission to validate user input:\n- **Regular Expressions**: It utilizes regex patterns to check the format of the `phone` and `email` fields.\n- **Error Messaging**: If the validation fails, an error message is displayed to the user, and the submission is aborted. \n\n# Implementation\n- **Add to a Business Rule**: This snippet should be incorporated into a business rule configured to run on the user profile table.\n- **Adjust Validation Patterns**: Modify the `phoneNumberPattern` and `emailPattern` variables to align with your specific requirements (e.g: international phone formats)."
  },
  {
    "path": "Server-Side Components/Business Rules/User Profile Field Validation/userProfileValidation.js",
    "content": "// Script for validating user profile fields on submit\n(function executeRule(current, previous /*null when async*/) {\n    var phoneNumberPattern = /^[0-9]{10}$/; // Modify as needed\n    var emailPattern = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\n    // Validate phone number\n    if (!phoneNumberPattern.test(current.phone)) {\n        gs.addErrorMessage(\"Phone number must be a 10-digit number.\");\n        current.setAbortAction(true);\n    }\n    \n    // Validate email format\n    if (!emailPattern.test(current.email)) {\n        gs.addErrorMessage(\"Invalid email format.\");\n        current.setAbortAction(true);\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Validate Checklist items/README.md",
    "content": "A business rule that verifies all checklist items are completed before allowing the record to progress to the next status.\n\nThe business rule consists of three main parts:\n\nPart A: Looks up all checklists (checklist table) tied to the current record (document = current.sys_id).\n\nPart B: For each checklist, query the checklist_item records:\n\n        Only items in that checklist.\n\n        Only items that are not complete (complete = false).\n        \nPart C: If any incomplete items exist, an error message is displayed and the action is aborted.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Validate Checklist items/incompleteChkListShowErrMsg.js",
    "content": "//Business Rule: before update on the incident table\n//Condition: changing state to 'In Progress'\n(function executeRule(current, previous /*null when async*/) {\n    var checklistGR = new GlideRecord(\"checklist\");\n    checklistGR.addQuery(\"document\", current.sys_id);\n    checklistGR.query();\n\n    while (checklistGR.next()) {\n        var itemGR = new GlideRecord(\"checklist_item\");\n        itemGR.addQuery(\"checklist\", checklistGR.sys_id);\n        itemGR.addQuery(\"complete\", false);\n        itemGR.query();\n\n        if (itemGR.hasNext()) {\n            gs.addErrorMessage('Checklist has incomplete items. Please complete all before assigning it back.');\n            current.setAbortAction(true);\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Validate Email on Import/README.md",
    "content": "This business rule can be implemented on any table which is having email type field and we want to validate the email\nwhen we are using the import feature from the list view to upload some data via spreadsheet.\n\nThis business rule can be used to validate the email address when \nwe are using the OOB 'Import' feature from the list context menu to upload some data from a spreadsheet into the table.\n\nTables where it could be implemented is the [sys_user] users table's > email field, and [sys_user_group] > 'group email' field.\n\nNote: [u_email_address] this field is just an example, this needs to be replaced by the email field in which this script will run.\nFor users table it will be 'email' field.\n\n\nBusiness Rule will have the below configuration:\nWhen to run: before insert + update\nFilter condition: Email[u_email_address] is not empty\n\nIn the advance > script section paste the code, it will run when we will import any spreadsheet \nin the table(record will insert/update)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Validate Email on Import/validateEmailOnImport.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    //[u_email_address],this field backend value needs to be replaced with the email field in which you want to implement the business rule.\n    //Example: In the [sys_user] table, the field will be 'email'\n        \n    var getEmail = current.u_email_address;\n\n    if (/\\s/.test(getEmail)) {\n        current.u_email_address = getEmail.trim().replace(/\\s/g, '');\n        getEmail.update();\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Validate JSON Property/README.md",
    "content": "Usage : Executes a business rule to verify if the JSON object saved in the property is a valid JSON. \n\nSteps for Creating the Business Rule:\n\nNavigate to Business Rules:\n  - Go to System Definition > Business Rules in ServiceNow.\n  - Create a New Business Rule:\n  - Click New to create a new business rule.\n\nFill in Basic Information:\n - Name: Provide a name like Validate JSON in Properties Table.\n - Table: Set the table to properties(sys_properties).\n - When to Run: Choose Before so that it validates before the record is saved.\n - Insert: Select True to run on insert.\n - Update: Select True to run on update (if needed).\n   \nAdd Conditions (optional):\n - Set conditions if you only want to validate the JSON under certain circumstances. For instance, you can add conditions like\n\n   value \"starts with\" { OR\n   value \"starts with\" [ AND\n   value \"ends with\" } OR\n   value \"ends with\" ] OR\n\n  to check specific fields.\n\nAdd the Script:\n\nUnder the Advanced tab, write the script to validate the JSON object. The script is mentioned in the jsonPropertyValidator.js file.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Validate JSON Property/jsonPropertyValidator.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n    // This BR will check if the property has a valid JSON object and will only allow save if the JSON is syntactically correct\n    try {\n        var regex =/\\s*(['\"])([^'\"]+)\\1\\s*[:;]\\s*(?:(['\"])([^'\"]*)\\3|true|false|null|\\d+)(?:\\s*[;,]\\s*(['\"])([^'\"]+)\\5\\s*[:;]\\s*(?:(['\"])([^'\"]*)\\7|true|false|null|\\d+))*?\\s*\\}|\\[\\{(?:\\s*(['\"])([^'\"]+)\\9\\s*[:;]\\s*(?:(['\"])([^'\"]*)\\11|true|false|null|\\d+)(?:\\s*[;,]\\s*(['\"])([^'\"]+)\\13\\s*[:;]\\s*(?:(['\"])([^'\"]*)\\15|true|false|null|\\d+))*?\\s*\\}\\s*(?:[;,]\\s*(?:\\{(?:\\s*(['\"])([^'\"]+)\\17\\s*[:;]\\s*(?:(['\"])([^'\"]*)\\19|true|false|null|\\d+)(?:\\s*[;,]\\s*(['\"])([^'\"]+)\\21\\s*[:;]\\s*(?:(['\"])([^'\"]*)\\23|true|false|null|\\d+))*?\\s*\\})\\s*)*)*\\s*\\])$/;\n\t\t\n        if (regex.test(current.value)) {\n            JSON.parse(current.value);\n        }\n    } catch (ex) {\n        gs.addErrorMessage(\"Invalid JSON format\");\n        current.setAbortAction(true);\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Warn for changed OOTB artifacts/README.md",
    "content": "# Description\n\nChanging OOTB artifacts like Script Includes or Business Rules is never a good idea, as you will get so called skipped records in the next upgrade/patch you have to care about. Otherwise, you can get into big trouble and risk broken functionalities as well as unpredictable behavior. But many developers are not aware about the consequences when playing around with OOTB artifacts or have no idea about how to revert the changes (see https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0993700 if you don't know how).\n\nTherefore I implemented the Busines Rule \"Warn for changed OOTB artifacts\" which displays a warning when opening a modified OOTB artifact. The warning message also provides a link to the most recent OOTB version, where you will find a UI Action for reverting to that version.\n\n# Business Rule\n\nThe JavaScript code of the Business Rule is written in way that be also used with a scoped application.\n\n**Table:** `sys_metadata`\n\n**When:** display\n\n# Result\n\n![Result](./screenshot.png)\n"
  },
  {
    "path": "Server-Side Components/Business Rules/Warn for changed OOTB artifacts/warn_for_changed_ootb_artifacts.js",
    "content": "(function executeRule(current) {\n\n\tvar _strCurrentTable = String(current.getTableName() || '');\n\tvar _strCurrentURL   = String(gs.action.getGlideURI() || '');\n\n\tif (_strCurrentTable.length === 0) {\n\t\treturn;\n\t}\n\n\t//only continue with form views in the Classic UI\n\tif (!_strCurrentURL.startsWith(_strCurrentTable + '.do')) {\n\t\treturn;\n\t}\n\n\t//determine the unique artifact ID\n\tvar _strUpdateName = \n\t\tString(\n\t\t\tcurrent.getValue('sys_update_name') || \n\t\t\tnew GlideUpdateManager2().getUpdateName(current) ||\n\t\t\t''\n\t\t);\n\n\t//Is this artifact tracked with versions?\n\tif (_strUpdateName.length > 0) {\n\t\tvar _grUpdateXml = new GlideRecord('sys_update_xml');\n\n\t\t//query all versions\n\t\t_grUpdateXml.addQuery('name', _strUpdateName);\n\t\t_grUpdateXml.orderByDesc('sys_updated_on');\n\t\t_grUpdateXml.setLimit(1);\n\t\t_grUpdateXml.query();\n\n\t\t//was the artifact changed and has a baseline version available?\n\t\tif (_grUpdateXml.next() && !_grUpdateXml.replace_on_upgrade) {\t\t\n\t\t\tvar _isOOTB    = false;\n\t\t\tvar _grVersion = new GlideRecord('sys_update_version');\n\n\t\t\t_grVersion.addQuery('name', _strUpdateName);\n\t\t\t_grVersion.orderByDesc('sys_recorded_at');\n\t\t\t_grVersion.query();\n\n\t\t\t//iterate the versions to check whether this is an OOTB artifact\n\t\t\twhile (!_isOOTB && _grVersion.next()) {\n\t\t\t\tvar _strSource = _grVersion.getValue('source_table') || '';\n\n\t\t\t\t_isOOTB = _strSource === 'sys_upgrade_history' || _strSource === 'sys_store_app';\n\t\t\t}\n\n\t\t\tif (_isOOTB) {\n\t\t\t\tgs.addErrorMessage(\n\t\t\t\t\t'This OOTB artifact was changed and thus will create an skipped record during the next ' +\n\t\t\t\t\t'upgrade!<br><br>In case the change was done accidentally, you should revert to the <a href=\"' + \n          _grVersion.getLink(true) + '\" target=\"_blank\">most recent OOTB version.</a>'\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n})(current);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/When Group is making inactive/Code.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\n    // Run only when group is being inactivated\n    if (current.active.changes() && current.active == false) {\n\n        var openTicketCount = 0;\n\n        // List of task tables to check\n        var taskTables = ['incident', 'problem', 'change_request'];\n\n        for (var i = 0; i < taskTables.length; i++) {\n            var gr = new GlideRecord(taskTables[i]);\n            gr.addQuery('assignment_group', current.sys_id);\n            gr.addQuery('active', true);\n            gr.query();\n            if (gr.hasNext()) {\n                openTicketCount++;\n                break; // We found at least one open ticket, no need to continue\n            }\n        }\n\n        // If open tickets exist, stop the update\n        if (openTicketCount > 0) {\n            gs.addErrorMessage('Cannot deactivate \"' + current.name + \n                '\" group, because there are open tickets assigned to it.');\n            current.setAbortAction(true);\n        }\n    }\n\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/When Group is making inactive/ReadMe.md",
    "content": "When marking a group as inactive, verify that there are no open tickets assigned to it from 'incident', 'problem', 'change_request' tables. If any open tickets are found, abort the deactivation process.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/duplicateattachments/AvoidduplicateattachmentsonIncident.js",
    "content": "\n   if(current.table_name==\"incident\") {\n\t\n\tvar attached = new GlideRecord('sys_attachment');\n\tattached.addQuery('table_name','incident');\n\tattached.addQuery('table_sys_id', current.table_sys_id);\n\tattached.addQuery('file_name',current.file_name);\n\tattached.query();\n\tif(attached.next())\n\t\t{\n\t\t\t\n\t\t\tgs.addInfoMessage('Attachement already Exists with the Same Name do not upload same attachement');\n\t\t\tcurrent.setAbortAction(true);\n\t\t\n\t\t}\n      }\n"
  },
  {
    "path": "Server-Side Components/Business Rules/duplicateattachments/README.md",
    "content": "Type: Business Rule When: OnBefore and On Insert Operation  Table:Incident\nThis BR runs on Insert Operation , IT compares the filename from Sysattachment table and if same attachment with Same file exists on the Incident this BR runs and Abort the Attaching Attachments\n"
  },
  {
    "path": "Server-Side Components/Business Rules/openAI/README.md",
    "content": "# ServiceNow ↔ OpenAI ChatGPT Integration  \n**Connect ServiceNow with ChatGPT using REST Message and Business Rule**\n\n---\n\n## Overview\nThis project demonstrates how to integrate **ServiceNow** directly with **OpenAI’s ChatGPT** platform using only native features - \nno Scripted REST APIs, no IntegrationHub, and no plugins required.  \n\nThe integration leverages:\n- A **REST Message** (calling OpenAI’s API endpoint)  \n- A **Business Rule** (triggering automatically on record updates)  \n\nOnce configured, ServiceNow can send ticket details (like short descriptions or work notes) to ChatGPT  \nand store AI-generated responses back in the record — such as incident summaries, resolution hints, or category suggestions.\n\n---\n\n## Key Highlights\n\n100% Native - Uses **RESTMessageV2** and **Business Rule** only  \nGenerates AI-based summaries, causes, or recommendations  \nWorks with **GPT-3.5-Turbo**, **GPT-4-Turbo**, or **GPT-4o-Mini** models  \nReal-time execution after record creation or update  \nNo external plugin dependencies  \n\n---\n\n## Use Case Example\nWhenever a new **Incident** is created or updated, ServiceNow automatically:\n1. Sends the incident’s short description and work notes to **ChatGPT**.  \n2. Receives an AI-generated summary or root cause statement.  \n3. Saves the response in a custom field (`u_ai_summary`).  \n\nThis automation reduces manual summarization and speeds up triage.\n\n---\n\n## Prerequisites\n\n| Requirement | Description |\n|--------------|-------------|\n| **OpenAI API Key** | Obtain from [https://platform.openai.com](https://platform.openai.com) |\n| **Model** | Any `gpt-4o-mini`, `gpt-4-turbo`, or `gpt-3.5-turbo` |\n| **ServiceNow Role** | admin / developer |\n| **Custom Field** | Add a field `u_ai_summary` (String or HTML) on `incident` table |\n\n---\n\n## Step 1 — Create REST Message\n\n**Name:** `OpenAI ChatGPT`  \n**Endpoint:** `https://api.openai.com/v1/chat/completions`  \n**HTTP Method:** `POST`\n\n### Headers\n| Name | Value |\n|------|--------|\n| Content-Type | application/json |\n| Authorization | Bearer `YOUR_OPENAI_API_KEY` |\n\n### Request Body\n```json\n{\n  \"model\": \"gpt-4o-mini\",\n  \"messages\": [\n    { \"role\": \"system\", \"content\": \"You are a helpful ITSM assistant.\" },\n    { \"role\": \"user\", \"content\": \"${prompt}\" }\n  ]\n}\n## — Create Business Rule\n\nTable: incident\nWhen: after insert or after update\nCondition: short_description changes OR work_notes changes\n\nExample Input (Incident Data)\n| Field             | Value                                                  |\n| ----------------- | ------------------------------------------------------ |\n| Short Description | VPN not connecting for remote users                    |\n| Work Notes        | User reports frequent disconnections during peak hours |\n\nExample Response (from ChatGPT)\n{\n  \"choices\": [\n    {\n      \"message\": {\n        \"content\": \"Remote employees are unable to connect to the VPN consistently, possibly due to gateway timeout or bandwidth issues.\"\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "Server-Side Components/Business Rules/openAI/chatGpt.js",
    "content": "/* Secnario :\nWhenever a record (for example, an Incident) is created or updated,\nServiceNow will automatically send a text prompt (like short description or work notes)\nto OpenAI ChatGPT API, get the response, and save it back to a field (like u_ai_summary)*/\n\n-----\n//Business Rule Script (After Insert)\n    try {\n        // Build the prompt dynamically from the record\n        var userPrompt = \"Summarize this issue in one sentence: \" + current.short_description;\n        if (!userPrompt) return;\n\n        // Prepare REST Message\n        var rest = new sn_ws.RESTMessageV2('OpenAI ChatGPT', 'POST'); // name & method\n        rest.setStringParameter('prompt', userPrompt); // replaces ${prompt} in body\n\n        // Execute API call\n        var response = rest.execute();\n        var responseBody = response.getBody();\n        var status = response.getStatusCode();\n\n        if (status != 200) {\n            gs.error(\"OpenAI API error: \" + responseBody);\n            return;\n        }\n\n        // Parse and store response\n        var data = new global.JSON().decode(responseBody);\n        var aiResponse = data.choices[0].message.content;\n\n        // Save to custom field (u_ai_summary)\n        current.u_ai_summary = aiResponse;\n        current.update();\n\n        gs.info(\"AI Summary added for incident: \" + current.number);\n\n    } catch (e) {\n        gs.error(\"Error calling OpenAI ChatGPT: \" + e.message);\n    }\n\n\n"
  },
  {
    "path": "Server-Side Components/Business Rules/setting future week dates/README.md",
    "content": "So the script \"setting future week dates\" is used to set a date field on a record to a future value and use that to trigger reminders to end users\nor external customers. \n\nThe script addresses the use case where the first reminder is expected to be sent 14 days from case create and the rest of the reminders every 3 days, and should be sent only on weekdays.\n\nIf the future computed date falls on a Sat 2 days are added, and if it falls on a Sunday 1 day is added.\n\nUsing this process a flow can be run each day to scan all the case records that have a notification date for the current day and a notification be sent\nto the case contact.\n"
  },
  {
    "path": "Server-Side Components/Business Rules/setting future week dates/setting future dates.js",
    "content": "(function executeRule(current, previous /*null when async*/ ) {\n\n \n    var notdt = new GlideDateTime();\n\t\n// Setting next notification date (NND) to 14 days from record create date or any other state that your record begins with\n\t//change state value per your needs\n\n        if (current.state == 602) {                                             \n            notdt.addDaysLocalTime(14);\n            current.next_notification_date = notdt.getLocalDate();\n        } else if (current.state == 603 || current.state == 18) {               // In Progress, Awaiting Info  or any other state\n            /* Setting the first \"Next Notification Date\" (NND) to 3 weekdays from current day\n               If adding 3 days makes it a Sat we need to add two more days so that the NND is a Monday. \n\t       If adding 3 days makes it a Sat then add two more days so that the NND is a Monday. \n\t       If adding 3 days makes it a Sun then add 1 more day so that the NND is a Monday */\n            notdt.addDaysLocalTime(3);\n            if (notdt.getDayOfWeekLocalTime() == 6) {\n                notdt.addDaysLocalTime(2);\n                current.next_notification_date = notdt.getLocalDate();\n            } else {\n                if (notdt.getDayOfWeekLocalTime() == 7) {\n                    notdt.addDaysLocalTime(1);\n                    current.next_notification_date = notdt.getLocalDate();\n                } else {\n                    current.next_notification_date = notdt.getLocalDate();\n                }\n            }\n        }\n    \n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Business Rules/user-activity-logger/README.md",
    "content": "# User Activity Logger\n\nA ServiceNow server-side script to log user activity with timestamps and session details.\n\n## Description\n\nThis script creates an audit log entry whenever called, recording the current user's information, session ID, and timestamp. Useful for tracking user activities across the platform and maintaining an audit trail of user actions.\n\n## Functionality\n\nThe User Activity Logger provides the following capabilities:\n- Captures current user information (name and user ID)\n- Records session ID for tracking purposes\n- Creates timestamped audit trail records\n- Provides success/error logging with descriptive messages\n- Returns the system ID of the created log entry for further processing\n\n## Usage Instructions\n\n### In Business Rules\n\n```javascript\n// When: before insert\n// Table: incident\n// Script:\n(function executeRule(current, previous /*null when async*/) {\n    // Call the activity logger\n    userActivityLogger();\n})(current, previous);\n```\n\n### In Scheduled Jobs\n\n```javascript\n// Run this as a scheduled job to periodically log user sessions\nuserActivityLogger();\n```\n\n### In Script Includes\n\n```javascript\nvar ActivityLogger = Class.create();\nActivityLogger.prototype = {\n    initialize: function() {\n    },\n    \n    logActivity: function() {\n        return userActivityLogger();\n    },\n    \n    type: 'ActivityLogger'\n};\n```\n\n## Prerequisites\n\n- Access to `sys_audit` table\n- `gs.getUser()` API access\n- Appropriate permissions to insert records into audit tables\n\n## Dependencies\n\n- GlideRecord API\n- GlideDateTime API\n- gs (GlideSystem) API\n\n\n\n## Category\n\nServer-Side Components / Business Rules / User Activity Logger\n\n## Hacktoberfest 2025\n\nCreated for ServiceNow Hacktoberfest 2025 🎃\n\n## License\n\nMIT License\n"
  },
  {
    "path": "Server-Side Components/Business Rules/user-activity-logger/user-activity-logger.js",
    "content": "/**\n * @name User Activity Logger\n * @description Logs user activity with timestamp and session details\n * @author Ashvin (@ashvin2005)\n * @category Server Scripts\n */\n\n(function userActivityLogger() {\n    'use strict';\n    \n    // Get current user information\n    var currentUser = gs.getUser();\n    var userName = currentUser.getName();\n    var userID = currentUser.getID();\n    var sessionID = gs.getSessionID();\n    \n    // Create activity log record\n    var activityLog = new GlideRecord('sys_audit');\n    activityLog.initialize();\n    activityLog.setValue('user', userID);\n    activityLog.setValue('reason', 'User Activity - Session: ' + sessionID);\n    activityLog.setValue('description', 'User ' + userName + ' activity logged at ' + new GlideDateTime());\n    \n    // Insert the record\n    var sysID = activityLog.insert();\n    \n    if (sysID) {\n        gs.info('Activity logged successfully for user: ' + userName);\n        return sysID;\n    } else {\n        gs.error('Failed to log activity for user: ' + userName);\n        return null;\n    }\n})();\n\n/**\n * Usage:\n * This script can be used in Business Rules, Scheduled Jobs, or Script Includes\n * to track user activities within ServiceNow.\n * \n * Example in Business Rule:\n * - When: before insert\n * - Table: incident\n * - Script: Call this function to log when users create incidents\n */"
  },
  {
    "path": "Server-Side Components/Extension Points/CustomNotificationHandlerInterfaceExtension.js",
    "content": "var CustomNotificationHandlerInterface = Class.create();\nCustomNotificationHandlerInterface.prototype = {\n    /**\n     * Initialize the notification handler\n     * @param {GlideRecord} record - The record triggering the notification\n     * @param {Object} config - Configuration object\n     */\n    initialize: function(record, config) {\n        this.record = record;\n        this.config = config || {};\n    },\n\n    /**\n     * Process the notification\n     * @returns {Object} Result object with status and message\n     */\n    process: function() {\n        throw new Error('process() must be implemented by extension');\n    },\n\n    /**\n     * Validate if notification should be sent\n     * @returns {Boolean} True if notification should be sent\n     */\n    shouldNotify: function() {\n        throw new Error('shouldNotify() must be implemented by extension');\n    },\n\n\t/**\n     * handles if the implementation needs to run\n     * @returns {Boolean} True if implementation will run\n     */\n    handles: function(notificationSystem) {\n        return notificationSystem == \"DEFAULT\";\n    },\n\n    type: 'CustomNotificationHandlerInterface'\n};\n"
  },
  {
    "path": "Server-Side Components/Extension Points/CustomNotificationHandlerInterfaceImplementation.js",
    "content": "var CustomNotificationHandlerInterface = Class.create();\nCustomNotificationHandlerInterface.prototype = {\n    /**\n     * Initialize the notification handler\n     * @param {GlideRecord} record - The record triggering the notification\n     * @param {Object} config - Configuration object\n     */\n    initialize: function(record, config) {\n        this.record = record;\n        this.config = config || {};\n    },\n\n    /**\n     * Process email notification\n     * @returns {Object} Result with status and details\n     */\n    process: function() {\n        try {\n            if (!this.shouldNotify()) {\n                return {\n                    success: false,\n                    message: 'Notification criteria not met'\n                };\n            }\n\n            var email = new GlideEmailOutbound();\n            email.setSubject(this._buildSubject());\n            email.setBody(this._buildBody());\n            email.setRecipients(this.emailConfig.recipients || '');\n            email.send();\n\n            return {\n                success: true,\n                message: 'Email notification sent successfully'\n            };\n        } catch (e) {\n            gs.error('EmailNotificationHandler error: ' + e.message);\n            return {\n                success: false,\n                message: 'Error sending notification: ' + e.message\n            };\n        }\n    },\n\n    /**\n     * Validate notification criteria\n     * @returns {Boolean}\n     */\n    shouldNotify: function() {\n        if (!this.record || !this.record.isValidRecord()) {\n            return false;\n        }\n\n        // Check if priority is high enough\n        var priority = this.record.getValue('priority');\n        var minPriority = this.config.minPriority || 3;\n\n        return parseInt(priority) <= parseInt(minPriority);\n    },\n\n    /**\n     * Build email subject\n     * @returns {String}\n     * @private\n     */\n    _buildSubject: function() {\n        var template = this.emailConfig.subjectTemplate || 'Notification: ${number}';\n        return GlideStringUtil.substitute(template, {\n            number: this.record.getDisplayValue('number'),\n            short_description: this.record.getDisplayValue('short_description')\n        });\n    },\n\n    /**\n     * Build email body\n     * @returns {String}\n     * @private\n     */\n    _buildBody: function() {\n        var body = 'Record Details:\\n';\n        body += 'Number: ' + this.record.getDisplayValue('number') + '\\n';\n        body += 'Short Description: ' + this.record.getDisplayValue('short_description') + '\\n';\n        body += 'Priority: ' + this.record.getDisplayValue('priority') + '\\n';\n        body += 'State: ' + this.record.getDisplayValue('state') + '\\n';\n        return body;\n    },\n\n    /**\n     * handles if the implementation needs to run\n     * @returns {Boolean} True if implementation will run\n     */\n    handles: function(notificationSystem) {\n        return notificationSystem == \"Email\";\n    },\n\n    type: 'CustomNotificationHandlerInterface'\n};"
  },
  {
    "path": "Server-Side Components/Extension Points/ExtensionPointCallingExample.js",
    "content": "var eps = new GlideScriptedExtensionPoint().getExtensions(\"x_snc_etension_p_0.CustomNotificationHandlerInterface\");\nfor (var i = 0; i < eps.length; i++) {\n\t// checking which extension point works\n\tgs.info(\"Does the extension point applies: \" + eps[i].handles(\"Email\"));\n    if (eps[i].handles(\"Email\")) {\n\t\t//invoking the process function\n        eps[i].process();\n    }\n}"
  },
  {
    "path": "Server-Side Components/Extension Points/README.md",
    "content": "# Extension Points\n\n## Overview\n\nExtension Points in ServiceNow provide a powerful mechanism for creating extensible, plugin-based architectures. They allow you to define interfaces that can be implemented by multiple extensions, enabling dynamic behavior selection at runtime without modifying core code.\n\n## What are Extension Points?\n\nExtension Points follow the interface-implementation pattern where:\n- **Extension Point (Interface)**: Defines a contract with methods that implementations must provide\n- **Extensions (Implementations)**: Concrete classes that implement the interface methods\n- **Consumer**: Code that discovers and invokes extensions dynamically using `GlideScriptedExtensionPoint`\n\nThis pattern is ideal for:\n- Creating pluggable notification systems\n- Building extensible validation frameworks\n- Implementing strategy patterns\n- Supporting multi-tenant customizations\n- Enabling third-party integrations\n\n## Files in this Directory\n\n### 1. CustomNotificationHandlerInterfaceExtension.js\nDefines the base interface (Extension Point) for notification handlers.\n\n**Key Methods:**\n- `initialize(record, config)`: Constructor for setting up the handler\n- `process()`: Must be implemented - processes the notification\n- `shouldNotify()`: Must be implemented - validates if notification should be sent\n- `handles(notificationSystem)`: Determines if this implementation applies to a specific system\n\n### 2. CustomNotificationHandlerInterfaceImplementation.js\nConcrete implementation of the notification handler interface for email notifications.\n\n**Features:**\n- Email notification processing using `GlideEmailOutbound`\n- Priority-based notification criteria\n- Template-based subject and body generation\n- Error handling and logging\n- Configurable recipients and templates\n\n### 3. ExtensionPointCallingExample.js\nDemonstrates how to discover and invoke extension point implementations dynamically.\n\n**Pattern:**\n```javascript\nvar eps = new GlideScriptedExtensionPoint().getExtensions(\"scope.InterfaceName\");\nfor (var i = 0; i < eps.length; i++) {\n    if (eps[i].handles(\"criteria\")) {\n        eps[i].process();\n    }\n}\n```\n\n## How to Use Extension Points\n\n### Step 1: Create the Extension Point (Interface)\n\nCreate a Script Include with the interface definition:\n\n```javascript\nvar CustomNotificationHandlerInterface = Class.create();\nCustomNotificationHandlerInterface.prototype = {\n    initialize: function(record, config) {\n        this.record = record;\n        this.config = config || {};\n    },\n    \n    process: function() {\n        throw new Error('process() must be implemented by extension');\n    },\n    \n    shouldNotify: function() {\n        throw new Error('shouldNotify() must be implemented by extension');\n    },\n    \n    handles: function(notificationSystem) {\n        return notificationSystem == \"DEFAULT\";\n    },\n    \n    type: 'CustomNotificationHandlerInterface'\n};\n```\n\n**Important Configuration:**\n- Set **API Name** to match the type (e.g., `CustomNotificationHandlerInterface`)\n- Check **Client callable** if needed from client-side\n- Set appropriate **Application** scope\n\n### Step 2: Create Extension Point Record\n\n1. Navigate to **System Definition > Extension Points**\n2. Create a new Extension Point record:\n   - **Name**: Descriptive name (e.g., \"Custom Notification Handler Interface\")\n   - **API name**: Full scoped name (e.g., `x_snc_extension_p_0.CustomNotificationHandlerInterface`)\n   - **Example implementation**: Reference your interface Script Include\n\n### Step 3: Create Implementations (Extensions)\n\nCreate Script Includes that extend the interface:\n\n```javascript\nvar EmailNotificationHandler = Class.create();\nEmailNotificationHandler.prototype = Object.extendsObject(CustomNotificationHandlerInterface, {\n    \n    process: function() {\n        // Implementation-specific logic\n        var email = new GlideEmailOutbound();\n        email.setSubject(this._buildSubject());\n        email.setBody(this._buildBody());\n        email.send();\n        \n        return { success: true, message: 'Email sent' };\n    },\n    \n    shouldNotify: function() {\n        // Custom validation logic\n        return this.record.getValue('priority') <= 3;\n    },\n    \n    handles: function(notificationSystem) {\n        return notificationSystem == \"Email\";\n    },\n    \n    type: 'EmailNotificationHandler'\n});\n```\n\n### Step 4: Register Extensions\n\n1. Navigate to **System Definition > Extensions**\n2. Create Extension records for each implementation:\n   - **Extension point**: Select your Extension Point\n   - **Name**: Implementation name\n   - **Implementation**: Reference your implementation Script Include\n\n### Step 5: Invoke Extensions Dynamically\n\nUse `GlideScriptedExtensionPoint` to discover and execute extensions:\n\n```javascript\n// Get all extensions for the interface\nvar eps = new GlideScriptedExtensionPoint().getExtensions(\"x_snc_extension_p_0.CustomNotificationHandlerInterface\");\n\n// Iterate and invoke matching extensions\nfor (var i = 0; i < eps.length; i++) {\n    gs.info(\"Checking extension: \" + eps[i].type);\n    \n    if (eps[i].handles(\"Email\")) {\n        var result = eps[i].process();\n        gs.info(\"Result: \" + JSON.stringify(result));\n    }\n}\n```\n\n## Best Practices\n\n### 1. Interface Design\n- Define clear contracts with well-documented methods\n- Use JSDoc comments for all interface methods\n- Throw errors for unimplemented required methods\n- Provide sensible defaults in the base interface\n\n### 2. Implementation Guidelines\n- Always implement all required interface methods\n- Use the `handles()` method to control when implementation applies\n- Include comprehensive error handling\n- Return consistent result objects\n- Log important operations for debugging\n\n### 3. Naming Conventions\n- Use descriptive names for Extension Points\n- Follow ServiceNow naming standards\n- Include scope prefix in API names\n- Use consistent naming across interface and implementations\n\n### 4. Performance Considerations\n- Cache extension instances when possible\n- Avoid heavy processing in `handles()` method\n- Use early returns to skip unnecessary processing\n- Consider lazy initialization for expensive resources\n\n### 5. Testing\n- Test each implementation independently\n- Verify `handles()` logic with various inputs\n- Test error handling and edge cases\n- Validate behavior when no extensions match\n\n## Common Use Cases\n\n### 1. Multi-Channel Notifications\nCreate extensions for Email, SMS, Slack, Teams, etc., all implementing a common notification interface.\n\n### 2. Validation Frameworks\nBuild extensible validation systems where different validators can be plugged in based on record type or business rules.\n\n### 3. Data Transformation\nImplement multiple transformation strategies that can be selected dynamically based on data source or format.\n\n### 4. Integration Adapters\nCreate adapters for different external systems, all conforming to a common integration interface.\n\n### 5. Approval Workflows\nBuild flexible approval mechanisms where different approval strategies can be applied based on conditions.\n\n## Troubleshooting\n\n### Extensions Not Found\n- Verify Extension Point API name matches exactly\n- Check that Extension records are active\n- Ensure Script Includes are in the correct application scope\n- Verify no typos in scope prefix\n\n### Methods Not Executing\n- Confirm `handles()` method returns true for your criteria\n- Check for errors in implementation code\n- Verify Script Include is set to correct type\n- Review logs for error messages\n\n### Performance Issues\n- Reduce number of extensions if possible\n- Optimize `handles()` method logic\n- Consider caching extension instances\n- Profile code to identify bottlenecks\n\n## Additional Resources\n\n- [ServiceNow Extension Points Documentation](https://docs.servicenow.com)\n- [GlideScriptedExtensionPoint API Reference](https://developer.servicenow.com/dev.do#!/reference/api/latest/server/no-namespace/c_GlideScriptedExtensionPointScopedAPI)\n- [Script Includes Best Practices](https://developer.servicenow.com/dev.do#!/learn/learning-plans/tokyo/new_to_servicenow/app_store_learnv2_scripting_tokyo_script_includes)\n\n## Example Scenarios\n\n### Scenario 1: Priority-Based Email Notifications\nUse the provided implementation to send email notifications only for high-priority incidents (priority 1-3).\n\n### Scenario 2: Multi-System Notification Router\nCreate multiple implementations (Email, SMS, Slack) and route notifications based on user preferences or incident severity.\n\n### Scenario 3: Custom Business Logic\nExtend the interface to add custom validation rules, approval logic, or data transformation specific to your organization.\n\n## Contributing\n\nWhen adding new Extension Point examples:\n1. Include both interface and at least one implementation\n2. Provide clear documentation in code comments\n3. Add usage examples\n4. Include screenshots of configuration if helpful\n5. Follow the repository's code quality standards\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Advanced Scripts/README.md",
    "content": "## These inbound action scripts will help you with the following:\n\n### 1) Creation - Map values from an email to variables and create a requested item for the same.\nCheck with script -> create_catalog_item_from_email.js  \n\nThis script will help you map the values coming with the email body to variables of a catalog items and submit a catalog item. In this particular script we're submitting the request for user-offboarding by mapping the userID and termination date. So, in a similar way as per your requirement you can map/add variables from an email to submit an catalog item.\n\n### 2) Updating - Check with already created requested item and update the variables and restart the workflow.\nCheck with script -> update_catalog_item_from_email.js\n\nNow, Once you've successfully mapped the values and submitted an item from the email that first came into ServiceNow, there can be a case scenario where we need to update the termination date to a new value. This script will help you query the existing RITM for the user and update the value for termination date to the new values that came into the new email and restart the workflow. So, again as per your requirement in a similar way you can query the existing RITM and update variables coming in with an email and update the RITM.\n\n### 3) Cancellation - Check with already created requested item and cancel the same also cancel the workflow.\nCheck with script -> cancel_catalog_item_from_email.js\n\nThird Scenario, here is once a termination request is submitted, now we need to re-hire the candidate. So, in this case we need to cancel the RITM. \nThis script will help you query the existing RITM for the user and cancel the RITM and all related tasks. So, as per your case scenario in a similar way you can query the existing RITM and cancel it with all the related catalog tasks.\n\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Advanced Scripts/cancel_catalog_item_from_email.js",
    "content": "//variable values\nvar worker = email.body.name;\nvar userid = email.body.user_id;\n\nvar userRec = new GlideRecord(\"sys_user\");\nuserRec.addQuery(\"user_name\", userid);\nuserRec.query();\nif (userRec.next()) {\n   var userID = userRec.getUniqueValue();\n}\n\n//query the exisiting RITM and cancel the same.\nvar ritm = new GlideRecord('sc_req_item');\nvar eq = 'cat_item=SYS_ID_OF_THE_ITEM^active=true^request.requested_for='+ userID; //Update the sys_id of the item and update the query as you need.\nritm.addEncodedQuery(eq);\nritm.query();\nif (ritm.next())\n{\n\tritm.work_notes = \"received from: \" + email.origemail + \"\\n\\n\" + email.body_text;\n\tritm.state = '7';\n\tritm.stage = 'Completed';\n\tvar workflow = new Workflow();\n\tworkflow.cancel(ritm);\n    \tritm.update();\n\n\t//query related catalog task and close the same as well.\n\tvar rec = new GlideRecord('sc_task');\n\trec.addQuery('request_item', ritm.getUniqueValue());\n\trec.query();\n\twhile(rec.next()){\n\trec.state = '7';\n\trec.update();\n\t}\n\t\n   }\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Advanced Scripts/create_catalog_item_from_email.js",
    "content": "createRequest();\n\nfunction createRequest() {\n\n   var cart = new Cart();   //calling the cart API\n\n   var item = cart.addItem('SYS_ID');   //Add sys_id of the catag item \n\n// set variables\nvar worker = email.body.name;\nvar termination = email.body.date;\nvar term_d = new GlideDateTime();\nterm_d.setDisplayValue(termination);\nvar userid = email.body.user_id;\n\nvar userRec = new GlideRecord(\"sys_user\");\nuserRec.addQuery(\"user_name\", userid);\nuserRec.query();\nif (userRec.next()) {\n   var userID = userRec.getUniqueValue();\n}\n\ncart.setVariable(item, 'select_employee', userID);\ncart.setVariable(item, 'termination_date', term_d);\n\nvar rc = cart.placeOrder();   \nvar ritmSys = rc.number;\n\nupdateRITM(rc.sys_id);   //call a function immediately to update the ritm.           \n}\n\nfunction updateRITM(req){\n\n   var ritm = new GlideRecord('sc_req_item');\n   ritm.addQuery('request', req);   \n   ritm.query();\n   while (ritm.next()){\n       ritm.description = email.body_text;   \n       ritm.update();\n\n   }\n\n}\nevent.state=\"stop_processing\";   \n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Advanced Scripts/update_catalog_item_from_email.js",
    "content": "//variable values\nvar worker = email.body.name;\nvar termination = email.body.date;\nvar term_d = new GlideDateTime();\nterm_d.setDisplayValue(termination);\nvar userid = email.body.user_id;\n\nvar userRec = new GlideRecord(\"sys_user\");\nuserRec.addQuery(\"user_name\", userid);\nuserRec.query();\nif (userRec.next()) {\n   var userID = userRec.getUniqueValue();\n}\n\n//query the exisiting RITM and update the same.\nvar ritm = new GlideRecord('sc_req_item');\nvar eq = 'cat_item=SYS_ID_OF_THE_ITEM^active=true^request.requested_for='+ userID; //Update the sys_id of the item and update the query as you need.\nritm.addEncodedQuery(eq);\nritm.query();\nif(ritm.next()){\n\tritm.work_notes = \"received from: \" + email.origemail + \"\\n\\n\" + email.body_text;\n\tritm.variables.termination_date = term_d;\n    \tritm.description = email.body_text;  \n\tnew Workflow().restartWorkflow(ritm);\n    ritm.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Auto Incident Creation from Case Email/README.md",
    "content": "**Auto Incident Creation from Case Email** \n\n**Description** \nThis inbound email action automatically creates an incident record when a customer replies to a case email containing keywords like outage, crash, error, or not working. \nThe new incident links to the case as a parent and inherits caller and configuration item details. \n\n**Use Case** \nIdeal for CSM environments integrated with ITSM, where customer issues escalate to incidents automatically. \n\n**How It Works**\n1. The inbound email action scans the subject and body for trigger keywords.\n2. If a match is found:\n   - A new incident record is created.\n   - The incident is linked to the case as a parent.\n   - Caller and CI are inherited if available.\n   - Work notes of both case and incident records are updated. \n\n**Inbound Action Configuration**\n   - Table: Case[sn_customerservice_case]\n   - Action type: Record Action\n   - Type: Reply\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Auto Incident Creation from Case Email/create_incident_from_case_email.js",
    "content": "(function runAction(/*GlideRecord*/ current, /*GlideRecord*/ event, /*EmailWrapper*/ email, /*ScopedEmailLogger*/ logger, /*EmailClassifier*/ classifier) {\n\n\t//Extract email subject and body\n\tvar subject = email.subject.toLowerCase();\n\tvar body = email.body_text.toLowerCase();\n\n\t//Define trigger keywords for creating the incident\n\tvar keywords = ['outage', 'crash', 'down', 'error', 'issue', 'not working', 'failure', 'slow'];\n\tvar trigger = false;\n\n\tfor(var i = 0; i < keywords.length; i++){\n\t\tif(subject.includes(keywords[i]) || body.includes(keywords[i])){\n\t\t\ttrigger = true;\n\t\t\tbreak;\n\t\t}\n\t}\n\n\t//Execute only if trigger word found\n\tif(trigger){\n\t\t//Create a new incident record\n\t\tvar inc = new GlideRecord('incident');\n\t\tinc.initialize();\n\t\tinc.short_description = 'Auto-created from Case ' + current.number + ': ' + email.subject;\n\t\tinc.description = 'Customer reported via Case ' + current.number + ':\\n' + email.body_text;\n\t\tinc.parent = current.sys_id;\n\n\t\t//Link caller and CI from case to incident if available\n\t\tif(current.contact)\n\t\t\tinc.caller_id = current.contact;\n\t\tif(current.cmdb_ci)\n\t\t\tinc.cmdb_ci = current.cmdb_ci;\n\n\t\tvar incSysId = inc.insert();\n\n\t\t//Update case work notes\n\t\tcurrent.work_notes = 'Incident ' + inc.number + ' created automatically from customer email.';\n\t\tcurrent.update();\n\n\t\t//Add incident work notes\n\t\tvar newInc = new GlideRecord('incident');\n\t\tif(newInc.get(incSysId)){\n\t\t\tnewInc.work_notes = 'Auto-created from Case ' + current.number + '.\\n\\nEmail Content:\\n' + email.body_text;\n\t\t\tnewInc.update();\n\t\t}\n\t}\n})(current, event, email, logger, classifier);\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Auto Reply Email/README.md",
    "content": "There are use cases where it has been requested to setup an \"auto reply\" type process within ServiceNow\n\nTo accomplish this, you'll need to do 3 steps:\n1) Create an event - https://docs.servicenow.com/bundle/rome-platform-administration/page/administer/platform-events/task/t_CreateYourOwnEvent.html\n2) Create a notification - https://docs.servicenow.com/bundle/rome-servicenow-platform/page/administer/notification/task/t_CreateANotification.html:\n- Click the \"Advanced view\" related link on the notification form\n- Select the table associated to this process\n- Set the \"When to send\" condition to send when \"Event is fired\"\n- Select your event you created from step 1 within the \"Event name\" field\n- Within the \"Who will receive\" tab, set the \"Event parm 1 contains recipient\" checkbox to true\n- Within the \"What it will contain\" tab, set your subject/message, etc.\n3) Create or edit an inbound action record to house the script within the code.js file from this code-snippet folder\n\nAdditional things to keep in mind is that this setup is recommended for an already established process/inbound action, so you can simply take the script from code.js and add it to your current script at the point where you'd like the email sent. If this is for a new inbound action, select the table that matches the table you set on the notification. You'll want to ensure that the \"Execution order\" field within the inbound action is set low enough number that this process will not be blocked by other inbound action that was evaluated before it and has its \"Stop processing\" checkbox set to true. For the inbound action you've created, also ensure the \"Stop processing\" checkbox is not checked if you require additional inbound actions to evaluate the inbound email and complete other actions.\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Auto Reply Email/code.js",
    "content": "//Please refer to the readme file which contains all the steps necessary for this to properly work\n//The below code is for use within the inbound action and should be placed at the point within your script where you'd like to generate the event which will trigger the notification\n\ngs.eventQueue('event_name', current, email.from, ''); //replace event_name with the name of your event -- this script will take the inbound email \"from\" address and pass that as parm 1 with this event\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Automate creation of incidents through inbound actions/README.md",
    "content": "This is a simple code to automate creation of incidents for the alerts receiving to ServiceNow instance. This code has functionality of adding receipients (excluding service-now instance emails) of email to watchlist of incident.\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Automate creation of incidents through inbound actions/incidentCreation.js",
    "content": "var recipients= email.recipients;   //get recipients of email.\nvar recipientsSplit= recipients.split(\",\");\nvar finalRecipients=\"\";\n// Add execution condition on \"When to run\" tab. The condition is \" User is email address of the alert receiving from.\" Here User is field on the \"sys_email\" table\ncurrent.caller_id = gs.getUserID();\ncurrent.comments = \"received from: \" + email.origemail + \"\\n\\n\" + email.body_text; // update incident comments with email body.\ncurrent.short_description = email.subject;\ncurrent.business_service = \"e32e0a921b223010d1462f8a2d4bcb68\";\ncurrent.assignment_group = \"b5a28e7c1bcc1d50789adbd7b04bcbb9\";\ncurrent.incident_state = IncidentState.NEW;\ncurrent.notify = 2;\ncurrent.contact_type = \"email\";\ncurrent.description= email.body_text;\nor(var i=0; i<recipientsSplit.length;i++)\n{\nif(!recipientsSplit[i].includes(\"service-now.com\")){ // exclude service-now instance emails.\n\n\tfinalRecipients=finalRecipients+\",\"+recipientsSplit[i];\n\t}\n\n}\ncurrent.watch_list = finalRecipients;// update current incident with reciepients as watch_list members\ncurrent.insert();\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Duplicate Incident Detection and Creation/README.md",
    "content": "**Duplicate Incident Detection and Creation**\n\n**Description**\nThis inbound email action detects duplicate incidents from incoming emails and either updates existing incidents or creates a new one.\n\n- Duplicate Found: Updates the existing incident's work notes with the new email content and aborts new incident creation.\n- No Duplicate Found: Creates a new incident with the email subject as short description and the email body as description.\n\n**Use Case**\nWhen multiple users report the same issue via email, this automation prevents duplicate incidents, keeping the incident queue cleaner and improving triage efficiency.\n\n**Inbound Action Configuration**\n- Target Table: incident \n- Action Type: Record Action\n- Type: New\n\n**How It Works**\n1. The inbound email action scans the email subject for matches against active incidents whose short description contains the subject excluding Closed or Cancelled incidents.   \n2. If a matching incident is found:\n   - Updates the incident's work notes with the email content.\n   - Aborts creation of a new incident.  \n3. If no match is found:\n   - Creates a new incident using the email subject as the short description and the email body as the description.\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Duplicate Incident Detection and Creation/script.js",
    "content": "(function runAction(current, event, email, logger, classifier) {\n    var subject = email.subject.trim();\n\n    //Look for duplicate incident\n    var gr = new GlideRecord('incident');\n    gr.addActiveQuery();\n    gr.addQuery('short_description', 'CONTAINS', subject);\n    gr.addQuery('state', 'NOT IN', '3,4'); //Ignore resolved or closed incidents\n    gr.query();\n\n    if(gr.next()){\n        //Update existing incident with duplicate email\n        gr.work_notes = 'Duplicate email received for this incident:\\n' + email.body_text;\n        gr.update();\n\n        //Abort creating new incident\n        action.setAbortAction(true);\n        return;\n    }\n\n\t//Create new incident if no duplicate incident found\n    current.short_description = email.subject;\n    current.description = email.body_text;\n\tcurrent.update();\n})(current, event, email, logger, classifier);\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Email Text as Attachment/README.md",
    "content": "# Save Email Text as Attachment\n\n### Steps:\n\nNavigate to System Definition \\ Script Includes and click New.\nSet the following values:<br />\n**Name:** emailAsAttachmentUtil<br />\n**Accessible from:** All application Scopes = this will allow it to be called by all applications<br />\n**Active:** checked<br />\n**Description:** You may want to set the description to something like the following to document what this script includes does and how to call it<br />\n\n* [Click here for Script include script](script.js)\n\n**Example of calling a script include from the Inbound action**\n```js\n//This utility script will take contents from an inbound email and create an attachment on the created record from the inbound email action.  To utilize this script, add the following lines at the end of the inbound email action script:\nvar emailAsAttachment = new global.emailAsAttachmentUtil();\nemailAsAttachment.createAttachment(email, current);\n```\n\n\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Email Text as Attachment/script.js",
    "content": "\n/**********************************************************************************************************************\nCreate a Script Include:\nName: emailAsAttachmentUtil\nAccessible from: All application Scopes = this will allow it to be called by all applications\nActive: checked\nDescription: You may want to set the description to something like the following to document what this script includes does and how to call it\n\n\nThis utility script will take contents from an inbound email and create an attachment on the created record from the inbound email action.  To utilize this script, add the following lines at the end of the inbound email action script:\nvar emailAsAttachment = new global.emailAsAttachmentUtil();\nemailAsAttachment.createAttachment(email, current);\n***********************************************************************************************************************/\n\nvar emailAsAttachmentUtil = Class.create();\nemailAsAttachmentUtil.prototype = {\n    initialize: function() {\n\t\tthis.newLineChar = \"\\r\\n\";  // Microsoft Windows expects \\r and \\n for return and new line\n\t\tthis.contentType = \"text/plain\";\n    },\n\t\n\tcreateAttachment: function (emailRec, currentRec) {\n\t\tvar fileName = emailRec.subject + '.eml';\n\t\t\n\t\t// Setup array to push email values into.  Add additional as needed/\n\t\tvar emailData = [];\n\t\temailData.push(\"To: \" + emailRec.to);\n\t\temailData.push(\"Subject: \" + emailRec.subject);\n\t\temailData.push(\"From: \" + emailRec.origemail);\n\t\temailData.push(emailRec.body_text);\n\t\t\n\t\t// Convert emailData to a string separated by new line character.\n\t\tvar emailString = emailData.join(this.newLineChar);\n\t\t\n\t\t// Create attachment with email string and attach it to the record creatd by the email.\n\t\tvar sysAttachment = new GlideSysAttachment();\n\t\tsysAttachment.write(currentRec, fileName, this.contentType, emailString);\n\t},\n\n    type: 'emailAsAttachmentUtil'\n};\n\n\n/**********************************************************************************************************************\nNavigate to System Policy \\ Email \\ Inbound Actions and open the one that you want to capture the contents of the email as an attachment.\nGo to the Actions Tab and scroll to the bottom of the script and paste in the following:\n***********************************************************************************************************************/\n\nvar emailAttachment = new global.emailAsAttachmentUtil();\nemailAttachment.createAttachment(email, current);\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Inbound Email Action to Create User and Assign Groups/Readme.md",
    "content": "Inbound Email Action to Create User and Assign Groups\n\nIf an admin sends an email with specific user details, the script automatically:\nCreates a new user (if not existing).\nAssigns them to multiple groups.\n\nCreate new Inbound Action:\nTarget table: sys_user\nType: New / Reply (depending on how you want it triggered)\n\nExample Email Format\n\nSubject: Create New User\nName: Abc Xyz\nEmail: abc.xyz@example.com\nUserID: abc_xyz\nDepartment: IT\nGroups: Network Team, Application Support, Database Admins\n\nWorking:\n\nScript reads each line from email body.\nExtracts values for each field (Name, Email, etc.) using regex.\nChecks if the user exists → if not, creates it.\nAdds the user to the given list of groups.\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Inbound Email Action to Create User and Assign Groups/auto_create_user_through_inbound_email_action.js",
    "content": "(function runInboundAction(email, action) {\n\n    var body = email.body_text;\n\n    // Function to extract field values using regex\n    function fetchFieldValue(label) {\n        var regex = new RegExp(label + \"\\\\s*:\\\\s*(.*)\", \"i\");\n        var match = body.match(regex);\n        return match ? match[1].trim() : \"\";\n    }\n\n    // Extract values from email\n    var name = fetchFieldValue(\"Name\");\n    var emailAddr = fetchFieldValue(\"Email\");\n    var userID = fetchFieldValue(\"UserID\");\n    var department = fetchFieldValue(\"Department\");\n    var groupsStr = fetchFieldValue(\"Groups\");\n    var groups = groupsStr ? groupsStr.split(\",\") : [];\n\n    if (!emailAddr) {\n        gs.log(\"Inbound Email User Creation: Email missing. Aborting.\");\n        return;\n    }\n\n    // Check if user already exists\n    var existingUser = new GlideRecord(\"sys_user\");\n    existingUser.addQuery(\"email\", emailAddr);\n    existingUser.query();\n\n    var userSysId;\n\n    if (existingUser.next()) {\n        gs.log(\"User already exists: \" + emailAddr);\n        userSysId = existingUser.sys_id.toString();\n    } else {\n        // Create new user\n        var newUser = new GlideRecord(\"sys_user\");\n        newUser.initialize();\n        newUser.name = name;\n        newUser.email = emailAddr;\n        newUser.user_name = userID;\n        newUser.department = department;\n        userSysId = newUser.insert();\n        gs.log(\"New user created: \" + name + \" (\" + emailAddr + \")\");\n    }\n\n    // Add user to groups\n    groups.forEach(function(groupName) {\n        groupName = groupName.trim();\n        if (groupName) {\n            var group = new GlideRecord(\"sys_user_group\");\n            group.addQuery(\"name\", groupName);\n            group.query();\n            if (group.next()) {\n                var mem = new GlideRecord(\"sys_user_grmember\");\n                mem.initialize();\n                mem.user = userSysId;\n                mem.group = group.sys_id;\n                mem.insert();\n                gs.log(\"User added to group: \" + groupName);\n            } else {\n                gs.log(\"Group not found: \" + groupName);\n            }\n        }\n    });\n\n})(email, action);\n\n\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Incident Creation from Email with Attachments Handling/README.md",
    "content": "Description\nThe Incident Creation from Email with Attachments Handling functionality in ServiceNow allows users to create incident records directly from incoming emails. \nThis automation streamlines the incident management process, enabling users to report issues efficiently without the need to log\ninto the ServiceNow platform. When an email is received, the script extracts critical information from the email, such as the subject and \nbody, to populate the incident fields. Additionally, any attachments \nincluded in the email are automatically linked to the incident, ensuring that all relevant context is preserved and easily accessible for support teams.\n\nKey Features :\n1) Automatic Incident Creation: Converts incoming emails into incident records, minimizing manual data entry.\n\n2) Dynamic Field Population:\nShort Description: Uses the email subject as the incident short description.\nDetailed Description: Captures the email body as the incident description.\nCaller Identification: Automatically sets the email sender as the incident caller for accurate tracking.\n\n3) Attachment Support: Handles multiple attachments, linking them to the incident record for context.\n\n4) Customizable Logic: Easily modify the script to route incidents based on keywords in emails.\n\n5) Error Handling and Logging: Integrates error handling to log issues during the incident creation process.\n\n6) Enhanced User Experience: Enables quick issue reporting via email, improving user satisfaction.\n\n7) Tracking and Reporting: Allows tracking of incidents created via email for analysis of trends and response times.\n\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Incident Creation from Email with Attachments Handling/Script.js",
    "content": "// Email Inbound Action: Create Incident from Email\n(function() {\n    var incidentGR = new GlideRecord('incident');\n    incidentGR.initialize();\n    \n    // Set the incident details from the email\n    incidentGR.short_description = email.subject; // Use email subject as short description\n    incidentGR.description = email.body; // Use email body as description\n    incidentGR.caller_id = email.from; // Set caller from the email sender\n\n    // Insert the new incident and capture the sys_id\n    var incidentID = incidentGR.insert();\n\n    // Handle attachments\n    var attachments = email.attachments;\n    if (attachments) {\n        for (var i = 0; i < attachments.length; i++) {\n            var attachmentGR = new GlideRecord('sys_attachment');\n            attachmentGR.initialize();\n            attachmentGR.table_name = 'incident'; // Link to the incident table\n            attachmentGR.table_sys_id = incidentID; // Link to the newly created incident\n            attachmentGR.file_name = attachments[i].file_name; // Attach the file name\n            attachmentGR.content_type = attachments[i].content_type; // Attach the content type\n            attachmentGR.insert(); // Save the attachment\n        }\n    }\n})();\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Reply Task/README.md",
    "content": "#contribution\n\nAn SC Task is assigned to the requester. When the requester replies with the following format:\n\nAre you good to proceed? Yes\nDate: 20-10-2025\n\n…the reply information is automatically extracted and populated into custom fields on the SC Task record.\n\nTwo custom fields have been created for this purpose:\n\nDate:20/10/2025\n\nProcess: Yes\n\nInbound Email Action:\n---------------------\nCreated Reply Inbound email action on sc_task table.\nTable - sc_task\nType - Reply\nCondition - Subject : has been assigned for Satiesfiction.\n\nObjective of the code:\n-----------------------\n\nWhen a requester replies to an SC Task email with specific text (like \"Are you good to Processed? Yes\" and \"Date: 2025-10-20\"), this script:\n\nUpdates task comments with the reply\nUses regex to search for date and tries to extract a Yes/No response from a line like:\n\nAre you good to proceed? Yes\nDate: 20-10-2025\n\nPopulates two custom fied the SC Task (u_date, u_processed)\n\nSets the task's state to 3 (Completed) once reply done & extract and auto population performed.\n"
  },
  {
    "path": "Server-Side Components/Inbound Actions/Reply Task/reply.js",
    "content": "gs.include('validators');\n\nif (current.getTableName() == \"sc_task\") {\n\n    // Update comments on current sc_task and save it\n    current.comments = \"reply from: \" + email.origemail + \"\\n\\n\" + email.body_text;\n   \n\n    // Parse Date (DD-MM-YYYY)\n    var DateMatch = email.body_text.match(/Date:\\s*([\\d]{2}-[\\d]{2}-[\\d]{4})/i);\n\n    var DateStr = DateMatch ? DateMatch[1] : null;\n\n    // Parse \"Are you good to Processed \"\n    var process = email.body_text.match(/Are you good to Processed\\?\\s*:\\s*(Yes|No)/i);\n  \n    var proceeStr = process ? procee.match[1].toLowerCase() : null;\n\n    \n     if (DateStr) {\n                var gd = new GlideDate();\n                gd.setValue(DateStr);\n                current.setValue('u_date', gd); // replace with field\n               \n            }\n\n            // Update \"Are you good to Process\" if found\n            if (proceeStr) {\n               \n                var normalizedInput = proceeStr.toLowerCase();\n\n                var choiceValue = null;\n                if (normalizedInput === 'yes') {                          //converting Yes/ No field to 1 , 2  as per backend field sc_task\n                    choiceValue = '1'; // choice value for Yes\n                } else if (normalizedInput === 'no') {\n                    choiceValue = '2'; // choice value for No\n                }\n\n                if (choiceValue) {\n                    current.setValue('u_processed', choiceValue); //set value in custom field \n                    \n                } \n            }\n       \n       \n        \n    }\n\n    current.state = 3;\n    current.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Processors/Dynamic Sitemap/README.md",
    "content": "## Intro\n\nFor organizations having public-facing service portals, there is often a need to have public content indexed, or crawled, by search engines in order for users to quickly and easily find relevant articles. Building a sitemap is one way of improving SEO by essentially announcing a given list of links that search crawlers should visit.  I will walk you through setting this up in ServiceNow by using a custom Processor.  We will be using the [sitemaps.org](https://www.sitemaps.org/protocol.html) protocol, so feel free to visit the link and get familiar with the XML tags to use.\n\n> **Note**: You will need the **_Custom Search Integration_** plugin activated in your instance, which must be done through a HI request, in order to update Robots.txt - this is necessary for this guide.\n\n## Create a Processor\n\n> **Disclaimer**: Per ServiceNow's [documentation](https://docs.servicenow.com/bundle/orlando-application-development/page/script/processors/concept/c_Processors.html), processors are in fact a deprecated feature.  However, they can still be created and used, at least for now.\n\nProcessors allow you to create a customizable URL endpoint that can execute server-side code to produce an output in HTML, JSON, Text, or even XML format.  In this example we will be generating an **XML** sitemap document.\n\n1. Navigate to **System Definition** -> **Processors**, and click **New**\n2. Modify the Form Layout to include the **Roles** Field.\n3. Enter the following field values for the Processor:\n   \n| Field | Value |\n|-------|-------|\n| **Name**  | Dynamic Sitemap |\n| **Type** | script |\n| **CSRF protect** | false (unchecked) |\n| **Roles** | public |\n| **Description** | Generate a Dynamic Sitemap from Knowledge Base Articles for SEO |\n| **Path** | sitemap |\n\n4. Modify the `dynamicSitemapProcessor.js` script to suit your needs, and enter it in the **Script** field. The comments in the script describe what is being done, so be sure to read them! :)\n\n## Create a Public Page\n\nThe next thing to do is to make the new **sitemap** endpoint available to the public so that web crawlers can find it and index your content.  To do this, we need to create a new public page.\n\n1. In the navigator type **sys_public.list** and press enter.\n2. Click new, and enter the following field values:\n\n| Field | Value |\n|-------|-------|\n| **Page**  | sitemap |\n| **Active** | true (checked) |\n\nThis allows us to navigate to https://<instance>.service-now.com/sitemap.do without being logged in.  Go ahead and try it in a private browser window! You should see something like this if everything works as expected:\n\n![ServiceNow Sitemap Results](./snow-sitemap-results.png)\n\n\n## Update Robots.txt\n\nThe last thing that needs to be done is to update Robots.txt - this is the file that tells search engines where they are allowed and disallowed from crawling, and also lets you specify the sitemap URL, which is useful when using a non-default URL (the default is /sitemap.xml, and ServiceNow uses /sitemap.do, for example).\n\n> This requires the Custom Search Integration plugin!\n\n1. Navigate to **Custom Search Integration** -> **Robots.txt Definitions**\n2. In the **Text** field, enter something like:\n\n```\nUser-agent: *\nAllow: /sp\nAllow: /sitemap.do\nDisallow: /\nSitemap: https://dev12345.service-now.com/sitemap.do\n```\n\nThe above **Robots.txt** definition allows crawling of any page under the **/sp** URL, and it allows crawling of the **sitemap**, but it disallows all other crawling from the instance.  Further, we have specified where crawlers can find the **sitemap** by providing the full URL.\n\nIt is recommended to either deactivate or use a data preserver on the Robots.txt file in sub-prod instances so that they do not get indexed by search engines.\n\nWith this, your instance's public content is now ready to be crawled by search engines!"
  },
  {
    "path": "Server-Side Components/Processors/Dynamic Sitemap/dynamicSitemapProcessor.js",
    "content": "(function process(g_request, g_response, g_processor) {\n\n  // Get the instance name for dynamic generation of URLs\n  var instanceName = gs.getProperty(\"instance_name\");\n  \n  // Enter static/manual URLs here, and the sitemap header\n  //   In this example, we're indexing the default service \n  //   portal home and service catalog\n  var xmlString = '<?xml version=\"1.0\" encoding=\"UTF-8\"?>';\n  xmlString = xmlString + '<urlset xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" ' +\n      'xsi:schemaLocation=\"http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd\" ' +\n      'xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">' +\n      '<url>' +\n      '  <loc>https://' + instanceName + '.service-now.com/sp</loc>\\n' +\n      '</url>' +\n      '<url>' +\n      '  <loc>https://' + instanceName + '.service-now.com/sp?id=sc_category</loc>' +\n      '</url>';\n\n  // ------ Dynamic URLs -----------------------------------\n\n  // Knowledge Articles - Public KB Articles\n  // Encoded query gets all Published articles in the IT Knowlege Base,\n  //   and where the Can Read user criteria is Empty (Public).  Tweak the\n  //   query to suit your needs.\n  var grKK = new GlideRecord('kb_knowledge');\n\n  grKK.addEncodedQuery(\"kb_knowledge_base=a7e8a78bff0221009b20ffffffffff17^can_read_user_criteriaISEMPTY^workflow_state=published\");\n  grKK.query();\n\n  while (grKK.next()) {\n      xmlString = xmlString +\n          '<url>' +\n          '  <loc>https://' + instanceName + '.service-now.com/sp?id=kb_article&amp;sys_id=' + grKK.getValue('sys_id') + '</loc>' +\n          '  <lastmod>' + grKK.getValue('sys_updated_on').split(' ')[0] + '</lastmod>' +\n          '</url>';\n  }\n  // End Knowledge Articles\n\n  // -- End Dynamic URLs -----------------------------------\n\n  // Close the XML document root tag\n  xmlString = xmlString + '</urlset>';\n\n  // Convert the XML string into an XMLDocument\n  var xmldoc = new XMLDocument(xmlString);\n\n  // Write the XML document to output in text/xml format\n  g_processor.writeOutput(\"text/xml\", xmldoc);\n\n})(g_request, g_response, g_processor);"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/API Token Expiry Warning/API Token Expiry Warning.js",
    "content": "(function() {\n\n    // Configuration via system properties\n    var warningDays = parseInt(gs.getProperty('api.token.expiry.warning.days', '7'), 10); // Days before expiry to warn\n    var emailRecipients = gs.getProperty('api.token.expiry.email.recipients', 'admin@example.com'); // Comma-separated emails\n\n    // Current time and warning threshold time\n    var now = new GlideDateTime();\n    var warningDate = new GlideDateTime();\n    warningDate.addDays(warningDays);\n\n    // Query oauth_credential records with expires_on between now and warningDate\n    var gr = new GlideRecord('oauth_credential');\n    gr.addQuery('expires_on', '>=', now);\n    gr.addQuery('expires_on', '<=', warningDate);\n    gr.addQuery('active', '=', true); // Only active tokens\n    gr.orderBy('expires_on');\n    gr.query();\n\n    if (!gr.hasNext()) {\n        gs.info('No OAuth credentials nearing expiry within ' + warningDays + ' days.');\n        return;\n    }\n\n    // Build notification email body\n    var emailBody = '<h3>API Token Expiry Warning</h3>';\n    emailBody += '<p>The following OAuth credentials are set to expire within ' + warningDays + ' days:</p>';\n    emailBody += '<table border=\"1\" cellpadding=\"5\" cellspacing=\"0\" style=\"border-collapse:collapse;\">';\n    emailBody += '<tr><th>Name</th><th>User</th><th>Client ID</th><th>Expires On</th></tr>';\n\n    while (gr.next()) {\n        emailBody += '<tr>';\n        emailBody += '<td>' + gr.getDisplayValue('name') + '</td>';\n        emailBody += '<td>' + gr.getDisplayValue('user') + '</td>';\n        emailBody += '<td>' + gr.getValue('client_id') + '</td>';\n        emailBody += '<td>' + gr.getDisplayValue('expires_on') + '</td>';\n        emailBody += '</tr>';\n    }\n    emailBody += '</table>';\n    emailBody += '<p>Please review and renew tokens to avoid integration failures.</p>';\n\n    // Send the email\n    var mail = new GlideEmailOutbound();\n    mail.setFrom('no-reply@yourdomain.com');\n    mail.setSubject('[ServiceNow] OAuth API Token Expiry Warning');\n    mail.setTo(emailRecipients);\n    mail.setBody(emailBody);\n    mail.setContentType('text/html');\n    mail.send();\n\n    gs.info('OAuth token expiry warning email sent to: ' + emailRecipients);\n\n})();\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/API Token Expiry Warning/Readme.md",
    "content": "API Token Expiry Warning Script\n**Overview**\nThis script provides proactive monitoring and alerting for OAuth API tokens stored in the ServiceNow oauth_credential table. It identifies tokens nearing expiry within a configurable countdown window and sends notification emails to administrators, helping prevent sudden integration failures due to expired credentials.\n**Problem Solved**\nAPI tokens used for integrations have expiration timestamps after which they become invalid. Without early warnings, tokens can expire unnoticed, causing integration outages, failed API calls, and increased support incidents. This solution enables administrators to receive timely alerts allowing proactive token renewal and smoother integration continuity.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Approval Reminder/README.md",
    "content": "## Use the code snippets to trigger approval reminder notifications. \n\n### 1 Change Approval Reminder.\nchange_reminder_scheduled_job.js\n\n### 2 Requested item approval reminder to approvers.\nrequested_item_approval_reminder_approver.js\n\n### 3 Requested item approval reminder to requestor.\nrequested_item_approval_reminder_requestor.js\n\n*Note: Notifications and Events to be configured sepearately.*\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Approval Reminder/change_reminder_scheduled_job.js",
    "content": "var dateTime = new GlideDateTime();\n var currentDate = dateTime.getLocalDate();\n var grCR = new GlideRecord('change_request');\n grCR.addEncodedQuery(\"source_table=change_request\"); //Add your own query.\n grCR.orderByDesc('number');\n grCR.query();\n while (grCR.next()) {\n     var grSA = new GlideRecord('sysapproval_approver');\n     grSA.addEncodedQuery(\"state=requested^sysapproval=\" + grCR.sys_id); //Add your own query.\n     grSA.query(); \n     while (grSA.next()) {\n         var dateCreated = new GlideDateTime(grSA.sys_created_on);\n         var dateCreatedLocal = dateCreated.getLocalDate();\n         var daysDifferenceMo = gs.dateDiff(dateCreatedLocal, currentDate, true);\n         if (daysDifferenceMo > 86400) //It will trigger a reminder after one day currently configure as required.\n             gs.eventQueue('event.name', grSA); //Add your own event name to trigger the notification.\n     }\n }\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Approval Reminder/requested_item_approval_reminder_approver.js",
    "content": "var dateTime = new GlideDateTime();\nvar currentDate = dateTime.getLocalDate();\nvar grSA = new GlideRecord('sysapproval_approver');\ngrSA.addEncodedQuery(\"state=requested^source_table=sc_req_item\"); //Add your own query here alongside with the date conditions.\ngrSA.query();\nwhile (grSA.next()) {        \n             gs.eventQueue('event.name', grSA); //Add your own event name to trigger the notification.\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Approval Reminder/requested_item_approval_reminder_requestor.js",
    "content": "var dateTime = new GlideDateTime();\nvar currentDate = dateTime.getLocalDate();\nvar grSA = new GlideAggregate('sysapproval_approver');\ngrSA.addEncodedQuery(\"state=requested^source_table=sc_req_item\"); //Add your own query here alongside with the date conditions.\ngrSA.addAggregate('count','sysapproval');\ngrSA.query();\nwhile (grSA.next()) {  \n\t         var current = grSA.sysapproval.getRefRecord(); \n           gs.eventQueue('event.name',current,current.requested_for); //Add your own event name to trigger the notification.\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto Disable account/Disable Service accounts due to inactivity",
    "content": "var user = new GlideRecord(\"sys_user\");\nuser.addActiveQuery();\nuser.addEncodedQuery(\"last_loginISNOTEMPTY^u_service_account=true\");\nuser.query();\nwhile (user.next()) {\n    var arr = [];\n    arr.push(user.last_login);\n    for (var i = 0; i <= arr.length - 1; i++) {\n        var time = arr[i];\n        var date1 = new GlideDate();\n        date1.setValue(time);\n        var date2 = new GlideDate();\n        var millisecondsDifference = date2.getNumericValue() - date1.getNumericValue();\n        var daysDifference = millisecondsDifference / (1000 * 60 * 60 * 24);\n        var days = Math.floor(daysDifference);\n        if (days > 30) {\n            user.active = false;\n            user.locked_out = true;\n            user.update();\n        }\n \n    }\n \n \n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto Disable account/Readme.md",
    "content": "This schedule job script will help to auto disable the service accounts which has not been logged in or used for last 30 days. This can be helpful for those who are looking to disable the accounts based on some certain time period.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto close changes requests updated 30 days prior/README.md",
    "content": "This script can be used to auto close records if they have not been updated from past 30 days. For eg. I have taken change requests. This script can be written in a schedule job\nto run it daily at 23:59:59 time to check if there is any such records present act accordingly.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto close changes requests updated 30 days prior/script.js",
    "content": "// Sript to auto close change requests if thre is no update from past 30 days.\n\nvar chg = new GlideRecord('change_request');\nchg.addActiveQuery(); // to fetch active change requests\nchg.addEncodedQuery('sys_updated_on<=javascript:gs.beginningOfLast30Days()'); // to get change requests upadated 30 days before\nchg.query();\nwhile(chg.next())\n\t{\n\t\tchg.comments = 'Auto closing changing requests as there is no update from past 30 days';\n\t\tchg.state = 3;\n\t\tchg.setWorkflow(false); // to prevent from any BR to run.\n\t\tchg.autoSysFields(false); // to prevent system fields to get upadated. (optional)\n\t\tchg.update();\n\t}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto upgrade store applications/Readme.md",
    "content": "This script will automatically upgrade the Store applications of your choice, based on a system property.\n\nA few key points about this approach:\n• The script upgrades only the applications listed in the system property (auto_upgrade_store_apps) and applications that are included as child.\n• It can be scheduled to run automatically or triggered manually.\n• You can add an email notification, a banner, or another form of alert to notify admins about which applications were updated.\n\nThis is a simple way to save time and keep Store applications up to date without manual intervention.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto upgrade store applications/script.js",
    "content": "upgradeSelectedStoreApps();\n\nfunction upgradeSelectedStoreApps() {\n    var propertyName = \"auto_upgrade_store_apps\";\n    var storeAppsList = gs.getProperty(propertyName, \"\");\n    if (!storeAppsList) {\n        gs.info(\"No store applications listed for auto-upgrade.\");\n        return;\n    }\n    var appsToUpgrade = storeAppsList.split(\",\").map(function(app) {\n        return app.trim();\n    });\n    var upgradedApps = [];\n    var storeAppGr = new GlideRecord('sys_store_app');\n    storeAppGr.addQuery('active', true); // Only active store applications\n    storeAppGr.addQuery('sys_id', 'IN', appsToUpgrade); // Filter by system property list\n    storeAppGr.query();\n    while (storeAppGr.next()) {\n        var appId = storeAppGr.getValue('sys_id');\n        var appName = storeAppGr.getValue('name');\n        var currentVersion = storeAppGr.getValue('version');\n        var availableVersion = storeAppGr.getValue('latest_version');\n        if (availableVersion && currentVersion !== availableVersion) {\n            try {\n                gs.info('Upgrading store application: ' + appName + ' from version ' + currentVersion + ' to ' + availableVersion);\n                var worker = new sn_appclient.AppUpgrader();\n                storeUpgradeResult = worker.upgrade(appId.toString(), availableVersion.toString(), false);\n                if (storeUpgradeResult) {\n                    gs.info('Store application \"' + appName + '\" upgraded successfully.');\n                    upgradedApps.push({\n                        name: appName,\n                        fromVersion: currentVersion,\n                        toVersion: availableVersion\n                    });\n                } else {\n                    gs.error('Failed to upgrade store application: ' + appName);\n                }\n            } catch (e) {\n                gs.error('Error upgrading store application \"' + appName + '\": ' + e.message);\n            }\n        } else {\n            gs.info('Store application \"' + appName + '\" is already up-to-date.');\n        }\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto upgrade store applications/sysproperty.js",
    "content": "/*\ncreate below system property sys_properties table and set sys id for store applications - eg 31774a2953839110a6f8ddeeff7b12cb\n*/\n\nauto_upgrade_store_apps\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto-Assign Unassigned Incidents Older Than 30 Minutes/Readme.md",
    "content": "This scheduled job automatically assigns unassigned incidents in ServiceNow to a random active user from the incident’s assignment group, but only if the incident is at least 30 minutes old.\nIt ensures timely triaging of new incidents and avoids backlog accumulation caused by unassigned tickets.\n\nHow It Works\n\nIdentify Eligible Incidents\n  Fetch all incidents from the incident table whereassigned_to is empty (unassigned) and assignment_group is not empty\n  \nFind Active Group Members\n  For each incident, look up the related group (sys_user_grmember table).Join with the sys_user table. This allows filtering users based on their active status.\n\nRandom Assignment\n  From the list of active members, pick a random user. Assign that user to the incident’s Assigned To field\n\nUpdate & Log\n  Update the incident record in the database. Log success or skip messages to the system log\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Auto-Assign Unassigned Incidents Older Than 30 Minutes/auto_assign_unassigned_incidents_via_scheduled_job.js",
    "content": "(function executeAutoAssignment() {\n    // --- CONFIGURABLE SETTINGS ---\n    var MINUTES_DELAY = 30;  // Only assign incidents older than this many minutes\n\n    // --- COMPUTE TIME CUTOFF ---\n    var cutoffTime = new GlideDateTime();\n    cutoffTime.addMinutes(-MINUTES_DELAY); // incidents created before this time are eligible\n\n    // --- QUERY: Find eligible unassigned incidents ---\n    var incGr = new GlideRecord('incident');\n    incGr.addNullQuery('assigned_to');                // incident not yet assigned\n    incGr.addNotNullQuery('assignment_group');        // has an assignment group\n    incGr.addQuery('sys_created_on', '<=', cutoffTime); // created at least 30 minutes ago\n    incGr.query();\n\n    var totalAssigned = 0;\n\n    // --- LOOP THROUGH EACH ELIGIBLE INCIDENT ---\n    while (incGr.next()) {\n        var groupSysId = incGr.assignment_group.toString();\n        if (!groupSysId) {\n            gs.info('[AutoAssign] Skipped ' + incGr.number + ': No assignment group defined.');\n            continue;\n        }\n\n        // --- FETCH ACTIVE USERS IN THAT ASSIGNMENT GROUP ---\n        var members = getActiveGroupMembers(groupSysId);\n\n        if (members.length === 0) {\n            gs.info('[AutoAssign] Skipped ' + incGr.number + ': No active users found in group ' + incGr.assignment_group.name);\n            continue;\n        }\n\n        // --- PICK A RANDOM USER ---\n        var assignedUser = getRandomElement(members);\n\n        // --- ASSIGN AND UPDATE ---\n        incGr.assigned_to = assignedUser;\n        incGr.update();\n\n        gs.info('[AutoAssign] Incident ' + incGr.number + ' assigned to user: ' + assignedUser);\n        totalAssigned++;\n    }\n\n    gs.info('[AutoAssign] Total incidents auto-assigned: ' + totalAssigned);\n\n    // -------------------------------------------------------------------\n    // --- HELPER FUNCTIONS ---\n    // -------------------------------------------------------------------\n\n    /**\n     * Fetches all active users in a given assignment group.\n     * @param {String} groupSysId - sys_id of assignment group\n     * @returns {Array} - Array of user sys_ids\n     */\n    function getActiveGroupMembers(groupSysId) {\n        var users = [];\n        var grMember = new GlideRecord('sys_user_grmember');\n        grMember.addQuery('group', groupSysId);\n        grMember.addJoinQuery('sys_user', 'user', 'sys_id');\n        grMember.addQuery('user.active', true);\n        grMember.query();\n\n        while (grMember.next()) {\n            users.push(grMember.user.toString());\n        }\n        return users;\n    }\n\n    /**\n     * Returns a random element from an array.\n     * @param {Array} arr\n     * @returns {*} Random element\n     */\n    function getRandomElement(arr) {\n        if (!arr || arr.length === 0) return null;\n        var index = Math.floor(Math.random() * arr.length);\n        return arr[index];\n    }\n\n})();\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Bucket Group Reporting/Bucket Group Age Calculation.js",
    "content": "var rec = new GlideRecord('case'); // any table which you want to use\n    rec.addEncodedQuery('stateNOT IN60,40, 20'); // filtering out all the closed/cancelled cases 60 is closed, 40 is cancelled, 20 is rejected\n        rec.query();\n    while (rec.next()) {\n    var openedDate = new GlideDateTime(rec.opened_at.getDisplayValue());\n    var dur = GlideDateTime.subtract(openedDate,actualDateTime );\n    var elapsedTime =  dur.getNumericValue()/86400000 ;\n    // Check to see when the item was created\n    var aging;\n    if (elapsedTime <= 2) aging = '0-2 Days';\n    if (elapsedTime > 2) aging = '3-4 Days';\n    if (elapsedTime > 4) aging = '5-7 Days';\n    if (elapsedTime > 7) aging = '8-15 Days';\n    if (elapsedTime > 15) aging = '16-30 Days';\n    if (elapsedTime > 30) aging = '31-60 Days';\n    if (elapsedTime > 60) aging = '61-90 Days';\n    if (elapsedTime > 90) aging = 'Over 90 Days';\n \n    rec.setWorkflow(false); // Skip any Business Rules\n    rec.autoSysFields(false); // Do not update system fields\n    rec.aging_category = aging; // updating aging category with defined buckets allocated above\n    rec.update();\n    }\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Bucket Group Reporting/readme.md",
    "content": "Find out all the cases with the difference in their create date and current date and put them in different buckets of their age. Then you can report on the aging. With this you will be able to run the Bucket Group reporting on tables without using PA.\n\nIn ServiceNow Performance Analytics (PA), a Bucket Group is used to categorize or group data into defined ranges or segments, which helps make reports and indicators more meaningful and easier to analyze. This code will help you to categorize the tickets based on any defined ranges or segments you want. Based on this defined ranges or segments you can get any king of reporting without using PAs in ServiceNow.\n\nThis code lets you create custom ranges (buckets) to classify numerical data (like durations, scores, or counts) into meaningful labels.For example, instead of showing raw numbers like 2, 7, 14, 30 days, you can define buckets such as:\n\n0–5 days → “Very Fast”\n\n6–10 days → “Fast”\n\n11–20 days → “Average”\n\n>20 days → “Slow”\n\nThen, these labels can be displayed in your dashboards, or reports, making the data easier to interpret.\n\nReal time user cases:\n\n1. Score or metric segmentation\nDivide customer satisfaction scores into Low, Medium, High ranges.\n\n2. Priority or risk scoring\nConvert numeric risk scores into descriptive ranges (Low Risk, Medium Risk, High Risk).\n\nAlso, SLA uses the tickets to be defined in SLA number or breach date. It doesnt let you define any segment or categorize the data. It helps you define the reporting basis on your category or segment\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Calculate Ticket's Aging/Calculate Ticket's Aging.js",
    "content": "var elapsedTime = 0;\n    var aging = '';\n    var actualDateTime = new GlideDateTime();\n   \n    var rec = new GlideRecord('cases');\n    rec.addEncodedQuery('stateNOT IN6,3,7');\n    rec.query();\n    while (rec.next()) {\n    var openedDate = new GlideDateTime(rec.opened_at.getDisplayValue());\n    var dur = GlideDateTime.subtract(openedDate,actualDateTime );\n    elapsedTime =  dur.getNumericValue()/86400000 ;\n    if (elapsedTime <= 2) aging = '0-2 Days';\n    if (elapsedTime > 2) aging = '3-4 Days';\n    if (elapsedTime > 4) aging = '5-7 Days';\n    if (elapsedTime > 7) aging = '8-15 Days';\n    if (elapsedTime > 15) aging = '16-30 Days';\n    if (elapsedTime > 30) aging = '31-60 Days';\n    if (elapsedTime > 60) aging = '61-90 Days';\n    if (elapsedTime > 90) aging = 'Over 90 Days';\n \n    rec.setWorkflow(false); // Skip any Business Rules\n    rec.autoSysFields(false); // Do not update system fields\n    rec.aging_category = aging;\n    rec.update();\n    }\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Calculate Ticket's Aging/README.md",
    "content": "This script helps is calculating the aging of the ticket/cases and define them in bucket of category aging like '0-2 Days','3-4 Days'\nBased on this you can get the reporting on cases aging. How old is the case. It calculates the aging from the creation date.\n\nIt works on all cases except the cases which are on resolved,cancelled and closed state.\nWith this script you can decide whether to show that case in red/orange or yellow colour so that agent will know just by seeing the case\nthat aging has increased. So if aging is greater than 30 we can make the case highlighted as red by using field styles conditions.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/CancelApproval/readme.md",
    "content": "Scheduled Script Execution: Auto-cancel RITM if group manager approval pending after 30 days\n\nThis script:\n- Finds RITM records older than 30 days\n- Checks if any group manager approvals are still pending (state='requested')\n- If so, cancels the RITM (sets state to 'Cancelled')\nUsage:\n- Schedule this script to run daily.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/CancelApproval/script.js",
    "content": "/*\nScheduled Script Execution: Auto-cancel RITM if group manager approval pending after 30 days\n\nThis script:\n- Finds RITM records older than 30 days\n- Checks if any group manager approvals are still pending (state='requested')\n- If so, cancels the RITM (sets state to 'Cancelled')\nUsage:\n- Schedule this script to run daily.\n*/\n\n// Calculate date 30 days ago\nvar thirtyDaysAgo = new GlideDateTime();\nthirtyDaysAgo.addDaysUTC(-30);\n\n// Query RITMs older than 30 days and not closed/cancelled already\nvar ritmGR = new GlideRecord('sc_req_item');\nritmGR.addQuery('sys_created_on', '<=', thirtyDaysAgo);\nritmGR.addEncodedQuery('stateIN1,2,112^cat_item=a24b1e113bc21e1050109c9c24e45a51');\nritmGR.query();\n\nwhile (ritmGR.next()) {\n    // Query approvals for this RITM from group managers - adjust condition accordingly\n    var approvalGR = new GlideRecord('sysapproval_approver');\n    approvalGR.addQuery('sysapproval', ritmGR.sys_id); // approvals linked to this RITM\n    approvalGR.addQuery('state', 'requested'); // pending approvals\n    approvalGR.query();\n\n    if (approvalGR.hasNext()) {\n        // Group manager approvals pending after 30 days => Cancel RITM\n\t\t\n        ritmGR.state = 8; // Closed Cancelled\n\t\tritmGR.assignment_group = '<assignment_group_name or sysid>'; //group ABC\n        ritmGR.work_notes = 'Auto-cancelled due to no group manager approval within 30 days.';\n        ritmGR.update();\n\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Clean up Inactive User access/readme.md",
    "content": "🧹 Inactive User Cleanup — ServiceNow Scheduled Job\n📌 Overview\n\nThis script automates daily cleanup of access and assignments for inactive users in ServiceNow.\nIt removes orphaned access, ensures task accountability, and sends an email summary upon completion.\n\n✅ Features Included\n🔹 Inactive Group Membership Cleanup\n\nSearches User Group Member table (sys_user_grmember)\nIdentifies inactive users (excluding Web Service Access Only accounts)\nRemoves them from all associated groups\nLogs each removal in system logs\nAdds removal details to the summary email\n\n🔹 Direct Role Revocation\n\nSearches User Has Role table (sys_user_has_role)\nRemoves roles not inherited via group membership\nPrevents unauthorized access after deactivation\nLogs each role removed\nIncluded in daily summary email\n\n🔹 Task Ownership Cleanup\n\nSearches Task table (task)\nFinds active tasks assigned to inactive users\nClears the Assigned To field without triggering workflow\nAdds work notes for audit traceability\nLogs entries + email reporting\nAll actions skip users where: web_service_access_only = true\n\n🛠 Script Placement & Configuration\nField\tValue\nScript Type\t✅ Scheduled Script Execution\nLocation\tRun this script section\n\nBefore using in your instance, update the following in script:\n\nLine\tUpdate Required\nLine 56\tReplace sender email in email.setFrom('xyz@service-now.com');\nLine 44\tReplace system property name in gs.getProperty('glide.xyz.admin.email.recipients');\n🔍 System Property Required\n\nCreate or update the System Property to store email recipients:\n\nName (example)\tValue (example)\nglide.xyz.admin.email.recipients\tadmin@example.com,user@example.com\n\nSupports single or comma-separated recipients ✅\n\n✉️ Email Summary Includes\n\nUsers removed from groups\nDirect roles removed\nActive tasks unassigned\nTimestamped logs for auditing\n\n📝 Work Notes Added\n\nFor tasks reassigned:\nSystem Administrator removed \"Assigned to\" value as the user is no longer active.\n\n⚠️ Best Practices\n\nRun in sub-prod first\nEnsure proper backups/audit compliance\nSchedule at low-traffic hours\nMonitor logs initially for data impact\n\n🧩 Extendability Ideas\n\nYou can easily modify:\nEmail template (HTML formatting)\nQuery filters for additional cleanup criteria\nLogging to include sys_id values\nScheduling frequency (default recommended: Daily)\n\n🧑‍💻 Maintainers\n\nFeel free to update script name, System Property naming, and sender email for your organization.\nPull requests & suggestions welcome! 🙌\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Clean up Inactive User access/script.js",
    "content": "///This is a Scheduled Job Script to be added in \"Run this script\" \n//replace the email in line 56- 'email.setFrom('xyz@service-now.com');'\n//replace the property name in line 44 'var recipientList = gs.getProperty('glide.xyz.admin.email.recipients');'\n\nvar emailBody = 'Daily Inactive User Group/Role/Task Clean-up Completed: \\n<br><br>';\n\n//REMOVE INACTIVE USERS FROM GROUPS\nvar queryString = \"user.active=false^user.web_service_access_only=false^user.sourceSTARTSWITHldap:\"; //query to find inactive members belonging to groups and ignores any users with \"web service access only\" = TRUE.\nvar recGrp = new GlideRecord('sys_user_grmember'); //searches the User Group Member table\nrecGrp.addEncodedQuery(queryString);\nrecGrp.query();\nwhile (recGrp.next()) {\n    emailBody += 'Inactive User, ' + recGrp.user.getDisplayValue() + ', member of Group: ' + recGrp.group.getDisplayValue() + ' was removed from Group.\\n<br>';\n    gs.log('Inactive User, ' + recGrp.user.getDisplayValue() + ', member of Group: ' + recGrp.group.getDisplayValue() + ' was removed from Group.');\n    recGrp.deleteRecord(); //deletes group membership record from inactive user\n}\n\n//REMOVE ROLES FROM INACTIVE USERS THAT WERE NOT ADDED BY GROUP MEMBERSHIP\nvar recRole = new GlideRecord('sys_user_has_role'); // search view User Has Role table\nvar queryString2 = \"user.active=false^user.web_service_access_only=false^user.sourceSTARTSWITHldap:^inherited=false\";\nrecRole.addEncodedQuery(queryString2); // find inactive users that have a role assigned and ignores any users with \"web service access only\" = TRUE.\nrecRole.query();\nwhile (recRole.next()) {\n    emailBody += 'Inactive User, ' + recRole.user.name + ' found - Role: ' + recRole.role.getDisplayValue() + ', was removed.\\n<br>';\n    gs.log('Inactive User, ' + recRole.user.name + ' found - Role: ' + recRole.role.getDisplayValue() + ', was removed.'); // add info message to system log about what user was inactive and role was removed\n    recRole.deleteRecord(); //deletes role record from inactive user\n}\n\n//CLEARS ASSIGNED TO ON ACTIVE TASKS ASSIGNED TO INACTIVE USERS\nvar recTask = new GlideRecord('task'); // search task table\nvar queryString3 = \"assigned_to.active=false^assigned_to.web_service_access_only=false^active=true\";\nrecTask.addEncodedQuery(queryString3); // find inactive users that have active tasks assigned to them and ignores any users with \"web service access only\" = TRUE.\nrecTask.query();\nwhile (recTask.next()) {\n    emailBody += 'Removed task from Inactive User: ' + recTask.assigned_to.getDisplayValue() + ' ' + recTask.number + '.\\n<br>';\n    gs.log('Removed task from Inactive User: ' + recTask.assigned_to.getDisplayValue() + ' ' + recTask.number); // add message about what user was inactive tasks removed\n    recTask.work_notes = 'System Administrator removed \"Assigned to\" value as the user is no longer active.'; //add work note explanation without workflow\n    recTask.update();\n    recTask.assigned_to = '';\n    recTask.setWorkflow(false); //removes assigned_to value without workflow\n    recTask.update();\n}\n\nvar recipientList = gs.getProperty('glide.xyz.admin.email.recipients');\nvar email = new GlideEmailOutbound();\nemail.setSubject('Daily Inactive User Group/Role/Task Clean-up Completed');\nemail.setBody(emailBody);\nif (recipientList.includes(',')) {\n    var recipients = recipientList.split(\",\");\n    for (var i = 0; i < recipients.length; i++) {\n        email.addRecipient(recipients[i]); // Add recipients from system property\n    }\n} else {\n    email.addRecipient(recipientList); // Add single recipient from system property\n}\nemail.setFrom('xyz@service-now.com');\nemail.save();\n\ngs.log('Daily Inactive User Group/Role/Task Clean-up Completed: \\n\\n' + emailBody);\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Condition script to trigger the scheduled job on Quarterly basis/Condition script to trigger the scheduled job on Quarterly basis.js",
    "content": "var d = new Date();// getting today's date\nvar month = d.getMonth();// getting the month\nvar a = month.toString();\nif(a== '2'|| a=='5' || a=='8' || a=='11'){//condition will be true only when month is March, June, September, December\nanswer = true;\n}\nelse{\nanswer = false;\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Condition script to trigger the scheduled job on Quarterly basis/README.md",
    "content": "The script in code-snippets/Scheduled Jobs/Condition script to trigger the scheduled job on Quarterly basis/Condition script to trigger the scheduled job on Quarterly basis.js\ncan be used in the condition script of scheduled job so that the scheudled job will trigger only quarterly.\n\nThe script will make the answer true only on March, June, September, December months. All other months the script will make answer false.\n\nUse Case:\nThere will be requirement to send audit tasks and approvals on every Quarter Day 1 (March 1, June1, September1, December1).\nIn this case the scheeduled job can be scheduled for every month day 1. Then in the condition script, the script in \"Condition script to trigger the scheduled job on Quarterly basis.js\" can be used.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Create Scheduled Imports Graphviz file/README.md",
    "content": "# Graphviz graph of Scheduled Import parent/child relations\n\nCreate a Graphviz DOT graph of Scheduled Import with parent/child relations\n\n**Not an actual Scheduled Job but rather to be run as a Background Script or in Xplore.**\n\nAdd \"grSIS.addEncodedQuery(...)\" lines as required to filter on specific Scheduled Imports.\n\nOutput is a Graphviz DOT file of all (or filtered) Scheduled Imports parent/child relationships.\n\nThe output file can for example be viewed on the following pages:\n- https://edotor.net/\n- https://dreampuf.github.io/GraphvizOnline/\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Create Scheduled Imports Graphviz file/script.js",
    "content": "// Create Scheduled Imports Graphviz file\n\nfunction get_dotItem(id, name, style) {\n    var return_string = '';\n    return_string += '\"' + id + '\"';\n    return_string += ' [label=\"' + name + '\"';\n    if (style) {\n        return_string += ' style=\"' + style + '\"';\n    }\n    return_string += ']';\n    return return_string;\n}\n\nfunction print_dotFile(itemsandrelations) {\n    var standard_options =\n        ' \\\n        graph [ \\\n        # rankdir = \"LR\" \\\n        ]; \\\n        node [ \\\n         fontsize = \"10\" \\\n         shape = \"box\" \\\n         fixedsize = false \\\n         width=1.8 \\\n        ];';\n\n    return 'digraph g {' + standard_options + '\\n' + itemsandrelations + '}';\n}\n\nfunction get_dotRelation(parent_id, child_id) {\n    return '\"' + child_id + '\"->\"' + parent_id + '\"';\n}\n\nvar grSIS = new GlideRecord('scheduled_import_set');\n//grSIS.addEncodedQuery(\"active=true\");\n//grSIS.addEncodedQuery(\"nameLIKEcmdb\");\ngrSIS.orderBy('run_time');\ngrSIS.setLimit(100);\ngrSIS.query();\nvar dotfile_content = '';\nwhile (grSIS.next()) {\n    if (grSIS.getValue('active') == true) {\n        dotfile_content += get_dotItem(grSIS.getValue('sys_id'), grSIS.getValue('name')) + \"\\n\";\n    } else {\n        dotfile_content += get_dotItem(grSIS.getValue('sys_id'), grSIS.getValue('name'), \"dotted\") + \"\\n\";\n    }\n    if (grSIS.getValue('parent')) {\n        dotfile_content += get_dotRelation(grSIS.getValue('sys_id'), grSIS.getValue('parent')) + \"\\n\";\n    }\n}\n\ngs.info('Graphviz File:\\n' + print_dotFile(dotfile_content));\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Create send reminders weekly/README.md",
    "content": "Consider the scenario of sending remainder approvals weekly. \n\nCreate an event that is triggered from the scheduled job.\n\nHere is the code on the scheduled job that runs daily but sends notification after every week(7 days) based on the due date.\n\nSame code can be applied to any other table, where you to trigger an event or edit a record on weekly basis based on a certain Date field.\n\nCreate a  notification that is triggered based on the event.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Create send reminders weekly/code.js",
    "content": "var app = new GlideRecord(\"sysapproval_approver\");\napp.addEncodedQuery('sysapproval.numberSTARTSWITHINC^state=requested'); // Please rename the \"INC\" with based on the number maintenance of the table that you are looking at.\napp.query();\nwhile(app.next())\n {\nvar createdte = new GlideDateTime(current.created_on);\nvar now = new GlideDateTime();\nvar dur = new GlideDuration();\ndur = GlideDateTime.subtract(createdte,now);\nvar days = dur.getDayPart();\nif(days%7==0) //check if it's been a week since the created date for each record.\n{\n gs.eventQueue('<eventname>',approval,app.approver,app.sysapproval);\n//event needs to created in the event Registry first and then the event name to be provided as the first parameter in the above eventQueue function.\n// Now you configure a Notificatin that triggers based on this event and that does the Job!!!!\n}\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Daily Summary Email/README.md",
    "content": "Use Case: Daily Summary Notification Email\nThis scheduled job sends a daily email to a specific IT group with a quick summary of important IT service metrics, including:\nNumber of open incidents\nPending approvals\nSLAs breached today\nHigh priority incidents (P1/P2)\nIncidents unassigned for more than 24 hours\n\nWho receives it?\nActive members of a designated ServiceNow group (like Incident Management or IT Operations).\n\nWhy?\nTo give IT teams and managers daily visibility into workload, critical issues, and bottlenecks so they can act quickly and keep service running smoothly.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Daily Summary Email/script.js",
    "content": "(function() {\n    var groupName = 'Incident Management';\n    var emailFrom = 'no-reply@yourcompany.com';\n    var emailSubject = 'ServiceNow Daily Summary';\n\n    var todayStart = new GlideDateTime();\n    todayStart.setDisplayValue(gs.beginningOfToday());\n\n    var dayAgo = new GlideDateTime();\n    dayAgo.addDaysUTC(-1);\n\n    // Open Incidents (not closed)\n    var grOpenInc = new GlideAggregate('incident');\n    grOpenInc.addAggregate('COUNT');\n    grOpenInc.addQuery('state', '!=', '7');\n    grOpenInc.query();\n    grOpenInc.next();\n    var openIncidents = grOpenInc.getAggregate('COUNT');\n\n    // Pending Approvals\n    var grApprovals = new GlideAggregate('sysapproval_approver');\n    grApprovals.addAggregate('COUNT');\n    grApprovals.addQuery('state', 'requested');\n    grApprovals.query();\n    grApprovals.next();\n    var pendingApprovals = grApprovals.getAggregate('COUNT');\n\n    // SLAs Breached Today\n    var grSLA = new GlideAggregate('task_sla');\n    grSLA.addAggregate('COUNT');\n    grSLA.addQuery('planned_end_time', '>=', todayStart); //Breach time is the field Label\n    grSLA.addQuery('stage', 'breached');\n    grSLA.query();\n    grSLA.next();\n    var breachedSLAs = grSLA.getAggregate('COUNT');\n\n    // High Priority Incidents (P1 & P2 open)\n    var grHighPri = new GlideAggregate('incident');\n    grHighPri.addAggregate('COUNT');\n    grHighPri.addQuery('priority', 'IN', '1,2');\n    grHighPri.addQuery('state', '!=', '7');\n    grHighPri.query();\n    grHighPri.next();\n    var highPriorityOpen = grHighPri.getAggregate('COUNT');\n\n    // Incidents unassigned > 24 hours\n    var grUnassigned = new GlideAggregate('incident');\n    grUnassigned.addAggregate('COUNT');\n    grUnassigned.addQuery('assigned_to', 'ISEMPTY');\n    grUnassigned.addQuery('opened_at', '<=', dayAgo);\n    grUnassigned.addQuery('state', '!=', '7');\n    grUnassigned.query();\n    grUnassigned.next();\n    var unassignedOld = grUnassigned.getAggregate('COUNT');\n\n    var emailBody = '';\n    emailBody += ' *ServiceNow Daily Summary (' + gs.nowDate() + ')*\\n\\n';\n    emailBody += '• Open Incidents: ' + openIncidents + '\\n';\n    emailBody += '• Pending Approvals: ' + pendingApprovals + '\\n';\n    emailBody += '• SLAs Breached Today: ' + breachedSLAs + '\\n';\n    emailBody += '• High Priority Incidents (P1/P2): ' + highPriorityOpen + '\\n';\n    emailBody += '• Unassigned Incidents > 24h: ' + unassignedOld + '\\n';\n    emailBody += '\\n';\n    var recipients = [];\n\n    var group = new GlideRecord('sys_user_group');\n    if (group.get('name', groupName)) {\n        var m2m = new GlideRecord('sys_user_grmember');\n        m2m.addQuery('group', group.sys_id);\n        m2m.query();\n        while (m2m.next()) {\n            var user = m2m.user.getRefRecord();\n            if (user.active && user.email) {\n                recipients.push(user.email.toString());\n            }\n        }\n    } else {\n        gs.error('Group \"' + groupName + '\" not found. No emails sent.');\n        return;\n    }\n\n    if (recipients.length === 0) {\n        gs.info('No active users with email found in group \"' + groupName + '\". No emails sent.');\n        return;\n    }\n\n    for (var i = 0; i < recipients.length; i++) {\n        var email = new GlideRecord('sys_email');\n        email.initialize();\n        email.type = 'send-ready';\n        email.recipients = recipients[i];\n        email.from = emailFrom;\n        email.subject = emailSubject;\n        email.body = emailBody;\n        email.insert();\n    }\n\n})();\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Daily detection of customer updates made in 'Default' update set/README.md",
    "content": "**Scheduled Script Execution**\n\nThis script allows detecting any customer updates made in 'Default' update set (on different one based on configuration) in this day. You can change the action after detection from logging, to sending e-mail notification or creating event based on your needs.\n\n\n**Example configuration of Scheduled Script Execution**\n\n ![Coniguration](ScreenShot_1.PNG)\n\n**Example execution log**\n\n ![Log](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Daily detection of customer updates made in 'Default' update set/script.js",
    "content": "//Script to detect any changes made in 'Default' (or different based on your configuration) update set\n\n//Sys_id value of the selected update set (In this case 'Default')\nvar DEFAULT_UPDATE_SET_ID = '3f8ee93a45553010c0a05206e0e0f800';\n\n//Query to get list of all updates done in specified update set this day\nvar grCustomerUpdate = new GlideRecord('sys_update_xml');\ngrCustomerUpdate.addQuery('update_set', DEFAULT_UPDATE_SET_ID);\ngrCustomerUpdate.addEncodedQuery('sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()');\ngrCustomerUpdate.query();\n\n//Go through all customer updates in the query \nwhile (grCustomerUpdate.next()) {\n\n    //Notify about found customer updates\n    //You can inform about detection in different ways, create event, send e-mail (based on your needs)\n    gs.warn('[Scheduled Script Execution] - detected changes made in Default update set in: ' + grCustomerUpdate.name);\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactivate INC in 90 days/90daysInactiveScript.js",
    "content": "//Formatted the code for background script       \nvar grIncident = new GlideRecord('incident');\ngrIncident.addEncodedQuery(\"sys_created_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()^active=true\");\ngrIncident.orderByDesc('number');\n//grIncident.setLimit(20);\ngrIncident.query();\nwhile (grIncident.next()) {\n\n    var id=grIncident.getValue('sys_id');\n    var tableName=grIncident.getValue('sys_class_name');\n\n    // var id=\"e329de99731423002728660c4cf6a73c\";\n//var tableName=\"incident\";  \n\n              var gdt = new GlideDateTime(gs.nowDateTime());\n             gdt.addDays(90);\n          \n              //gs.addErrorMessage(gdt);\n\n              var grST = new GlideRecord(\"sys_trigger\");\n\n              grST.initialize();\n\n              grST.name = \"Inactivate \"+tableName+\" record\";\n\n              grST.next_action.setValue(gdt);\n\n              grST.job_id.setDisplayValue('RunScriptJob');\n\n              grST.script = doTimelySchedule(id);\n\n              grST.document = 'syslog';\n\n              grST.state = 0;\n\n              grST.trigger_type = 0;\n\n              grST.insert();\n           }\n\n              function doTimelySchedule(id) {\n\n                                           var ret = \"\"\n\n                             + \"var gr = new GlideRecord('\"+tableName+\"');\\n\"\n\n                             + \"gr.addQuery('sys_id', '\" +id+ \"');\\n\"\n\n                             + \"gr.query();\\n\"\n\n                             + \"if (gr.next()) {\\n\"\n\n                                           + \"gr.active = false;\\n\"\n\n                                           + \"gr.update();\\n\"\n\n                                           + \"}\";\n\n                            return ret;      \n                           gs.print( \"return value : \" + ret);//prints the inserted code in sysy trigger table\n                           //open the schedule item with the name \n                                                            \n\n\n                             }\n                            \n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactivate INC in 90 days/README.md",
    "content": "This code snippet will help to inactivate the table records after 90 days of creation through schedule insert on sys trigger table  .\nCan be used in BR/Script Inculde/Background script.\n### Formatted for background script, please check the result in sys_ trigger Table or else click on document id it will redirect to  inserted JOb \n\n\n### Sample Output :\n==============\n\nOperation\tTable\tRow Count\ninsert\tsys_trigger\t1  \n\n*** Script: Below runscript scheduled on sys trigger at 2023-01-23 13:29:48\n\nvar gr = new GlideAggregate('incident');\ngr.addQuery('sys_id', '91cce5c52fb6111015d2e33df699b6f9');\ngr.query();\nif (gr.next()) {\ngr.active = false;\ngr.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactivate Inactive Users and Notify Managers/Readme.md",
    "content": "Scheduled Job: Deactivate Inactive Users and Notify Managers\n\nOverview\nThis scheduled job automatically deactivates inactive users based on their last login time and creation date, and sends an email notification to their active manager using the ServiceNow event framework (gs.eventQueue()).\n\nThe entire process is divided into three components:\n1.Scheduled Job Script — Finds inactive users and fires an event.\n2.Event Registration — Registers user.deactivation.notify_manager in the Event Registry.\n3.Script Action — Sends an email to the manager dynamically.\n\n1. Scheduled Job Script\n\nPurpose\nThis script runs on a schedule (e.g., daily or weekly) and:\nFinds users who haven’t logged in for a specific number of days.\nChecks their account creation date.\nDeactivates those users.\nFires an event to notify their manager if the manager is active.\n\nLogic Summary\nCalculates a cutoff date (e.g., 90 days of inactivity).\nQueries sys_user for users:\nWhose last_login_time is older than the cutoff date OR is empty.\nWhose sys_created_on is older than the cutoff date.\nWho are currently active.\nFor each matching user:\n    Finds their manager record.\n    Checks if the manager is active.\n    Deactivates the user.\n\nSends an event with:\n    parm1: User name\n    parm2: Manager’s email\n\n2. Event Registration:\n\nName: user.deactivation.notify_manager\nTable: sys_user\nDescription: “Triggered when a user is deactivated due to inactivity.”\n\n3. Script Action Setup\n\nName: Notify Manager on User Deactivation\nEvent name: user.deactivation.notify_manager\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactivate Inactive Users and Notify Managers/auto_deactivate_inactive_users.js",
    "content": "(function() {\n    var inactiveDays = 90;\n    var cutoffDate = new GlideDateTime();\n    cutoffDate.addDaysUTC(-inactiveDays);\n\n    var userGR = new GlideRecord('sys_user');\n    userGR.addActiveQuery(); // Only active users\n    userGR.addQuery('sys_created_on', '<', cutoffDate); // Old accounts\n\n    // Using encoded query: users with last login before cutoff OR never logged in\n    userGR.addEncodedQuery('last_login_time<' + cutoffDate.getValue() + '^ORlast_login_timeISEMPTY');\n    userGR.query();\n\n    while (userGR.next()) {\n        var wasActive = userGR.active;\n        var managerSysId = userGR.manager;\n\n        // Deactivate the user\n        userGR.active = false;\n        userGR.update();\n\n        // Notify only if manager exists and is active\n        if (wasActive && managerSysId) {\n            var mgrGR = new GlideRecord('sys_user');\n            mgrGR.addQuery('sys_id', managerSysId);\n            mgrGR.addQuery('active', true);\n            mgrGR.query();\n\n            if (mgrGR.next()) {\n                gs.eventQueue(\n                    'user.deactivation.notify_manager',  // Event name\n                    userGR,                              // Current user record\n                    userGR.name.toString(),              // parm1: user's name\n                    mgrGR.email.toString()               // parm2: manager's email\n                );\n            }\n        }\n    }\n\n    gs.info('Inactive or never-logged users deactivated; active managers notified.');\n})();\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactivate Memberless Group/README.md",
    "content": "There are instances where groups are created but remain without members for an extended period, making them ineffective.\nThis script helps identify such groups that have no members within a specified timeframe and make them inactive.\n\nScenario-\nDeactivate the active groups which doesn't have members for last 6 months.\nApproach-\n1. A scheduled job runs daily or weekly to identify groups without members.\n2. To track when a group becomes memberless, a Date field (e.g. u_memberless_date) is added to the sys_user_group table and populated with the current date when no members are found.\n3. If members are added later, the field value is cleared.\n4. Groups that remain memberless for over six months (based on the u_memberless_date) are automatically deactivated.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactivate Memberless Group/deactivate group.js",
    "content": "var groupGr = new GlideRecord('sys_user_group');\ngroupGr.addActiveQuery();\ngroupGr.query();\nwhile (groupGr.next()) {\n    var groupSysId = groupGr.sys_id.toString();\n\t// Query Group member table\n    var memberGr = new GlideRecord('sys_user_grmember');\n    memberGr.addQuery('group', groupSysId);\n    memberGr.query();\n    if (memberGr.hasNext()) {\n\t\t// If group has member but date is also populated that means member has joined recently. Clear the field value.\n        if (!gs.nil(groupGr.u_memberless_date)) {\n            groupGr.u_memberless_date = '';\n            groupGr.update();\n        }\n    } else {\n        var today = new GlideDate();\n        if (gs.nil(groupGr.u_memberless_date)) {\n\t\t\t// If group doesn't have member populate the fields with today's date if doesn't have a value.\n            groupGr.u_memberless_date = today;\n            groupGr.update();\n        } else {\n\t\t\t// If the field value is present compare the dates and deactivate group accordingly.\n            var fieldDate = groupGr.getValue('u_memberless_date');\n            today.addMonths(-6);\n            if (fieldDate < today) {\n                groupGr.active = false;\n\t\t\t\tgroupGr.description = \"Group has been deactivated for not having members in last 6 months.\";\n                groupGr.update();\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactive and Reactivate Catalog Items/README.md",
    "content": "# Scheduled Deactivation/Reactivation of Forms\n\nA scheduled job script to deactivate and reactivate forms automatically during a maintenance window.\n\n## Description\n\nThis script can be added to a scheduled job to deactivate and reactivate catalog items and/or record producers. An example of a use case is where maintenance is being performed which will involve a system outage so certain forms that might rely on an integration with that system need to be disabled during the maintenance window.\n\n## Getting Started\n\n### Dependencies\n\n* None\n\n### Execution\n\n1. Create a scheduled job that automatically runs a script of your choosing.\n2. Configure the scheduled job to run at the relevant time (see image for example).\n3. Copy the script from deactivate-reactivate-cat-item.js into the 'Run this script' field.\n4. Modify the variables as required:\n    * Add or remove variables to capture all of the catalog items that need to be deactivated/reactivated.\n    * Ensure the sys_id of the catalog item is set as the variable value.\n5. Update the array list on line 8 to include the variables you want to use.\n6. Set the value of the active flag on line 11 to 'false' to deactivate the catalog item or 'true' to activate the catalog item.\n7. Save the scheduled job and it will run once the trigger condition is met.\n\n### Additional Information\nIf you are setting a deactivate scheduled job for a maintenance window, make sure you create a second scheduled job to reactivate the catalog items once the maintenance window is closed.\n\n## Authors\n\nBrad Warman\n\nhttps://www.servicenow.com/community/user/viewprofilepage/user-id/80167\n\n## Version History\n\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Deactive and Reactivate Catalog Items/schedule-deactivation.js",
    "content": "// Configure a variable for each catalog item that needs to be deactivated/reactivated. Variable value needs to be the catalog item sys_id\nvar newITAccountForm = 'acbf71d7dbd3341087d5bc56f39619d8';\nvar modifyITAccountForm = 'bdcaa8f6db58ff00ee115d87f49619b3';\nvar dlManagementForm = 'a7d613e0dbce7740ee115d87f496193c';\nvar sharedMailboxManagementForm = '04eabdd1dbc67340de2e32e43a96196c';\n\n// Add the vatalog item variables to the array as required\nvar array = [modifyITAccountForm, dlManagementForm, sharedMailboxManagementForm];\n\nfor (item in array) {\n\tvar grItem = new GlideRecord('sc_cat_item');\n\tgrItem.get(array[item]);\n\tgrItem.setValue('active', false); // False to deactivate, True to activate.\n\tgrItem.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Delete Retired CI Rel/readme.md",
    "content": "This script will identify all the retired CI and update the releationship by removing it from the CIs\n This is will query the cmdb_ci_rel table and fetch all ci with status as install status == 7 and parent install status == 7\n\n As result it will delete all CI relationship and update the delete entry by querying custom table u_deleteret_app\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Delete Retired CI Rel/script.js",
    "content": "//get all relationship records where retired CI is Parent\nvar gr = new GlideRecord('cmdb_rel_ci');\ngr.addEncodedQuery(\"child.install_status=7^ORparent.install_status=7\");\ngr.query();\n\n//For each record with a retired CI\nwhile (gr.next()) {\n    var par = gr.parent;\n    var child = gr.child;\n    var tp = gr.type;\n    gr.deleteRecord();\n\n    var gr1 = new GlideRecord('u_delete_retired_relationships');\n    gr1.initialize();\n    gr1.u_child = child;\n    gr1.u_parent = par;\n    gr1.u_type = tp;\n    gr1.insert();\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Employee Probation case/Probation.js",
    "content": "//This schedule job will Execute daily \nvar hrProf = new GlideRecord('sn_hr_core_profile');\nhrProf.addQuery('user.active', true);   \nvar diff = [];\nhrProf.query();\nwhile (hrProf.next()) {\n var start = new GlideDateTime(hrProf.probation_date);\n    var currentDate = new GlideDateTime();\n    var gdt2 = new GlideDateTime(currentDate.getDisplayValue());\n    diff = GlideDateTime.subtract(gdt2, start);\n    var res = diff.getDisplayValue().toString();\n    var days = res.substring(0, 2);\n    var datediff = diff.getNumericValue();\n    var dateDifferenceInDays = Math.floor(datediff / (1000 * 60 * 60 * 24));\n      if (dateDifferenceInDays == \"30\") {\n            var hrCase = new GlideRecord(\"sn_hr_le_case\");\n            hrCase.initialize();\n            hrCase.hr_service = gs.getProperty(\"Probation HR Service\"); //Probation HR Service\n            hrCase.opened_for = hrProf.user.manager; // Manager of the user\n            hrCase.subject_person = hrProf.user;\n            hrCase.opened_by = hrProf.user.manager;\n            hrCase.state = '10';\n            hrCase.short_description = \"Probation HR Case for \" + hrProf.user.getDisplayValue();\n            hrCase.insert();\n            \n        }\n}\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Employee Probation case/README.md",
    "content": "#contribution 2\nI want to create Probation HR case for employees after 30 days of probation end date, means difference b/w current date & Probation end date should be 30 days in Schedule wise.This schedule job will be Executed daily basis. Here, HR case will create for active employee.Probation date field is available in HR Profile.\nHR case will store HR Service, state,subject person,short description, Opened by,Opened for etc.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Export Filtered Records to CSV Automatically/README.md",
    "content": "This code_snippet.js script export incidents of the last 7 days and email the CSV.\nThis script will be created as Scheduled Script.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Export Filtered Records to CSV Automatically/code_snippet.js",
    "content": "// Below script export incidents of the last 7 days and email the CSV using Scheduled Script\nvar fileName = 'incident_export_' + gs.nowDateTime() + '.csv';\nvar csv = 'Number,Short Description,Priority\\n';\nvar gr = new GlideRecord('incident');\ngr.addQuery('opened_at', '>=', gs.daysAgoStart(7));\ngr.query();\nwhile (gr.next()) {\n    csv += gr.number + ',' + gr.short_description + ',' + gr.priority + '\\n';\n}\nvar attachment = new GlideSysAttachment();\nattachment.write('incident', '', fileName, csv);\ngs.eventQueue('csv.report.ready', null, fileName, gs.getUserID());\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Get All Catalog Tasks without Request items/catalogTaskWithoutReqItem.js",
    "content": "function catalogTaskWithoutReqItem(){\n    var gliderecordToCatalogTask = new GlideRecord('sc_task');\n    gliderecordToCatalogTask.encodedQuery('request_itemISEMPTY');\n    gliderecordToCatalogTask.query();\n}\n// catalogTaskWithoutReqItem();"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Get All Catalog Tasks without Request items/catalogTaskWithoutReqItem.md",
    "content": "# Fetch all Catalog tasks which do not have any Request items associated.\n\nGet all the Catalog tasks which do not have any Request items, so that the requester can be notified and action can be taken."
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Licensed User Access Job/Weekly_LicensedUser_Access_Revoke_90Days.js",
    "content": "(function executeWeeklyJob() {\n\n    var DAYS_INACTIVE_THRESHOLD = 90; // number of days without login before revocation\n    var licensedRoles = ['itil', 'sys_approver', 'admin', 'business_stakeholder'];\n\n    var roleGroupMap = {\n        'itil': 'ITIL Group',\n        'sys_approver': 'Approver Group',\n        'admin': 'Admin Group',\n        'business_stakeholder': 'Business Stakeholder Group'\n    };\n\n    var thresholdDate = new GlideDateTime();\n    thresholdDate.addDaysUTC(-DAYS_INACTIVE_THRESHOLD);\n\n    // Iterate through each licensed role\n    for (var i = 0; i < licensedRoles.length; i++) {\n        var role = licensedRoles[i];\n        var groupName = roleGroupMap[role];\n\n        var userRoleGR = new GlideRecord('sys_user_has_role');\n        userRoleGR.addQuery('role.name', role);\n        userRoleGR.addQuery('user.active', true);\n        userRoleGR.query();\n\n        while (userRoleGR.next()) {\n            var user = userRoleGR.user.getRefRecord();\n            var lastLogin = user.last_login_time;\n\n            // If user never logged in or inactive beyond threshold\n            if (!lastLogin || lastLogin < thresholdDate) {\n//                gs.info('Revoking access for user: ' + user.name + ' (' + role + ')');\n\n                // Remove from corresponding group\n                var groupGR = new GlideRecord('sys_user_grmember');\n                groupGR.addQuery('user', user.sys_id);\n                groupGR.addQuery('group.name', groupName);\n                groupGR.query();\n                while (groupGR.next()) {\n                    groupGR.deleteRecord();\n                }\n\n            }\n        }\n    }\n})();\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Licensed User Access Job/readme.md",
    "content": "# Weekly Licensed User Access Review (90-Day Inactivity)\n\n# Overview\nThis scheduled job runs weekly and automatically revokes access for licensed users who have been inactive/last login for more than 90 days.  \nIt ensures license compliance, cost control, and adherence to security policies.\n\n# Objective\nTo identify active users holding licensed roles who have not logged into ServiceNow within the past 90 days and revoke their access by removing them from their respective groups.\n\n# Configuration Summary\n1. Threshold - 90 days since last login\n2. Frequency - Weekly\n3. Licensed Roles Checked - 'itil', 'sys_approver', 'admin', 'business_stakeholder'\n4. Groups Managed - ITIL Group, Approver Group, Admin Group, Business Stakeholder Group\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Lock out users who have not logged into the system longer than 30 days/README.md",
    "content": "**Scheduled Script Execution**\n\nThis script allows to *lock out* users who have not logged into the system for *longer than 30 days*. You can customize the additional query parameters and change the current ones for example in order to shorten or enlarge the time period. \n\n**Example configuration of Scheduled Script Execution**\n ![Coniguration](ScreenShot_1.PNG)\n\n**Example execution log**\n ![Execution](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Lock out users who have not logged into the system longer than 30 days/script.js",
    "content": "//Script to lock out users who have not logged into the system longer than 30 days\n\n//Query the users who are active, not locked out and have not logged into the system longer than 30 days\n//You can add addtional conditions to protect certain groups, e.g. technical users (use grUser.addQuery())\nvar grUser = new GlideRecord('sys_user');\ngrUser.addActiveQuery();\ngrUser.addEncodedQuery('last_login<javascript:gs.beginningOfLast30Days()^locked_out=false');\ngrUser.query();\n\n//For all users from the query, set the locked out flag to true\n//You can also set different parameters, for example set active to false instead of locked out\nwhile (grUser.next()) {\n    grUser.locked_out = true;\n    grUser.update();\n}\n\n//Log information about the number of locked out users\ngs.info('[Scheduled Script Execution] Locked out ' + grUser.getRowCount() + ' users, which have not logged into system for longer than 30 days.');\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Paginated Export/Paginated Export.md",
    "content": "# Paginated Export\n\nServiceNow (wisely) limits the number of rows allowed in an export. However, they do not provide a way to break up exports into multiple files if you need to export more than the limit. Run this script as a scheduled job or scheduled flow instead of doing a scheduled export if you need to export large data sets.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Paginated Export/paginatedExport.js",
    "content": "var tableName = ''; //table name to export from\nvar fileNamePrefix = ''; //name of the file that will be exported\nvar rowsPerExport; //number of rows to export per file\nvar orderByField = 'sys_id'; //name of the field to orderby, sys_id is recommended except in rare circumstances like querying a db view table\nvar instanceURL = ''; //url of the instance to export from\nvar fileFormat = ''; //desired format of the exported file\nvar authenticationProfileID = ''; //sys_id of the authentication profile to use for rest calls\nvar authenticationProfileType = ''; //type of authentication profile, either basic or oauth\nvar viewName = ''; //name of the view to use for the export (controls what fields are included)\nvar midServerName = ''; //name of midserver to export to\n\nvar gr = new GlideRecord(tableName);\ngr.setLimit(1000000);\ngr.query();\nvar count = gr.getRowCount();\nvar iterations = parseInt(count/rowsPerExport) + 1;\nfor(var i = 0; i < iterations; i++){\n  var id = getLastSysID(i * 10000);\n  var eccAttachmentID = createECCAttachmentGR();\n  var fileName = fileNamePrefix + i;\n  var attachmentID = downloadFile(id, eccAttachmentID, fileName);\n  var eccID = insertToECC(attachmentID, eccAttachmentID, fileName);\n}\n\nfunction getLastSysID(row){\n  var gr = new GlideRecord(tableName);\n  gr.orderBy(orderByField);\n  gr.chooseWindow(row, row+1);\n  gr.query();\n  gr.next();\n  return gr.getValue(orderByField);\n}\n\nfunction createECCAttachmentGR(){\n\tvar eccAttachmentGR = new GlideRecord('ecc_agent_attachment');\n\teccAttachmentGR.initialize();\n\teccAttachmentGR.setValue('source', 'Paginated Export');\n\teccAttachmentGR.setValue('name', 'Export Set Attachment');\n\tvar eccAttachmentID = eccAttachmentGR.insert();\n\treturn eccAttachmentID;\n}\n\nfunction downloadFile(id, eccAttachmentID, fileName){\n var request = new sn_ws.RESTMessageV2();\nrequest.setAuthenticationProfile(authenticationProfileType, authenticationProfileID);\n  request.setEndpoint(instanceURL + '/' + tableName + '_list.do?' + fileFormat.toUpperCase() + '&sysparm_view=' + viewName + '&sysparm_query=' + orderByField + '%3E%3D' + id + '&sysparm_orderby=' + orderByField + '&sysparm_record_count=' + rowsPerExport);\n  request.setHttpMethod('GET');\n  request.saveResponseBodyAsAttachment('ecc_agent_attachment', eccAttachmentID, fileName + '.' + fileFormat);\n  var response = request.execute(); \n  var attachmentID = response.getResponseAttachmentSysid();\n  return attachmentID;\n}\n\nfunction insertToECC(attachmentID, recordID, fileName){\n\tvar xmlString = getXMLString(attachmentID, recordID, fileName);\n\tvar eccGR = new GlideRecord('ecc_queue');\n\teccGR.initialize();\n\teccGR.setValue('agent', midServerName);\n\teccGR.setValue('topic', 'StreamPipeline');\n\teccGR.setValue('queue', 'output');\n\teccGR.setValue('payload', xmlString);\n\teccGR.insert();\n}\n\nfunction getXMLString(attachmentID, recordID, fileName){\n    fileName += '.' + fileFormat;\n\tvar xmlString = \n\t'<?xml version=\"1.0\" encoding=\"UTF-8\"?>' + \n\t'<parameters>' +\n\t\t'<parameter name=\\\"stream_relay_response_topic\\\" value=\\\"ExportSetResult\\\"/>' +\n\t\t'<stream_relay_source attachment_sys_id=\\\"' + attachmentID + '\\\" type=\\\"AttachmentSource\\\"/>' +\n\t\t'<stream_relay_transform attachment.table_sys_id=\\\"' + recordID + '\\\" order=\\\"0\\\" stream_relay_transfer_progress_interval=\\\"150\\\" type=\\\"AttachmentProgressTransformer\\\"/>' +\n\t\t'<stream_relay_sink path=\"\\/' + fileName + '\\\" type=\\\"FileSink\\\"/>' +\n\t\t'</parameters>';\n\treturn xmlString;\n}\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/PostUserDisabledActivity/README.md",
    "content": "Get today's deactivated users records script Scheduled Jobs\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/PostUserDisabledActivity/script.js",
    "content": "/*\nGet today's deactivated users records\n*/\nvar grUserRecords = JSON.parse(new global.glide_record_functions().getTableRecords('sys_user', 'sys_updated_onONToday@javascript:gs.beginningOfToday()@javascript:gs.endOfToday()^active=false^u_disabled_due_to_inactivity=false', 'sys_id'));\nif (grUserRecords.length > 0) {\n    //Take action on individual record\n    for (var i = 0; i < grUserRecords.length; i++) {\n        gs.eventQueue('actions.post.user.account.disabled', current, grUserRecords[i].sys_id);\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Proactive Change Request Reminder/README.md",
    "content": "**Proactive Change Request Reminder (Due in 24 Hours)**\n\n**Description**\nThis Scheduled Job sends an automated email reminder to the assigned agent of each Priority 1 Change Request that is due within the next 24 hours.\nIt helps teams stay ahead of deadlines, prevent SLA breaches, and ensure timely change implementation.\n\n**When to Use**\nUse this script to ensure that high-priority change requests are addressed on time by proactively notifying assigned engineers.\n\n**How It Works**\nChecks all active change_request records with:\n  - priority = 1\n  - state not in \"Closed\" or \"Complete\"\n  - due_date within the next 24 hours\nSends an email reminder to the assigned agent with the change number, description, and due date.\n\n**Scheduled Job Configuration**\nRecommended schedule:\n- Run: Periodically  \n- Repeat interval: 1 hour\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Proactive Change Request Reminder/proactive_change_reminder.js",
    "content": "//Get current time and time after 24 hours\nvar now = new GlideDateTime();\nvar after24Hours = new GlideDateTime();\nafter24Hours.addSeconds(24 * 60 * 60);\n\n//Query for high-priority Change Requests due within 24 hours which are not closed or complete\nvar gr = new GlideRecord('change_request');\ngr.addQuery('priority', 1);\ngr.addQuery('state', 'NOT IN', '3,4'); \ngr.addQuery('due_date', '>=', now);\ngr.addQuery('due_date', '<=', after24Hours);\ngr.query();\n\nwhile (gr.next()) {\n    var assignedTo = gr.assigned_to.getRefRecord();\n    if (assignedTo && assignedTo.email) {\n        //Email reminder\n        var mail = new GlideEmailOutbound();\n        mail.setSubject('Proactive Reminder: ' + gr.number + ' is due within 24 hours');\n        mail.setBody(\n            'Hi ' + assignedTo.name + ',\\n\\n' +\n            'This is a reminder that the following Change Request is due within the next 24 hours:\\n\\n' +\n            'Change Request: ' + gr.number + '\\n' +\n            'Short Description: ' + gr.short_description + '\\n' +\n            'Due Date: ' + gr.due_date.getDisplayValue() + '\\n\\n' +\n            'Please review and take the necessary actions.'\n        );\n        mail.addAddress('to', assignedTo.email);\n        mail.send();\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Reject approvals created before an year/README.md",
    "content": "**Script Purpose:**\nThis script helps you manage approval records in ServiceNow. It searches for approval requests in the sysapproval_approver table that were created more than 12 months ago and are currently marked as \"requested.\" The script then updates these records to change their status to \"rejected.\"\n\n**How to Use This Script**\nWhere to Run It: You can execute this script in a server-side context, such as a Scheduled Jobs, Script Include, or Background Script. Make sure you have the necessary permissions to access and update records in the sysapproval_approver table.\n\n**Be Cautious:** The script will automatically find the relevant approval records and update all matching records, so double-check the criteria before executing it.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Reject approvals created before an year/Reject Approvals Created Before an Year.js",
    "content": "var grAppr = new GlideRecord(\"sysapproval_approver\");\ngrAppr.addEncodedQuery(\"sys_created_on<javascript:gs.beginningOfLast12Months()^state=requested\");\ngrAppr.query();\nwhile(grAppr.next()){\n\tgrAppr.setValue('state', 'rejected');\n\tgrAppr.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Remove Inactive and locked out users from All Groups and Roles/README.md",
    "content": "# Remove Inactive and locked out users from All Groups and Roles\n\nIt is always a good practise to have a secure and clean working instance. Having users with specific criteria such as the user is inactive and has been locked out should not have any role or belong to any group."
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Remove Inactive and locked out users from All Groups and Roles/code.js",
    "content": "function removeRolesAndGroupMembership() {\n    var glideRecordUserTable = new GlideRecord(\"sys_user\");\n    glideRecordUserTable.addEncodedQuery('active=false^locked_out=true');\n    glideRecordUserTable.query();\n\n    while (glideRecordUserTable.next()) {\n        var glideRecordGroupMembers = new GlideRecord('sys_user_grmember');\n        glideRecordGroupMembers.addQuery('user', glideRecordUserTable.sys_id);\n        glideRecordGroupMembers.query();\n        while (glideRecordGroupMembers.next()) {\n            glideRecordGroupMembers.deleteRecord();\n        }\n\n        var glideRecordUserRoles = new GlideRecord('sys_user_has_role');\n        glideRecordUserRoles.addQuery('user', glideRecordUserTable.sys_id);\n        glideRecordUserRoles.query();\n        while (glideRecordUserRoles.next()) {\n            glideRecordUserRoles.deleteRecord();\n        }\n    }\n}\n\n// removeRolesAndGroupMembership();"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Role Usage Analyzer/README.md",
    "content": "# Role Usage Analyzer for ServiceNow\n\n## Overview\n\nThis script analyzes role assignments in your ServiceNow instance and identifies roles that are assigned to users but appear to be unused. It cross-references user activity logs to determine whether assigned roles are actively used.\n\n## Features\n\n- Scans all roles assigned to users\n- Checks user activity via `sys_history_line` to infer role usage\n- Flags roles that are assigned but show no signs of usage\n- Logs unused roles and the number of users assigned to them\n\n## Usage\n\n1. Navigate to **System Definition > Scheduled Jobs**.\n2. Create a new Script Include or Scheduled Job named `Role_Usage_Analyzer`.\n3. Paste the contents of `Role_Usage_Analyzer.js` into the script field.\n4. Run the script manually or schedule it to run periodically (e.g., weekly or monthly).\n\n## Notes\n\n- This script uses `sys_history_line` to infer user activity. For more accurate results, consider integrating with login logs or audit tables if available.\n- You can extend the script to automatically notify administrators or generate reports.\n- Roles used only in background scripts or integrations may not show up in history logs — manual review is recommended.\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Role Usage Analyzer/code.js",
    "content": "\n    // Role Usage Analyzer Script\n    // Description: Identifies roles assigned to users that may be unused.\n\n    var roleUsageMap = {};\n    var grUserRole = new GlideRecord('sys_user_has_role');\n    grUserRole.query();\n\n    while (grUserRole.next()) {\n        var userId = grUserRole.user.toString();\n        var roleId = grUserRole.role.toString();\n\n        if (!roleUsageMap[roleId]) {\n            roleUsageMap[roleId] = {\n                users: [],\n                used: false\n            };\n        }\n\n        roleUsageMap[roleId].users.push(userId);\n    }\n\n    var grHistory = new GlideRecord('sys_history_line');\n    grHistory.addQuery('user', 'ISNOTEMPTY');\n    grHistory.query();\n\n    while (grHistory.next()) {\n        var userId = grHistory.user.toString();\n        for (var roleId in roleUsageMap) {\n            if (roleUsageMap[roleId].users.indexOf(userId) !== -1) {\n                roleUsageMap[roleId].used = true;\n            }\n        }\n    }\n\n    for (var roleId in roleUsageMap) {\n        if (!roleUsageMap[roleId].used) {\n            var grRole = new GlideRecord('sys_user_role');\n            if (grRole.get(roleId)) {\n                gs.info('[Role Usage Analyzer] Unused Role: ' + grRole.name + ' | Assigned to Users: ' + roleUsageMap[roleId].users.length);\n            }\n        }\n    }\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/ScheduleAtSpecificDaysAndTimes/README.md",
    "content": "Script to execute a schedule job on specific days and times. For eg: Schedule to run on all Mondays, Wednesdays and Fridays at 6, 12, 18 and 24 hours. You could place this in the condition script of a schedule job, configure the properties to setup the required number of days and times. The job should be scheduled to run periodically at every one hour and depending on the configured values, it will execute the job at the required days and time.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/ScheduleAtSpecificDaysAndTimes/schedulejobcondition.js",
    "content": "/*************************************************************************************/\n// INPUT ARGUMENTS\n// days - Create a system property \"job.days.execute\" and specify the number of days as comma separated values eg 1,3,5 \n// 1 - Monday, 2 - Tuesday, 3 - Wednesday, 4 - Thursday, 5 - Friday, 6 - Saturday and 7 - Sunday\n\n// hours - Create a system property \"job.hours.execute\" and specify the time as comma separated values eg 6,12,18,24\n// 1-1am, 12-12pm, 18-6pm and 24-12am \n\n// OUTPUT ARGUMENTS\n// execute - Returns true or false and determines whether the job should be executed or not\n/*************************************************************************************/\n\nexecuteJob();\n\nfunction executeJob() {\n    var execute = false;\n    var days = gs.getProperty('job.days.execute');\n    var hours = gs.getProperty('job.hours.execute');\n\n    var dateTime = new GlideDateTime();\n    var dayOfWeek = dateTime.getDayOfWeekLocalTime();\n    var timeOfDay = dateTime.getLocalTime().toString().split(\" \")[1];\n    var hourOfDay = parseInt(timeOfDay.split(\":\")[0]);\n\n    if (days.indexOf(dayOfWeek) > -1 && hours.indexOf(hourOfDay) > -1) {\n        execute = true;\n    }\n\n    return execute;\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Scheduled Data Import for Groups Population(Support and Managed By) for CMDB Classes/ReadMe.md",
    "content": "This is a Post-Import Script designed for a Scheduled Import . Its purpose is to cleanly map and update the Support Group and Managed By Group fields on Configuration Items (CIs) after data has been loaded into the staging table.\n\nScript Functionality\nThe script iterates through the records of an Import Set and performs the following core steps for each row:\n\n i)Extract Data: Reads the CI Class Display Name (u_class), Support Group Name (u_support_group), and Managed By Group Name (u_managed_by_group) from the staging table record.\n\n ii)Validate Class: Uses the u_class(name from staging table) value to look up and confirm the correct target CMDB table name (e.g., finding cmdb_ci_server from the display name \"Server\").\n\n iii)Resolve Groups: Finds the system-unique sys_ids for both the Support Group and Managed By Group names.\n\n iv)Update CIs: Queries the determined CMDB table, filters the CIs based on the Optional-Filters placeholder, and sets the support_group and managed_by_group fields using the resolved sys_ids.\n\n**Points to note :\n1) The schedule-import should be linked to a Data Source which has the spreadsheet attached(where groups and classes info is present)\n2) This script populates the groups based on Group Name given in the spreadsheet(Make sure they are present in the instance and are following the appropriate naming convention)\n3) The script provided is a post script ,which executes after the data is imported.**\n   \n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Scheduled Data Import for Groups Population(Support and Managed By) for CMDB Classes/post-script.js",
    "content": "var impGR = new GlideRecord(data_source.import_set_table_name);\nimpGR.addQuery('sys_import_set', import_set.sys_id);\nimpGR.query();\n\nwhile (impGR.next()) {\n    var classDisplayName = impGR.getValue('u_class');//column name as in staging table\n    var supportGroupName = impGR.getValue('u_support_group');//column name as in staging table\n    var managedByGroupName = impGR.getValue('u_managed_by_group');//column name as in staging table\n\n    if (!classDisplayName) {\n        gs.warn('[Import] Skipping row with empty class.');\n        continue;\n    }\n\n    var classTable = '';\n    var dbObjGR = new GlideRecord('sys_db_object');\n    dbObjGR.addQuery('label', classDisplayName);\ndbObjGR.addEncodedQuery('nameSTARTSWITHu_cmdb_ci^ORnameSTARTSWITHcmdb_ci');//custom CMDB table names are prepended with u_cmdb_ci\n    dbObjGR.query();\n    if (dbObjGR.next()) {\n\n        classTable = dbObjGR.getValue('name');\n    }\n\n    if (!classTable) {\n        gs.warn('[Import] Could not find table for class: ' + classDisplayName);\n        continue;\n    }\n\n    var supportGroupId = '';\n    var managedByGroupId = '';\n\n    var groupGR = new GlideRecord('sys_user_group');\n    groupGR.addQuery('name', 'IN', supportGroupName + ',' + managedByGroupName);\n    groupGR.query();\n    while (groupGR.next()) {\n        var name = groupGR.getValue('name');\n        if (name === supportGroupName) supportGroupId = groupGR.getUniqueValue();\n        if (name === managedByGroupName) managedByGroupId = groupGR.getUniqueValue();\n    }\n\n    if (!supportGroupId || !managedByGroupId) {\n        gs.warn('[Import] Missing group sys_id for: ' + supportGroupName + ' or ' + managedByGroupName);\n        continue;\n    }\n\n\n    var ciGR = new GlideRecord(classTable);\n    ciGR.addEncodedQuery('Optional-Filters');\n    ciGR.query();\n    if (!ciGR.hasNext()) {\n        gs.warn('[Import] No CI found in ' + classTable + ' with name: ' + classDisplayName);\n    }\n\n    while (ciGR.next()) {\n        ciGR.setValue('support_group', supportGroupId);\n        ciGR.setValue('managed_by_group', managedByGroupId);\n        ciGR.update();\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Scheduled Job to Email Incident Count Report by Category/Readme.md",
    "content": "Scheduled Job: Sends an email with monthly report based on incident category count\n\nUses a custom event (custom.monthly.incident.report) with two parameters:\nparm1 → The formatted incident count report body\nparm2 → The month name\n\nWorking:\nRuns automatically on the 1st of each month.\nFetches all incidents created in the previous month.\nGroups them by category and counts totals.\nSends a summarized email report to the admin.\n\nEvent Registration\nName: custom.monthly.incident.report\nQueue: default\n\nNotification Configuration\nName: Monthly Incident Report by Category\nWhen to send: Event is fired\nEvent name: custom.monthly.incident.report\nRecipients: admin@example.com (or “Admin” group)\n\nSubject\nMonthly Incident Count Report - ${event.parm2}\n\nBody\n\nHello Admin,\n\nHere is the count of incidents created during ${event.parm2}, categorized by type:\n${event.parm1}\n\nRegards,\nServiceNow Automated Reports\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Scheduled Job to Email Incident Count Report by Category/Send_monthly_incident_category_count_report.js",
    "content": "(function() {\n    // Step 1: Define the date range for the previous month\n    var startOfMonth = new GlideDateTime();\n    startOfMonth.setValue(gs.beginningOfLastMonth());\n\n    var endOfMonth = new GlideDateTime();\n    endOfMonth.setValue(gs.endOfLastMonth());\n\n    // Step 2: Query all incidents created in that month\n    var gr = new GlideRecord('incident');\n    gr.addQuery('opened_at', '>=', startOfMonth);\n    gr.addQuery('opened_at', '<=', endOfMonth);\n    gr.query();\n\n    // Step 3: Build a map of category counts\n    var categoryCount = {};\n    while (gr.next()) {\n        var category = gr.category ? gr.category.toString() : 'Uncategorized';\n        categoryCount[category] = (categoryCount[category] || 0) + 1;\n    }\n\n    // Step 4: Build report body\n    var reportBody = '';\n    var total = 0;\n\n    for (var categoryName in categoryCount) {\n        total += categoryCount[categoryName];\n        reportBody += categoryName + ': ' + categoryCount[categoryName] + '\\n';\n    }\n\n    if (total === 0) {\n        reportBody = 'No incidents were created in the last month.';\n    } else {\n        reportBody = 'Total Incidents: ' + total + '\\n\\n' + reportBody;\n    }\n\n    // Step 5: Add month name for better readability\n    var monthName = gs.getMonthName(gs.monthsAgo(1));\n\n    // Step 6: Trigger custom event to send email\n    // parm1 = report body\n    // parm2 = month name\n    gs.eventQueue('custom.monthly.incident.report', null, reportBody, monthName);\n\n    gs.info('Monthly Incident Report event triggered for ' + monthName);\n\n})();\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Submit catalog item/README.md",
    "content": "# Submit catalog item (Scheduled Job)\n\nThis snippet demonstrates how to submit a Service Catalog item programmatically from server-side code using the `sn_sc.CartJS` API.\n\n## Purpose\n\nAdd an item to the Service Catalog cart with variables and perform checkout to create the request/req item records.\n\n## Script behavior\n\n- Creates a `sn_sc.CartJS()` cart instance.\n- Calls `addToCart()` with `sysparm_id`, optional `sysparm_quantity`, optional `sysparm_requested_for`, and a `variables` object.\n- Calls `checkoutCart()` to place the order.\n- Logs success or error information using `gs.info`, `gs.warn`, and `gs.error`.\n\n## Placeholders to replace\n\n- `SYS_ID_OF_CAT_ITEM`: the `sys_id` of the Catalog Item (`sc_cat_item` record).\n- `SYS_ID_OF_REQUESTED_FOR_USER`: (optional) the `sys_id` of the user to set as Requested For. Remove the property to default to the current user.\n- `variable_name1`, `variable_name2`, `variable_nameN` :variable names expected by the catalog item. Use the variable name (not label) unless your instance expects variable IDs.\n\n## Expected returns\n\nYou will receive an object containing the request number representating of the created `sc_request` with a `sc_req_item` Item. If unsure, log `JSON.stringify(checkoutInfo)` to inspect.\n\n## Overview\n1.Creates a cart instance with sn_sc.CartJS</br>\n2.Adds a catalog item to the cart</br>\n3.Places the order (checkout)</br>\n4.Logs success or error messages\n\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Submit catalog item/submit_catalog_item.js",
    "content": "// This script submits a catalog item to the ServiceNow Service Catalog using the CartJS API.\n// It adds the item to the cart with specified variables and then checks out the cart. \ntry {\n    var cart = new sn_sc.CartJS();\n    // Add the catalog item to the cart\n\n    var newItem = cart.addToCart({\n        // Replace 'SYS_ID_OF_CAT_ITEM' with the actual sys_id of the catalog item\n        \"sysparm_id\": \"SYS_ID_OF_CAT_ITEM\",\n        // Specify the quantity of the item\n        \"sysparm_quantity\": \"1\",\n        // Replace 'SYS_ID_OF_REQUESTED_FOR_USER' with the sys_id of the user for whom the item is requested\n        \"sysparm_requested_for\": \"SYS_ID_OF_REQUESTED_FOR_USER\",\n        // Add any necessary variables in the variables object\n        \"variables\": {\n            \"variable_name1\": \"value\",\n            \"variable_name2\": \"value\",\n            \"variable_name3\": \"value\"\n        }\n    });\n\n    if (newItem) {\n        // Checkout the cart to submit the order\n        var checkoutInfo = cart.checkoutCart();\n        gs.info('Catalog item submitted successfully: ' + JSON.stringify(checkoutInfo));\n    } else {\n        gs.warn('Failed to add catalog item to cart.');\n    }\n\n\n}\ncatch (e) {\n    // Catch any unexpected errors and log them\n    gs.error('Unexpected error while submitting catalog item: ' + e.message);\n}\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Survey Trigger Scheduled Script/README.md",
    "content": "## Use the code snippets to trigger survey via scheduled script. \n\n### Survey Trigger Scheduled Job\nsurvey_trigger_sj.js\n\n*Note:  Please update the query and sys_id as per the comments in the script*\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Survey Trigger Scheduled Script/survey_trigger_sj.js",
    "content": "var incidentGR = new GlideRecord('incident');\nincidentGR.addEncodedQuery(\"resolved_atONYesterday@javascript:gs.beginningOfYesterday()@javascript:gs.endOfYesterday()\"); //Update query as per your requirement for incident records\nincidentGR.query();\nwhile(incidentGR.next()){\nvar checkIfSent = checkIfSurveyAlreadySentforThisWeek(incidentGR.caller_id);\n\tif(checkIfSent == false)\n\t\t(new sn_assessment_core.AssessmentCreation()).conditionTrigger(incidentGR, 'sys_id'); //sys_id of the trigger condition record for the survey table name \"asmt_condition\".\n}\n\nfunction checkIfSurveyAlreadySentforThisWeek(callerId){\n\t\nvar surveyInstanceRec = new GlideRecord('asmt_assessment_instance');\n\tsurveyInstanceRec.addQuery('user',callerId);\n\tsurveyInstanceRec.addEncodedQuery('metric_type='Survey SYS ID'^task_id.sys_class_name=incident^state!=canceled'); //Update query as per your requirement and add sys_id for the survey record.\nsurveyInstanceRec.query();\n\tif(surveyInstanceRec.next()){\n\t\treturn true;\n\t}\n\telse\n\t\treturn false;\n}\n\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Top10jobsbyprocessingtime/README.md",
    "content": "This is script will be useful to montior your system performance by identifying the top contributors from schedule jobs by processing time to take necessary action. \n\nIn this script time intervalis updated as last 15 min but in ideal scenario this should be scheduled everyday to get the count report and montior the schedule job that is take more time to get completed. \n\nOUTPUT:\n*** Script: \nTop 10 url values from syslog_transaction\n\n\n\n********** Execution Details for the column JOB: Check Glide Service Status **********\n\nExecuted total number of times : JOB: Check Glide Service Status 1\n\nTop 10 response times : 45300\n\n\n********** Execution Details for the column JOB: Regenerate CRL and Flush CRL Cache **********\n\nExecuted total number of times : JOB: Regenerate CRL and Flush CRL Cache 1\n\nTop 10 response times : 1462\n\n\n********** Execution Details for the column JOB: SC - Calculate Compliance **********\n\nExecuted total number of times : JOB: SC - Calculate Compliance 1\n\nTop 10 response times : 5401\n\n\n********** Execution Details for the column JOB: [ITSM Analytics] Daily Data Collection **********\n\nExecuted total number of times : JOB: [ITSM Analytics] Daily Data Collection 1\n\nTop 10 response times : 16341\n\n[0:00:00.048] Total Time\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Top10jobsbyprocessingtime/script.js",
    "content": "*\nQuery the table SYS_LOG_TRANSACTION to identify the TOP 10 Schedule Job by Number of times it executed in one day and How much processing time it took to complete the execution\n>>>>> Go to https://<your instance URL>/syslog_transaction_list.do?sysparm_query=urlLIKE<your scheduled job name> and check the \"Transaction processing time\"\nThis will help to identify top contibutors that consume instance resource and can potentially cause slowness\nYou can execute this as Background scipt or Fix script\n*/\ntopN('syslog_transaction', 'url', 10);\n\nfunction topN(pTable, pColumn, pCount) {\n    var ga = new GlideAggregate(pTable);\n    ga.addAggregate('COUNT', pColumn);\n    ga.orderByAggregate('COUNT', pColumn);\n    //ga.addEncodedQuery('sys_created_onONYesterday@javascript:gs.beginningOfYesterday()@javascript:gs.endOfYesterday()^type=scheduler'); // Schedle job executed yesterday to identify Top 10 by execution time\n    ga.addEncodedQuery('type=scheduler^sys_created_onONLast 15 minutes@javascript:gs.beginningOfLast15Minutes()@javascript:gs.endOfLast15Minutes()'); // Schedle job executed in last 15 min to identify Top 10 by execution time\n    ga.query();\n    var i = 0;\n    var stdout = [];\n    var responseTime = [];\n    stdout.push('\\nTop ' + pCount + ' ' + pColumn + ' values from ' + pTable + '\\n'); //Get all Top 10 ScheduleJon details\n    while (ga.next() && (i++ < pCount)) {\n        stdout.push('\\n\\n***Execution Details for the column ' + ga.getValue(pColumn) + '***\\n');\n        var result1 = getResponseTimeDetails(pTable, 'type=scheduler^sys_created_onONLast 15 minutes@javascript:gs.beginningOfLast15Minutes()@javascript:gs.endOfLast15Minutes()^url=' + ga.getValue(pColumn)); // Schedle job executed in last 15 min to identify Top 10 by execution time\n        stdout.push('Executed total number of times : ' + ga.getValue(pColumn) + ' ' + ga.getAggregate('COUNT', pColumn));\n        stdout.push('\\nTop 10 response times : ' + result1);\n    }\n    gs.print(stdout.join(\"\\n\"));\n}\n\n// Fetch response Time of the schedule job Execution\nfunction getResponseTimeDetails(table, query) {\n    var responseTime = [];\n    var gr = new GlideAggregate(table);\n    gr.addEncodedQuery(query);\n    gr.orderByDesc('response_time');\n    gr.setLimit(10); // Set limit to 10\n    gr.query();\n\n    while (gr._next()) {\n        responseTime.push(gr.response_time.toString());\n    }\n    return responseTime.join(',');\n}\n\n/*\n******************OUTPUT************\n*** Script: \nTop 10 url values from syslog_transaction\n*** Execution Details for the column JOB: Flow Engine Event Handler ***\nExecuted total number of times : JOB: Flow Engine Event Handler[ 290 ]\nTop 10 response times : 58018,57294,56949,39272,38874,38174,38085,37490,37138,36447,25947\n********** Execution Details for the column JOB: BackgroundProgressJob **********\nExecuted total number of times : JOB: BackgroundProgressJob[ 221 ] \nTop 10 response times : 8671,7646,7050,7040,7035,7008,6993,6987,6880,6861,6803\n********** Execution Details for the column JOB: ASYNC: AgentNowResponse**********\nExecuted total number of times : JOB: ASYNC: AgentNowResponse [ 576 ]\nTop 10 response times : 17680,13488,12094,11999,11579,11281,10672,10620,9688,9552,9373\n********** Execution Details for the column JOB: events process**********\nExecuted total number of times : JOB: events process [ 075 ]\nTop 10 response times : 26986,14921,14102,13640,13603,3870,3808,3665,3360,3277,3001\n********** Execution Details for the column JOB: Service Mapping**********\nExecuted total number of times : JOB: Service Mapping Recomputation[ 167 ]\nTop 10 response times : 24035,11209,9297,8431,7857,7142,6555,6541,6218,6124,5855\n********** Execution Details for the column JOB: Event Management **********\nExecuted total number of times : JOB: Event Management[ 64 ]\nTop 10 response times : 939,744,729,644,629,598,585,534,533,518,452\n*/\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Unpublish Public Reports/README.md",
    "content": "Scheduled Job to query the report table for any reports that are Published or roles are set to Public and remove the public role from report.\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Unpublish Public Reports/UnpublishReports.js",
    "content": "var pubReport = new GlideRecord('sys_report');\npubReport.addQuery('is_published=true^ORroles=public');\npubReport.query();\nwhile(pubReport.next()) {\n    //Obtain current roles report is shared with\n\tvar removePublic = pubReport.roles;\n    //Remove public role from string\n\tremovePublic = removePublic.replace(/public/g, '');\n    //Set report roles to new string value. Wihtout public role, report will auto unpublish\n\tpubReport.roles.setValue(removePublic);\n\tpubReport.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Update Inactive Application Owner/README.md",
    "content": "This code snippet will update the owner of application records in the cmdb_ci_appl table where the current owner is inactive. It specifically sets the owner to the manager of that inactive owner, ensuring that each application has an active owner assigned.\n\n**GlideRecord Initialization:**\nvar grApp = new GlideRecord(\"cmdb_ci_appl\");\n\n**Query for Inactive Owners:**\ngrApp.addEncodedQuery(\"owned_by.active=false\");\n\n**Executing the Query:**\ngrApp.query();\n\n**Iterating Through Records:**\nwhile(grApp.next()){\n\n**Getting the Manager’s Sys ID:**\nvar managerSysId = grApp.owned_by.manager.toString();\n\n**Updating the Owner:**\nif (managerSysId) {\n    grApp.owned_by = managerSysId;\n    grApp.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Update Inactive Application Owner/Update Inactive Application Owner.js",
    "content": "var grApp = new GlideRecord(\"cmdb_ci_appl\");\ngrApp.addEncodedQuery(\"owned_by.active=false\");\ngrApp.query();\nwhile(grApp.next()){\nvar managerSysId = grApp.owned_by.manager.toString(); // Get Manager SysId     \nif (managerSysId) {\ngrApp.owned_by = managerSysId;\ngrApp.update();\n}\n}\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Weekly Incident Trend Analysis/README.md",
    "content": "# Weekly Incident Trend Analysis\n\n## Overview\nCompares incident volume week-over-week to track trends and identify anomalies in incident patterns.\n\n## What It Does\n- Counts incidents created last week\n- Counts incidents created this week\n- Calculates the difference (increase/decrease)\n- Logs the trend analysis result\n\n## Use Cases\n- Monitor incident volume trends\n- Identify weeks with unusual spike/drop in incidents\n- Track service health over time\n- Alert on incident volume anomalies\n- Weekly reporting on incident patterns\n\n## Files\n- `incident_trend_analyzer.js` - GlideAggregate-based trend analysis\n\n## How to Use\n\n### Option 1: Run as Scheduled Job\n1. Go to **System Scheduler > Scheduled Jobs**\n2. Create new Scheduled Job\n3. Copy code from `incident_trend_analyzer.js`\n4. Set to run weekly (e.g., every Monday morning)\n5. Check logs for trend results\n\n### Option 2: Run from Background Script\n1. Go to **System Diagnostics > Script Background**\n2. Copy and execute the code\n3. View results in logs\n\n### Example Usage\n```javascript\n// The script automatically:\n// 1. Queries incidents from last week\n// 2. Queries incidents from this week\n// 3. Compares counts\n// 4. Logs: \"Incident count increased by X compared to last week.\"\n```\n\n## Output Examples\n```\n\"Incident count increased by 15 compared to last week.\"\n\"Incident count decreased by 8 compared to last week.\"\n\"No change in incident volume week-over-week.\"\n```\n\n## Key Features\n- Uses `GlideAggregate` for efficient counting\n- No heavy querying of individual records\n- Date range filtering using ServiceNow helper functions\n- Week-over-week comparison logic\n\n## Requirements\n- ServiceNow instance with Incident table\n- Access to run Background Scripts or create Scheduled Jobs\n- Read access to incident records\n\n## Performance Notes\n- Very efficient - uses aggregation not GlideRecord loops\n- Minimal database impact\n- Suitable for running on schedule without performance concerns\n\n## Customization\nTo track other tables (Change, Problem, etc.):\n```javascript\n// Change 'incident' to your table name\nvar lastWeekAgg = new GlideAggregate('change_request');\nvar thisWeekAgg = new GlideAggregate('change_request');\n```\n\nTo track different time periods:\n```javascript\n// Use other helper functions:\n// gs.beginningOfThisMonth(), gs.endOfThisMonth()\n// gs.beginningOfThisYear(), gs.endOfThisYear()\n// gs.addMonthsUTC(), gs.addDaysUTC() for custom ranges\n```\n\n## Related ServiceNow APIs\n- [GlideAggregate](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_UsingGlideAggregate.html) - Used for efficient counting\n- [GlideSystem Date Functions](https://docs.servicenow.com/bundle/sandiego-application-development/page/app-store/dev_apps/concept/c_SystemDateFunctions.html)\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/Weekly Incident Trend Analysis/incident_trend_analyzer.js",
    "content": "(function() {\n    // Get last week's incident count\n    var lastWeekAgg = new GlideAggregate('incident');\n    lastWeekAgg.addAggregate('COUNT');\n    lastWeekAgg.addEncodedQuery('opened_at>=javascript:gs.beginningOfLastWeek()^opened_at<=javascript:gs.endOfLastWeek()');\n    lastWeekAgg.query();\n\n    var lastWeekCount = 0;\n    if (lastWeekAgg.next()) {\n        lastWeekCount = lastWeekAgg.getAggregate('COUNT');\n    }\n\n    // Get this week's incident count\n    var thisWeekAgg = new GlideAggregate('incident');\n    thisWeekAgg.addAggregate('COUNT');\n    thisWeekAgg.addEncodedQuery('opened_at>=javascript:gs.beginningOfThisWeek()^opened_at<=javascript:gs.endOfThisWeek()');\n    thisWeekAgg.query();\n\n    var thisWeekCount = 0;\n    if (thisWeekAgg.next()) {\n        thisWeekCount = thisWeekAgg.getAggregate('COUNT');\n    }\n\n    // Compare and log\n    var diff = thisWeekCount - lastWeekCount;\n    if (diff > 0)\n        gs.info(\"Incident count increased by \" + diff + \" compared to last week.\");\n    else if (diff < 0)\n        gs.info(\"Incident count decreased by \" + Math.abs(diff) + \" compared to last week.\");\n    else\n        gs.info(\"No change in incident volume week-over-week.\");\n})();\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/trigger on weekday/read.md",
    "content": "Purpose: To send notifications only on business days (Monday to Friday).\n\nVerifies if the current day is a weekday (Monday to Friday).\n\nAction on Valid Business Day:\n\nTriggers a custom event: \n\nUse Case:\n\nIdeal for daily reminders, alerts, or updates that should not be sent on weekends\n"
  },
  {
    "path": "Server-Side Components/Scheduled Jobs/trigger on weekday/script.js",
    "content": "(function executeRule(current, previous) {\n    var today = new GlideDateTime();\n    var dayOfWeek = today.getDayOfWeek(); // Returns 1 (Monday) to 7 (Sunday)\n\n    // Check if it's a weekday (Monday to Friday)\n    if (dayOfWeek >= 1 && dayOfWeek <= 5) {\n     \n        var grHoliday = new GlideRecord('cmn_schedule_holiday');\n        grHoliday.addQuery('date', today.getDate());\n        grHoliday.query();\n        if (!grHoliday.hasNext()) {\n            // Trigger notification\n            gs.eventQueue('<weekday>', current, '', '');\n        }\n    }\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Script Actions/Attachment Downloads Logger/README.md",
    "content": "# Attachment Downloads Logger\n\nA ServiceNow utility that logs attachment download activities by adding comments to the work notes of the associated record, enhancing visibility and ensuring compliance.\n\n## Challenge\n\nTracking attachment downloads in ServiceNow can be critical for maintaining data security and compliance. Without proper logging, it becomes difficult to identify who accessed sensitive information and when. This utility addresses this challenge by providing an automated solution to log attachment download activities directly in the work notes of the record.\n\nThis tool is particularly useful in scenarios where data access needs to be monitored for compliance purposes or to ensure accountability in data handling.\n\n## Description\n\nThe Attachments Download Logger Script Action is designed to automatically add work note of a record whenever an attachment is downloaded. This ensures that all attachment access activities are logged, providing a clear audit trail for administrators and compliance officers.\n\n## Functionality\n\nThe Attachments Download Logger Script Action provides the following capabilities:\n- Automatically logs attachment download activities in the work notes of the associated record.\n- Captures details such as the user who downloaded the attachment and the timestamp and what attachment was downloaded.\n- Enhances visibility into data access activities for better compliance and accountability.\n- Operates seamlessly in the background without requiring manual intervention.\n\n## Usage Instructions\n\n\n### Creating the Script Action\n\nTo create the Script Action for the Attachment Download Logger, follow these steps:\n\n1. Navigate to **System Policy > Events > Script Actions** in your ServiceNow instance.\n2. Click on the **New** button to create a new Script Action.\n3. Fill in the following fields:\n\n    - **Name**: `Add worknote for attachment download`\n    - **Event Name**: `attachment.read`\n    - **Active**: `true`\n    - **Order**: `100` (or any appropriate order value based on your instance configuration)\n    - **Script**:\n      ```javascript\n      //Add the attached script action code \n     \n      ```\n\n4. Click **Submit** to save the Script Action.\n\nThis Script Action will now automatically log attachment download activities in the work notes of the associated record.\n\n\n\n### Customizations \n\nYou can customize the script to include additional details, such as the IP address of the user or the reason for the download. Additionally, you can restrict the logging functionality to specific tables or user roles based on your requirements.\n\nIf you want to restrict the logging functionality to a specific table, you can use the `current.table_name` property in your script. For example, to apply the logging only to the `incident` table, you can add the condition in the **condition Script** field as below.\n\n```javascript\ncurrent.table_name == 'incident'\n```\n\nThis ensures that the logging functionality is executed only for records in the `incident` table.\n\n\n## Category\n\nServer-Side Components / Script Actions / Attachment Downloads Logger\n\n## Screenshots\n<img width=\"1256\" height=\"132\" alt=\"2025-10-23_22-57-59\" src=\"https://github.com/user-attachments/assets/dbd95461-2b81-40d8-9425-f3c98e724dd1\" />\n"
  },
  {
    "path": "Server-Side Components/Script Actions/Attachment Downloads Logger/scriptActionCode.js",
    "content": "var recordSysId = current.table_sys_id; //get the sys_id of the record to which the attachment is linked\nvar userSysId = event.user_id; //the user who read the attachment\nvar userName = event.user_name; // get the user name\nvar attachmentName = event.parm1; //get the attachment name\nvar tableName = current.table_name; //get the table name\n\nvar gr = new GlideRecord(tableName);\nif (gr.get(recordSysId)) {\n  gr.work_notes = `Attachment \"${attachmentName}\" was downloaded by user \"${userName}\" on ${new GlideDateTime().getDisplayValue()}.`;\n  gr.update();\n}\n"
  },
  {
    "path": "Server-Side Components/Script Actions/Custom Table Helper/README.md",
    "content": "# Make Number Field Read-Only Script\n\nDo you find it frustrating that the number field is not read-only by default on new tables? When creating a custom table extending from a task, the number field is, by default, editable. There's almost no reason why this field should not be set to read-only by default!\n\nThis script action addresses the issue. It will perform the following actions every time you create a new custom table extending from a task:\n\n- Sets the number field to read-only\n- Removes the default ITIL reference qualifier from assigned_to and assignment_group and sets only \"active=true\"\n"
  },
  {
    "path": "Server-Side Components/Script Actions/Custom Table Helper/Script Action.js",
    "content": "(function() {\n    /*\n    When new custom tables are created extended from task, some of the common activities are to \n    \t- set number field to readonly\n    \t- over-ride reference qualifier for assignment_group & assigned_to\n    This script action just does that!\n\n    Why not put this in business rule? Well, weirdly business rules don't trigger when new custom table is created through App Engine Studio\n    */\n\n    //ua.customtable.insert event is triggered when a new custom table is created.\n    var tableName = event.parm1.toString();\n    var tableScope = '';\n    \n    var getNewTable = new GlideRecord('sys_db_object');\n    getNewTable.addEncodedQuery('name=' + tableName);\n    getNewTable.setLimit(1);\n    getNewTable.query();\n    if (getNewTable.next()) {\n        tableScope = getNewTable.getValue('sys_scope');\n    }\n\n    if (tableName && tableScope) {\n        var isExtendedFromTask = isParentTask(tableName);\n        if (isExtendedFromTask) {\n            //table is extended from task, create overrides\n            createOverride(tableScope, tableName, 'number', true, true, false, '');\n            createOverride(tableScope, tableName, 'assignment_group', false, false, true, 'active=true');\n            createOverride(tableScope, tableName, 'assigned_to', false, false, true, 'active=true');\n        } else {\n            //do nothing! - current table is not extended from task.\n        }\n    }\n\n    //task could be parent/grand-parent/great grand-parent, keep checking recursively\n    function isParentTask(tableName) {\n        var getTable = new GlideRecord('sys_db_object');\n        getTable.addEncodedQuery('super_classISNOTEMPTY^name=' + tableName);\n        getTable.setLimit(1);\n        getTable.query();\n        if (!getTable.next()) {\n            return false;\n        }\n        if (getTable.super_class.name.toString() === 'task') {\n            return true;\n        }\n        var parentTable = getTable.super_class.name.toString();\n        return isParentTask(parentTable);\n    }\n\n    //now create overrides\n    function createOverride(scope, table, column, overrideRead, setReadOnly, overrideRef, refQual) {\n        var overrideGR = new GlideRecord('sys_dictionary_override');\n        overrideGR.newRecord();\n        overrideGR.setValue('sys_scope', scope);\n        overrideGR.setValue('base_table', 'task');\n        overrideGR.setValue('name', table);\n        overrideGR.setValue('element', column);\n        overrideGR.setValue('read_only_override', overrideRead);\n        overrideGR.setValue('read_only', setReadOnly);\n        overrideGR.setValue('reference_qual_override', overrideRef);\n        overrideGR.setValue('reference_qual', refQual);\n        overrideGR.insert();\n    }\n\n})();"
  },
  {
    "path": "Server-Side Components/Script Actions/Deactivate Inactive Users and Notify Managers/Readme.md",
    "content": "Script Action: Notify Manager on User Deactivation\n\nOverview\nThis Script Action is triggered when the event user.deactivation.notify_manager is fired by a background or scheduled job.\nIt dynamically sends an email notification to the manager of the deactivated user using the GlideEmailOutbound API.\n\nPurpose\nAutomatically inform a user’s manager when their account has been deactivated due to inactivity.\nUse event-driven notification — no direct email sending in the scheduled job script.\nKeep manager email addresses dynamic, using event parameters (parm1, parm2).\n\nEvent and Parameters\nThe Script Action listens for this event:\nEvent name: user.deactivation.notify_manager\n\nExplanation\nparm1 and parm2 are populated dynamically by the job that fired the event.\nparm1 → user’s name\nparm2 → manager’s email\nGlideEmailOutbound is used to send emails programmatically without needing a Notification record.\nThe message body is kept simple and readable, but can be formatted in HTML if needed.\n"
  },
  {
    "path": "Server-Side Components/Script Actions/Deactivate Inactive Users and Notify Managers/user_deactivation_notify_manager_script_action.js",
    "content": "(function(current, event, parm1, parm2) {\n    var userName = parm1;\n    var managerEmail = parm2;\n\n    var subject = 'User Deactivated: ' + userName;\n    var body = 'Hello,\\n\\n' +\n               'The user \"' + userName + '\" has been deactivated due to inactivity.\\n\\n' +\n               'Regards,\\nSystem Administrator';\n\n    // Send the email using GlideEmailOutbound (manual way)\n    var mail = new GlideEmailOutbound();\n    mail.setSubject(subject);\n    mail.setBody(body);\n    mail.setTo(managerEmail);\n    mail.send();\n})(current, event, parm1, parm2);\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/AbstractStrategy.js",
    "content": "var AbstractStrategy = Class.create();\nAbstractStrategy.prototype = Object.extendsObject(AbstractStrategyBase, {\n    /** Baseline Application \n    * Please override any base functionality here \n    * ---\n    * All vendor changes will be made to the *Base classes\n    * This will ensure you can track changes to future upgrades\n    * \n    * Copy existing methods to this file and make changes here\n    * **/\n    type: 'AbstractStrategy'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/AbstractStrategyBase.js",
    "content": "var AbstractStrategyBase = Class.create();\nAbstractStrategyBase.prototype = Object.extendsObject(ApplicationCore, {\n    initialize: function (/* expected */) {},\n    run: function() {\n        /**\n         * do something here\n         */\n    },\n\n    type: 'AbstractStrategyBase'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ApplicationCore.js",
    "content": "var ApplicationCore = Class.create();\nApplicationCore.prototype = Object.extendsObject(ApplicationCoreBase, {\n\n\t/** Baseline Application \n     * Please override any base functionality here \n     * ---\n     * All vendor changes will be made to the *Base classes\n     * This will ensure you can track changes to future upgrades\n     * \n     * Copy existing methods to this file and make changes here\n     * **/\n\n    type: 'ApplicationCore'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ApplicationCoreBase.js",
    "content": "var ApplicationCoreBase = Class.create();\nApplicationCoreBase.prototype = {\n    initialize: function () { },\n\n    /**\n     * skeleton\n     */\n    _init: function() {},\n\n    /**\n     * Constants for Defaults which should be set at the Application level\n     */\n    C_DEFAULT_NOUN1: gs.getProperty('', ''), // always use a 2nd parm default on gs.getProperty to avoid null\n\n    /**\n     * Constants for Tables\n     */\n    C_TBL_USER: 'sys_user',\n    C_TBL_GROUP: 'sys_user_group',\n\n    /**\n     * Constants for Property names, which will be called by gs.getProperty(this.C_PROP_N1, '');\n     */\n    C_PROP_NOUN1: '',\n\n    /** EXAMPLE OF HOW _getGr() can be consumed, only pass sys_id to endpoint based on class\n     * get a GlideRecord of the Group based on ID\n     * @param {string} id sys_id of the record\n     * @returns GlideRecord, if it exists\n     */\n    _getGrGroup: function (id) {\n        return this._getGr(this.C_TBL_GROUP, id);\n    },\n\n    /** EXAMPLE OF HOW _getGr() can be consumed, only pass sys_id to endpoint based on class\n     * get a GlideRecord of the User based on ID\n     * @param {string} id sys_id of the record\n     * @returns GlideRecord, if it exists\n     */\n    _getGrUser: function (id) {\n        return this._getGr(this.C_TBL_USER, id);\n    },\n\n    /**\n     * get a GlideRecord of the specified table \n     * @param {string} strTableName name of table\n     * @param {string} strFieldName name of field // OPTIONAL\n     * @param {string} id sys_id of the record\n     * @returns GlideRecord of record, if it exists\n     */\n     _getGr: function(strTableName, strFieldName /* optional */ , id) {\n        if (!strTableName || !strFieldName) return;\n        id = id ? id : strFieldName;\n        var wrGr = new GlideRecord(strTableName);\n        if (wrGr.isValidField(strFieldName)) {\n            if (!wrGr.get(strFieldName, id)) return;\n        } else {\n            if (!wrGr.get(id)) return;\n        }\n        return wrGr;\n    },\n\n    /**\n     * \n     * @param {GlideRecord} wrGr record to be updated\n     * @param {Object} objFieldValues name value pair of fieldname and value e.g {'short_description': 'record title'}\n     * @returns string sys_id of the record being updated, if it exists\n     */\n    _setGr: function (wrGr, objFieldValues) {\n        if (!wrGr || typeof wrGr != 'object' || !wrGr.isValidRecord()) return;\n        objFieldValues = objFieldValues || {};\n        if (typeof objFieldValues != 'object') return;\n        for (var key in objFieldValues) {\n            if (wrGr.getElement(key) != null)\n                wrGr.setValue(key, objFieldValues[key]);\n        }\n        return wrGr.update();\n    },\n\n    /**\n     * \n     * @param {String} strTableName name of table\n     * @param {String} id sys_id of the record\n     * @param {Object} objFieldValues name value pair of fieldname and value e.g {'short_description': 'record title'}\n     * @returns string sys_id of the record being updated, if it exists\n     */\n     _setRecordFieldValuesFromTableNameAndId: function (strTableName, id, objFieldValues) {\n        var wrGr = this._getGr(strTableName, id);\n        if (!wrGr) return;\n        return this._setGr(wrGr, objFieldValues);\n    },\n\n    type: 'ApplicationCoreBase'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/Engine.js",
    "content": "var Engine = Class.create();\nEngine.prototype = Object.extendsObject(EngineBase, {\n    /** Baseline Application \n      * Please override any base functionality here \n      * ---\n      * All vendor changes will be made to the *Base classes\n      * This will ensure you can track changes to future upgrades\n      * \n      * Copy existing methods to this file and make changes here\n      * **/\n\n    type: 'Engine'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/EngineBase.js",
    "content": "var EngineBase = Class.create();\nEngineBase.prototype = Object.extendsObject(ApplicationCore, {\n    initialize: function (/* expected */) {},\n\n    process: function() {\n        return this._process();\n    },\n\n    _process: function() {\n        try {\n            var strategy = this._getStrategy(/* expected */);\n            if (!strategy) return;\n            strategy.run();\n        } catch (ex) {\n            gs.error(ex.getMessage());\n        }\n    },\n\n    _getStrategy: function(grRecord) {\n        grRecord = grRecord || this.grRecord || null;\n        if(!grRecord) return;\n        var strType = this._getType(grRecord) || ''; // method needs to be defined\n        switch (strType) {\n            case '1':\n                return new ExampleStrategy1(/* expected */);\n            case '2':\n                return new ExampleStrategy2(/* expected */);\n        }\n    },\n\n    type: 'EngineBase'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ExampleObject.js",
    "content": "var ExampleObject = Class.create();\nExampleObject.prototype = Object.extendsObject(ExampleObjectBase, {\n\t/** Baseline Object\n     * All application calls should be made directly to this API\n     *  \n     * Please override any base functionality here \n     * ---\n     * All vendor changes will be made to the *Base classes\n     * This will ensure you can track changes to future upgrades\n     * \n     * Copy existing methods to this file and make changes here\n     * **/\n\n    type: 'ExampleObject'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ExampleObjectBase.js",
    "content": "var ExampleObjectBase = Class.create();\nExampleObjectBase.prototype = Object.extendsObject(ApplicationCore, {\n    /**\n     * Typically the constructor function will be required on the new Object base\n     * if every object would be constructed in same way, move this to the core ? \n     * or move to the core, and let it be overridden - might be better\n     */\n    initialize: function (grRecord) {\n        if (!this._init(grRecord)) return; // stop dead, but only to prevent further errors. object still created. validate caller.\n        // some other dependent behaviour\n    },\n\n    /**\n     * use JSDOC , VSCode4life TLA IDST\n     * @returns <3\n     */\n    _anyFunctionYouWant: function() {\n        if (!this.grRecord) return; // use a guard clause on every function to ensure class is correctly constructed\n    },\n\n    /**\n     * pass off the main constructor function and abstract complexity to keep things tidy\n     * just some ideas about how to handle the instantiation validation complication\n     */\n    _init: function (grRecord) {\n        // set-up your prototype here\n        if (!grRecord) return;\n        if (typeof grRecord == 'object' && grRecord.isValidRecord()) {\n            this.grRecord = grRecord;\n        } else if (typeof grRecord == 'string' && grRecord.length == 32) {\n            // + assuming we knew what table:\n            var strTableName = '';\n            var id = grRecord; // it is a 32 char string\n            this.grRecord = this._getGr(strTableName, id);\n        }       \n        // some other dependent behaviour\n        // here\n        // turn on & validate object specific settings\n        // here, e.g this._setLogLevel() \n        return this.grRecord; // prove success\n    },\n\n    type: 'ExampleObjectBase'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ExampleStrategy1.js",
    "content": "var ExampleStrategy1 = Class.create();\nExampleStrategy1.prototype = Object.extendsObject(ExampleStrategy1Base, {\n    /** Baseline Application \n    * Please override any base functionality here \n    * ---\n    * All vendor changes will be made to the *Base classes\n    * This will ensure you can track changes to future upgrades\n    * \n    * Copy existing methods to this file and make changes here\n    * **/\n    type: 'ExampleStrategy1'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ExampleStrategy1Base.js",
    "content": "var ExampleStrategy1Base = Class.create();\nExampleStrategy1Base.prototype = Object.extendsObject(AbstractStrategy, {\n    initialize: function (/* expected */) {},\n    run: function() {\n        /**\n         * do something here\n         */\n    },\n\n    type: 'ExampleStrategy1Base'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ExampleStrategy2.js",
    "content": "var ExampleStrategy2 = Class.create();\nExampleStrategy2.prototype = Object.extendsObject(ExampleStrategy2Base, {\n    /** Baseline Application \n    * Please override any base functionality here \n    * ---\n    * All vendor changes will be made to the *Base classes\n    * This will ensure you can track changes to future upgrades\n    * \n    * Copy existing methods to this file and make changes here\n    * **/\n    type: 'ExampleStrategy2'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/ExampleStrategy2Base.js",
    "content": "var ExampleStrategy2Base = Class.create();\nExampleStrategy2Base.prototype = Object.extendsObject(AbstractStrategy, {\n    initialize: function (/* expected */) {},\n    run: function() {\n        /**\n         * do something here\n         */\n    },\n\n    type: 'ExampleStrategy2Base'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/API Model Template for New Application/README.md",
    "content": "# Application Baseline Pattern\n\nUse this Script Include to extend into your new API model and provide a set of re-usable and extendable methods to enhance your application design\n\nCreate the new Script Include in your SN instance in the relevant scope, using the matching filename\nThen change the name and ensure all references are updated\n\nYou can re-use and extend this pattern based on any object and centralise code in the core API base\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Add Business Days/README.md",
    "content": "The client callable script includes adds number of business days using the OOB 8-5 weekdays, excluding holidays, schedule\nThe script includes the sys_id of the OOB schedule, calculates the number of business days, and returns the date.\nFor the onChange client script, refer to the README file in the Auto-POpulate Planned End Date client script folder. Link: [Auto-Populate Planned End Date Client Script](/Client-Side%20Components/Client%20Scripts/Auto-Populate%20Planned%20End%20Date/README.md)\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Add Business Days/addBusinessDays.js",
    "content": "// Takes the number of days as a parameter, and the start date\n//Name: addBusinessDays\n//Client Callable: checked\nvar addBusinessDays = Class.create();\nvar schedule = new GlideSchedule('090eecae0a0a0b260077e1dfa71da828');\naddBusinessDays.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\n    addDays: function() {\n        var days = parseInt(this.getParameter(\"sysparm_days\"));\n        var startDate = new GlideDateTime(this.getParameter(\"sysparm_date\").toString());       \n        var sd = startDate;\n        startDate = startDate.getDate();\n        var time = sd.getTime().getByFormat('HH:mm:ss');\n        var dur = new GlideDuration(60 * 60 * 1000 * (days * 9));\n        schedule = new GlideSchedule('090eecae0a0a0b260077e1dfa71da828'); //sys_id of OOB schedule 8-5 weekdays excluding holidays\n        var end = schedule.add(startDate, dur);       \n        var endDate = end.getDate().getByFormat(\"MM/dd/yyyy\");\n        var endDateTime = endDate + ' ' + time;\n        return endDateTime.toString();\n    },\n    type: 'addBusinessDays'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Add and Remove Group Member/README.md",
    "content": "# Add and Remove Group Member\nI've developed a script include that facilitates the addition and removal of members from a group.\nBoth the \"addMember\" and \"removeMember\" functions in this script include require the sys_id of the group and the user as input parameters.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Add and Remove Group Member/groupMember.js",
    "content": "//A script include to add and remove member from a group. We need to pass the sys_id of the group and user to both addMember and removeMember function.\nvar GroupMember = Class.create();\nGroupMember.prototype = {\n    initialize: function() {},\n\n    addMember: function(groupSysId, userSysId) {\n        var groupMemberGR = new GlideRecord('sys_user_grmember');\n        groupMemberGR.initialize();\n        groupMemberGR.setValue('group', groupSysId);\n        groupMemberGR.setValue('user', userSysId);\n        groupMemberGR.insert();\n    },\n\n    removeMember: function(groupSysId, userSysId) {\n        var groupMemberGR = new GlideRecord('sys_user_grmember');\n        groupMemberGR.addQuery('group', groupSysId);\n        groupMemberGR.addQuery('user', userSysId);\n        groupMemberGR.query();\n        if (groupMemberGR.next()) {\n            groupMemberGR.deleteRecord();\n        }\n    },\n\n    type: 'GroupMember'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Advanced REST API Integration with Retry Logic/README.md",
    "content": "# Advanced REST API Integration with Retry Logic\n\nThis folder contains advanced Script Include examples demonstrating robust REST API integration patterns with retry logic, circuit breaker pattern, rate limiting, and comprehensive error handling.\n\n## Overview\n\nProduction-grade REST API integrations require more than basic HTTP calls. This example demonstrates:\n- **Exponential backoff retry logic** for transient failures\n- **Circuit breaker pattern** to prevent cascading failures\n- **Rate limiting** to respect API quotas\n- **Request/response logging** for debugging and auditing\n- **OAuth 2.0 authentication** with token caching\n- **Comprehensive error handling** with custom exceptions\n- **Response caching** to reduce API calls\n\n## Script Descriptions\n\n- **RESTIntegrationHandler.js**: Main Script Include with complete integration framework including retry logic, circuit breaker, and rate limiting.\n- **RESTIntegrationExample.js**: Example usage showing how to implement specific API integrations using the handler.\n- **RESTIntegrationUtils.js**: Utility functions for common REST operations like authentication, response parsing, and error handling.\n\n## Key Features\n\n### 1. Exponential Backoff Retry Logic\nAutomatically retries failed requests with increasing delays:\n```javascript\n// Retry delays: 1s, 2s, 4s, 8s, 16s\nmaxRetries: 5\ninitialDelay: 1000ms\nbackoffMultiplier: 2\n```\n\n### 2. Circuit Breaker Pattern\nPrevents overwhelming failing services:\n- **Closed**: Normal operation, requests pass through\n- **Open**: Service failing, requests fail fast\n- **Half-Open**: Testing if service recovered\n\n### 3. Rate Limiting\nRespects API rate limits:\n- Token bucket algorithm\n- Configurable requests per second\n- Automatic throttling\n\n### 4. OAuth 2.0 Support\nHandles authentication automatically:\n- Token acquisition and refresh\n- Secure credential storage\n- Automatic retry on 401 errors\n\n## Use Cases\n\n- **External API Integration**: Integrate with third-party services (Slack, Teams, Jira, etc.)\n- **Microservices Communication**: Call internal microservices with resilience\n- **Data Synchronization**: Sync data between ServiceNow and external systems\n- **Webhook Handlers**: Make reliable outbound webhook calls\n- **Enterprise Service Bus**: Connect to ESB/API Gateway\n\n## Configuration\n\nCreate a system property for each integration:\n```javascript\n// System Properties\nx_company.api.base_url = https://api.example.com\nx_company.api.client_id = your_client_id\nx_company.api.client_secret = [encrypted]\nx_company.api.max_retries = 5\nx_company.api.rate_limit = 100 // requests per minute\n```\n\n## Error Handling\n\nThe framework provides detailed error information:\n- HTTP status codes\n- Error messages from API\n- Retry attempts made\n- Circuit breaker state\n- Request/response logs\n\n## Performance Considerations\n\n- **Connection Pooling**: Reuses HTTP connections\n- **Response Caching**: Caches GET responses with TTL\n- **Async Operations**: Supports async calls for long-running operations\n- **Timeout Configuration**: Configurable timeouts per endpoint\n\n## Security Best Practices\n\n- Store credentials in encrypted system properties\n- Use OAuth 2.0 or API keys (never hardcode)\n- Log requests but sanitize sensitive data\n- Implement IP whitelisting where possible\n- Use HTTPS only\n- Validate SSL certificates\n\n## Monitoring and Alerting\n\nThe framework logs metrics for monitoring:\n- Request count and success rate\n- Average response time\n- Retry count\n- Circuit breaker state changes\n- Rate limit violations\n\n## Testing\n\nInclude unit tests for:\n- Successful API calls\n- Retry logic on transient failures\n- Circuit breaker state transitions\n- Rate limiting behavior\n- Authentication token refresh\n- Error handling\n\n## Related Patterns\n\n- **RESTMessageV2**: ServiceNow's built-in REST client\n- **GlideHTTPRequest**: Lower-level HTTP client\n- **Outbound REST Messages**: Configuration-based REST calls\n- **MID Server**: For on-premise API calls\n\n## Additional Resources\n\n- ServiceNow REST API Documentation\n- Circuit Breaker Pattern\n- OAuth 2.0 Specification\n- Rate Limiting Algorithms\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Advanced REST API Integration with Retry Logic/RESTIntegrationExample.js",
    "content": "/**\n * Example usage of RESTIntegrationHandler\n * Demonstrates how to implement specific API integrations\n */\n\n// ========================================\n// Example 1: Slack Integration\n// ========================================\nvar SlackIntegration = Class.create();\nSlackIntegration.prototype = Object.extendsObject(RESTIntegrationHandler, {\n    \n    initialize: function() {\n        RESTIntegrationHandler.prototype.initialize.call(this, 'slack');\n    },\n    \n    /**\n     * Send a message to a Slack channel\n     * @param {string} channel - Channel ID or name\n     * @param {string} text - Message text\n     * @param {object} options - Additional options (attachments, blocks, etc.)\n     */\n    sendMessage: function(channel, text, options) {\n        options = options || {};\n        \n        var payload = {\n            channel: channel,\n            text: text,\n            username: options.username || 'ServiceNow Bot',\n            icon_emoji: options.icon || ':robot_face:'\n        };\n        \n        // Add attachments if provided\n        if (options.attachments) {\n            payload.attachments = options.attachments;\n        }\n        \n        // Add blocks for rich formatting\n        if (options.blocks) {\n            payload.blocks = options.blocks;\n        }\n        \n        var response = this.post('/chat.postMessage', payload);\n        \n        if (response.success) {\n            gs.info('[Slack] Message sent successfully to ' + channel);\n            return {\n                success: true,\n                messageId: response.data.ts,\n                channel: response.data.channel\n            };\n        } else {\n            gs.error('[Slack] Failed to send message: ' + response.error);\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    /**\n     * Create an incident notification in Slack\n     * @param {GlideRecord} incidentGr - Incident GlideRecord\n     */\n    notifyIncident: function(incidentGr) {\n        var channel = gs.getProperty('x_company.slack.incident_channel', '#incidents');\n        \n        var attachments = [{\n            color: this._getPriorityColor(incidentGr.getValue('priority')),\n            title: incidentGr.getValue('number') + ': ' + incidentGr.getValue('short_description'),\n            title_link: gs.getProperty('glide.servlet.uri') + 'incident.do?sys_id=' + incidentGr.getUniqueValue(),\n            fields: [\n                {\n                    title: 'Priority',\n                    value: incidentGr.getDisplayValue('priority'),\n                    short: true\n                },\n                {\n                    title: 'State',\n                    value: incidentGr.getDisplayValue('state'),\n                    short: true\n                },\n                {\n                    title: 'Assigned To',\n                    value: incidentGr.getDisplayValue('assigned_to') || 'Unassigned',\n                    short: true\n                },\n                {\n                    title: 'Assignment Group',\n                    value: incidentGr.getDisplayValue('assignment_group') || 'Unassigned',\n                    short: true\n                }\n            ],\n            footer: 'ServiceNow',\n            ts: Math.floor(new Date().getTime() / 1000)\n        }];\n        \n        return this.sendMessage(channel, 'New Critical Incident', {\n            attachments: attachments\n        });\n    },\n    \n    _getPriorityColor: function(priority) {\n        var colors = {\n            '1': 'danger',    // Critical - Red\n            '2': 'warning',   // High - Orange\n            '3': '#439FE0',   // Medium - Blue\n            '4': 'good',      // Low - Green\n            '5': '#CCCCCC'    // Planning - Gray\n        };\n        return colors[priority] || '#CCCCCC';\n    },\n    \n    type: 'SlackIntegration'\n});\n\n// ========================================\n// Example 2: GitHub Integration\n// ========================================\nvar GitHubIntegration = Class.create();\nGitHubIntegration.prototype = Object.extendsObject(RESTIntegrationHandler, {\n    \n    initialize: function() {\n        RESTIntegrationHandler.prototype.initialize.call(this, 'github');\n    },\n    \n    /**\n     * Create an issue in GitHub repository\n     * @param {string} owner - Repository owner\n     * @param {string} repo - Repository name\n     * @param {object} issueData - Issue data (title, body, labels, assignees)\n     */\n    createIssue: function(owner, repo, issueData) {\n        var endpoint = '/repos/' + owner + '/' + repo + '/issues';\n        \n        var payload = {\n            title: issueData.title,\n            body: issueData.body || '',\n            labels: issueData.labels || [],\n            assignees: issueData.assignees || []\n        };\n        \n        if (issueData.milestone) {\n            payload.milestone = issueData.milestone;\n        }\n        \n        var response = this.post(endpoint, payload);\n        \n        if (response.success) {\n            gs.info('[GitHub] Issue created: ' + response.data.html_url);\n            return {\n                success: true,\n                issueNumber: response.data.number,\n                url: response.data.html_url,\n                id: response.data.id\n            };\n        } else {\n            gs.error('[GitHub] Failed to create issue: ' + response.error);\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    /**\n     * Get repository information\n     * @param {string} owner - Repository owner\n     * @param {string} repo - Repository name\n     */\n    getRepository: function(owner, repo) {\n        var endpoint = '/repos/' + owner + '/' + repo;\n        var response = this.get(endpoint, null, { useCache: true });\n        \n        if (response.success) {\n            return {\n                success: true,\n                name: response.data.name,\n                description: response.data.description,\n                stars: response.data.stargazers_count,\n                forks: response.data.forks_count,\n                language: response.data.language,\n                url: response.data.html_url\n            };\n        } else {\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    /**\n     * List pull requests for a repository\n     * @param {string} owner - Repository owner\n     * @param {string} repo - Repository name\n     * @param {string} state - PR state (open, closed, all)\n     */\n    listPullRequests: function(owner, repo, state) {\n        var endpoint = '/repos/' + owner + '/' + repo + '/pulls';\n        var params = {\n            state: state || 'open',\n            per_page: 100\n        };\n        \n        var response = this.get(endpoint, params, { useCache: true });\n        \n        if (response.success) {\n            var prs = response.data.map(function(pr) {\n                return {\n                    number: pr.number,\n                    title: pr.title,\n                    state: pr.state,\n                    author: pr.user.login,\n                    url: pr.html_url,\n                    created_at: pr.created_at\n                };\n            });\n            \n            return {\n                success: true,\n                pullRequests: prs,\n                count: prs.length\n            };\n        } else {\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    type: 'GitHubIntegration'\n});\n\n// ========================================\n// Example 3: Jira Integration\n// ========================================\nvar JiraIntegration = Class.create();\nJiraIntegration.prototype = Object.extendsObject(RESTIntegrationHandler, {\n    \n    initialize: function() {\n        RESTIntegrationHandler.prototype.initialize.call(this, 'jira');\n    },\n    \n    /**\n     * Create a Jira issue\n     * @param {object} issueData - Issue data\n     */\n    createIssue: function(issueData) {\n        var payload = {\n            fields: {\n                project: {\n                    key: issueData.projectKey\n                },\n                summary: issueData.summary,\n                description: issueData.description || '',\n                issuetype: {\n                    name: issueData.issueType || 'Task'\n                }\n            }\n        };\n        \n        // Add optional fields\n        if (issueData.priority) {\n            payload.fields.priority = { name: issueData.priority };\n        }\n        \n        if (issueData.assignee) {\n            payload.fields.assignee = { name: issueData.assignee };\n        }\n        \n        if (issueData.labels) {\n            payload.fields.labels = issueData.labels;\n        }\n        \n        var response = this.post('/rest/api/2/issue', payload);\n        \n        if (response.success) {\n            gs.info('[Jira] Issue created: ' + response.data.key);\n            return {\n                success: true,\n                issueKey: response.data.key,\n                issueId: response.data.id,\n                url: this.baseUrl + '/browse/' + response.data.key\n            };\n        } else {\n            gs.error('[Jira] Failed to create issue: ' + response.error);\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    /**\n     * Search for Jira issues using JQL\n     * @param {string} jql - JQL query string\n     * @param {number} maxResults - Maximum results to return\n     */\n    searchIssues: function(jql, maxResults) {\n        var params = {\n            jql: jql,\n            maxResults: maxResults || 50,\n            fields: 'summary,status,assignee,priority,created,updated'\n        };\n        \n        var response = this.get('/rest/api/2/search', params, { useCache: true });\n        \n        if (response.success) {\n            var issues = response.data.issues.map(function(issue) {\n                return {\n                    key: issue.key,\n                    summary: issue.fields.summary,\n                    status: issue.fields.status.name,\n                    assignee: issue.fields.assignee ? issue.fields.assignee.displayName : 'Unassigned',\n                    priority: issue.fields.priority ? issue.fields.priority.name : 'None',\n                    created: issue.fields.created,\n                    updated: issue.fields.updated\n                };\n            });\n            \n            return {\n                success: true,\n                issues: issues,\n                total: response.data.total\n            };\n        } else {\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    /**\n     * Add comment to Jira issue\n     * @param {string} issueKey - Jira issue key (e.g., PROJ-123)\n     * @param {string} comment - Comment text\n     */\n    addComment: function(issueKey, comment) {\n        var endpoint = '/rest/api/2/issue/' + issueKey + '/comment';\n        var payload = {\n            body: comment\n        };\n        \n        var response = this.post(endpoint, payload);\n        \n        if (response.success) {\n            gs.info('[Jira] Comment added to ' + issueKey);\n            return {\n                success: true,\n                commentId: response.data.id\n            };\n        } else {\n            return {\n                success: false,\n                error: response.error\n            };\n        }\n    },\n    \n    type: 'JiraIntegration'\n});\n\n// ========================================\n// Usage Examples\n// ========================================\n\n// Example 1: Send Slack notification\nfunction sendSlackNotification() {\n    var slack = new SlackIntegration();\n    \n    // Simple message\n    var result = slack.sendMessage('#general', 'Hello from ServiceNow!');\n    \n    if (result.success) {\n        gs.info('Message sent successfully');\n    }\n}\n\n// Example 2: Notify critical incident to Slack\nfunction notifyCriticalIncident(incidentSysId) {\n    var incGr = new GlideRecord('incident');\n    if (incGr.get(incidentSysId)) {\n        var slack = new SlackIntegration();\n        slack.notifyIncident(incGr);\n    }\n}\n\n// Example 3: Create GitHub issue from ServiceNow incident\nfunction createGitHubIssueFromIncident(incidentSysId) {\n    var incGr = new GlideRecord('incident');\n    if (incGr.get(incidentSysId)) {\n        var github = new GitHubIntegration();\n        \n        var issueData = {\n            title: incGr.getValue('number') + ': ' + incGr.getValue('short_description'),\n            body: 'ServiceNow Incident: ' + incGr.getValue('number') + '\\n\\n' +\n                  'Description: ' + incGr.getValue('description') + '\\n\\n' +\n                  'Priority: ' + incGr.getDisplayValue('priority') + '\\n' +\n                  'Link: ' + gs.getProperty('glide.servlet.uri') + 'incident.do?sys_id=' + incidentSysId,\n            labels: ['servicenow', 'incident', 'priority-' + incGr.getValue('priority')],\n            assignees: ['devops-team']\n        };\n        \n        var result = github.createIssue('myorg', 'myrepo', issueData);\n        \n        if (result.success) {\n            // Update incident with GitHub issue link\n            incGr.work_notes = 'GitHub issue created: ' + result.url;\n            incGr.update();\n        }\n    }\n}\n\n// Example 4: Sync ServiceNow incident to Jira\nfunction syncIncidentToJira(incidentSysId) {\n    var incGr = new GlideRecord('incident');\n    if (incGr.get(incidentSysId)) {\n        var jira = new JiraIntegration();\n        \n        var issueData = {\n            projectKey: 'SUPPORT',\n            summary: incGr.getValue('short_description'),\n            description: 'ServiceNow Incident: ' + incGr.getValue('number') + '\\n\\n' +\n                        incGr.getValue('description'),\n            issueType: 'Bug',\n            priority: mapPriorityToJira(incGr.getValue('priority')),\n            labels: ['servicenow', incGr.getValue('number')]\n        };\n        \n        var result = jira.createIssue(issueData);\n        \n        if (result.success) {\n            // Store Jira key in incident\n            incGr.correlation_id = result.issueKey;\n            incGr.work_notes = 'Jira issue created: ' + result.url;\n            incGr.update();\n            \n            gs.info('Incident synced to Jira: ' + result.issueKey);\n        }\n    }\n}\n\nfunction mapPriorityToJira(snPriority) {\n    var priorityMap = {\n        '1': 'Highest',\n        '2': 'High',\n        '3': 'Medium',\n        '4': 'Low',\n        '5': 'Lowest'\n    };\n    return priorityMap[snPriority] || 'Medium';\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Advanced REST API Integration with Retry Logic/RESTIntegrationHandler.js",
    "content": "/**\n * Advanced REST API Integration Handler\n * Provides retry logic, circuit breaker, rate limiting, and comprehensive error handling\n * \n * @class RESTIntegrationHandler\n * @example\n * var handler = new RESTIntegrationHandler('MyAPI');\n * var response = handler.get('/users/123');\n * if (response.success) {\n *     gs.info('User data: ' + JSON.stringify(response.data));\n * }\n */\n\nvar RESTIntegrationHandler = Class.create();\nRESTIntegrationHandler.prototype = {\n    \n    /**\n     * Initialize the REST Integration Handler\n     * @param {string} integrationName - Name of the integration (used for config lookup)\n     */\n    initialize: function(integrationName) {\n        this.integrationName = integrationName;\n        this.baseUrl = gs.getProperty('x_company.' + integrationName + '.base_url');\n        this.maxRetries = parseInt(gs.getProperty('x_company.' + integrationName + '.max_retries', '3'));\n        this.timeout = parseInt(gs.getProperty('x_company.' + integrationName + '.timeout', '30000'));\n        this.rateLimit = parseInt(gs.getProperty('x_company.' + integrationName + '.rate_limit', '100'));\n        \n        // Circuit breaker configuration\n        this.circuitBreaker = {\n            state: 'CLOSED', // CLOSED, OPEN, HALF_OPEN\n            failureCount: 0,\n            failureThreshold: 5,\n            successCount: 0,\n            successThreshold: 2,\n            lastFailureTime: null,\n            resetTimeout: 60000 // 60 seconds\n        };\n        \n        // Rate limiter (token bucket algorithm)\n        this.rateLimiter = {\n            tokens: this.rateLimit,\n            lastRefill: new Date().getTime(),\n            refillRate: this.rateLimit / 60 // per second\n        };\n        \n        // Response cache\n        this.cache = {};\n        this.cacheTimeout = 300000; // 5 minutes\n    },\n    \n    /**\n     * Make a GET request with retry logic\n     * @param {string} endpoint - API endpoint path\n     * @param {object} params - Query parameters\n     * @param {object} options - Additional options (headers, cache, etc.)\n     * @returns {object} Response object with success, data, error, statusCode\n     */\n    get: function(endpoint, params, options) {\n        options = options || {};\n        options.method = 'GET';\n        options.params = params;\n        \n        // Check cache for GET requests\n        if (options.useCache !== false) {\n            var cacheKey = this._getCacheKey(endpoint, params);\n            var cachedResponse = this._getFromCache(cacheKey);\n            if (cachedResponse) {\n                gs.debug('[RESTIntegrationHandler] Cache hit for: ' + endpoint);\n                return cachedResponse;\n            }\n        }\n        \n        var response = this._executeWithRetry(endpoint, options);\n        \n        // Cache successful GET responses\n        if (response.success && options.useCache !== false) {\n            this._addToCache(cacheKey, response);\n        }\n        \n        return response;\n    },\n    \n    /**\n     * Make a POST request with retry logic\n     * @param {string} endpoint - API endpoint path\n     * @param {object} body - Request body\n     * @param {object} options - Additional options\n     * @returns {object} Response object\n     */\n    post: function(endpoint, body, options) {\n        options = options || {};\n        options.method = 'POST';\n        options.body = body;\n        return this._executeWithRetry(endpoint, options);\n    },\n    \n    /**\n     * Make a PUT request with retry logic\n     * @param {string} endpoint - API endpoint path\n     * @param {object} body - Request body\n     * @param {object} options - Additional options\n     * @returns {object} Response object\n     */\n    put: function(endpoint, body, options) {\n        options = options || {};\n        options.method = 'PUT';\n        options.body = body;\n        return this._executeWithRetry(endpoint, options);\n    },\n    \n    /**\n     * Make a DELETE request with retry logic\n     * @param {string} endpoint - API endpoint path\n     * @param {object} options - Additional options\n     * @returns {object} Response object\n     */\n    'delete': function(endpoint, options) {\n        options = options || {};\n        options.method = 'DELETE';\n        return this._executeWithRetry(endpoint, options);\n    },\n    \n    /**\n     * Execute request with exponential backoff retry logic\n     * @private\n     */\n    _executeWithRetry: function(endpoint, options) {\n        var attempt = 0;\n        var delay = 1000; // Initial delay: 1 second\n        var lastError = null;\n        \n        // Check circuit breaker\n        if (!this._checkCircuitBreaker()) {\n            return {\n                success: false,\n                error: 'Circuit breaker is OPEN. Service unavailable.',\n                statusCode: 503,\n                circuitBreakerState: this.circuitBreaker.state\n            };\n        }\n        \n        // Check rate limit\n        if (!this._checkRateLimit()) {\n            return {\n                success: false,\n                error: 'Rate limit exceeded. Please try again later.',\n                statusCode: 429\n            };\n        }\n        \n        while (attempt <= this.maxRetries) {\n            try {\n                gs.debug('[RESTIntegrationHandler] Attempt ' + (attempt + 1) + ' for: ' + endpoint);\n                \n                var response = this._executeRequest(endpoint, options);\n                \n                // Success - reset circuit breaker\n                if (response.success) {\n                    this._recordSuccess();\n                    return response;\n                }\n                \n                // Check if error is retryable\n                if (!this._isRetryable(response.statusCode)) {\n                    this._recordFailure();\n                    return response;\n                }\n                \n                lastError = response;\n                \n            } catch (ex) {\n                lastError = {\n                    success: false,\n                    error: 'Exception: ' + ex.message,\n                    statusCode: 0\n                };\n                gs.error('[RESTIntegrationHandler] Exception on attempt ' + (attempt + 1) + ': ' + ex.message);\n            }\n            \n            attempt++;\n            \n            // Don't sleep after last attempt\n            if (attempt <= this.maxRetries) {\n                gs.debug('[RESTIntegrationHandler] Retrying in ' + delay + 'ms...');\n                gs.sleep(delay);\n                delay *= 2; // Exponential backoff\n            }\n        }\n        \n        // All retries exhausted\n        this._recordFailure();\n        lastError.retriesExhausted = true;\n        lastError.totalAttempts = attempt;\n        \n        return lastError;\n    },\n    \n    /**\n     * Execute the actual HTTP request\n     * @private\n     */\n    _executeRequest: function(endpoint, options) {\n        var url = this.baseUrl + endpoint;\n        var request = new sn_ws.RESTMessageV2();\n        \n        request.setHttpMethod(options.method);\n        request.setEndpoint(url);\n        request.setHttpTimeout(this.timeout);\n        \n        // Add query parameters for GET requests\n        if (options.params) {\n            for (var key in options.params) {\n                request.setQueryParameter(key, options.params[key]);\n            }\n        }\n        \n        // Add request body for POST/PUT\n        if (options.body) {\n            request.setRequestBody(JSON.stringify(options.body));\n        }\n        \n        // Set headers\n        request.setRequestHeader('Content-Type', 'application/json');\n        request.setRequestHeader('Accept', 'application/json');\n        \n        // Add authentication\n        this._addAuthentication(request, options);\n        \n        // Add custom headers\n        if (options.headers) {\n            for (var header in options.headers) {\n                request.setRequestHeader(header, options.headers[header]);\n            }\n        }\n        \n        // Execute request\n        var response = request.execute();\n        var statusCode = response.getStatusCode();\n        var responseBody = response.getBody();\n        \n        // Log request/response\n        this._logRequest(options.method, url, options.body, statusCode, responseBody);\n        \n        // Parse response\n        var result = {\n            success: statusCode >= 200 && statusCode < 300,\n            statusCode: statusCode,\n            headers: this._parseHeaders(response),\n            rawBody: responseBody\n        };\n        \n        // Parse JSON response\n        try {\n            if (responseBody) {\n                result.data = JSON.parse(responseBody);\n            }\n        } catch (ex) {\n            result.data = responseBody;\n        }\n        \n        // Add error message for failed requests\n        if (!result.success) {\n            result.error = this._extractErrorMessage(result.data, statusCode);\n        }\n        \n        return result;\n    },\n    \n    /**\n     * Check circuit breaker state\n     * @private\n     */\n    _checkCircuitBreaker: function() {\n        var now = new Date().getTime();\n        \n        // If circuit is OPEN, check if reset timeout has passed\n        if (this.circuitBreaker.state === 'OPEN') {\n            if (now - this.circuitBreaker.lastFailureTime > this.circuitBreaker.resetTimeout) {\n                gs.info('[RESTIntegrationHandler] Circuit breaker transitioning to HALF_OPEN');\n                this.circuitBreaker.state = 'HALF_OPEN';\n                this.circuitBreaker.successCount = 0;\n                return true;\n            }\n            return false;\n        }\n        \n        return true;\n    },\n    \n    /**\n     * Record successful request\n     * @private\n     */\n    _recordSuccess: function() {\n        if (this.circuitBreaker.state === 'HALF_OPEN') {\n            this.circuitBreaker.successCount++;\n            if (this.circuitBreaker.successCount >= this.circuitBreaker.successThreshold) {\n                gs.info('[RESTIntegrationHandler] Circuit breaker transitioning to CLOSED');\n                this.circuitBreaker.state = 'CLOSED';\n                this.circuitBreaker.failureCount = 0;\n            }\n        } else if (this.circuitBreaker.state === 'CLOSED') {\n            this.circuitBreaker.failureCount = 0;\n        }\n    },\n    \n    /**\n     * Record failed request\n     * @private\n     */\n    _recordFailure: function() {\n        this.circuitBreaker.failureCount++;\n        this.circuitBreaker.lastFailureTime = new Date().getTime();\n        \n        if (this.circuitBreaker.failureCount >= this.circuitBreaker.failureThreshold) {\n            gs.warn('[RESTIntegrationHandler] Circuit breaker transitioning to OPEN');\n            this.circuitBreaker.state = 'OPEN';\n        }\n    },\n    \n    /**\n     * Check rate limit using token bucket algorithm\n     * @private\n     */\n    _checkRateLimit: function() {\n        var now = new Date().getTime();\n        var timePassed = (now - this.rateLimiter.lastRefill) / 1000; // seconds\n        \n        // Refill tokens\n        this.rateLimiter.tokens = Math.min(\n            this.rateLimit,\n            this.rateLimiter.tokens + (timePassed * this.rateLimiter.refillRate)\n        );\n        this.rateLimiter.lastRefill = now;\n        \n        // Check if we have tokens available\n        if (this.rateLimiter.tokens >= 1) {\n            this.rateLimiter.tokens -= 1;\n            return true;\n        }\n        \n        gs.warn('[RESTIntegrationHandler] Rate limit exceeded');\n        return false;\n    },\n    \n    /**\n     * Check if HTTP status code is retryable\n     * @private\n     */\n    _isRetryable: function(statusCode) {\n        // Retry on server errors (5xx) and specific client errors\n        var retryableCodes = [408, 429, 500, 502, 503, 504];\n        return retryableCodes.indexOf(statusCode) !== -1;\n    },\n    \n    /**\n     * Add authentication to request\n     * @private\n     */\n    _addAuthentication: function(request, options) {\n        var authType = gs.getProperty('x_company.' + this.integrationName + '.auth_type', 'bearer');\n        \n        if (authType === 'bearer') {\n            var token = this._getAuthToken();\n            if (token) {\n                request.setRequestHeader('Authorization', 'Bearer ' + token);\n            }\n        } else if (authType === 'basic') {\n            var username = gs.getProperty('x_company.' + this.integrationName + '.username');\n            var password = gs.getProperty('x_company.' + this.integrationName + '.password');\n            request.setBasicAuth(username, password);\n        } else if (authType === 'apikey') {\n            var apiKey = gs.getProperty('x_company.' + this.integrationName + '.api_key');\n            var headerName = gs.getProperty('x_company.' + this.integrationName + '.api_key_header', 'X-API-Key');\n            request.setRequestHeader(headerName, apiKey);\n        }\n    },\n    \n    /**\n     * Get authentication token (with caching)\n     * @private\n     */\n    _getAuthToken: function() {\n        // Check cache\n        var cacheKey = 'auth_token_' + this.integrationName;\n        var cachedToken = this._getFromCache(cacheKey);\n        if (cachedToken) {\n            return cachedToken.token;\n        }\n        \n        // Acquire new token (implement OAuth 2.0 flow here)\n        var token = gs.getProperty('x_company.' + this.integrationName + '.access_token');\n        \n        // Cache token\n        this._addToCache(cacheKey, { token: token }, 3600000); // 1 hour\n        \n        return token;\n    },\n    \n    /**\n     * Cache management\n     * @private\n     */\n    _getCacheKey: function(endpoint, params) {\n        return endpoint + '_' + JSON.stringify(params || {});\n    },\n    \n    _getFromCache: function(key) {\n        var cached = this.cache[key];\n        if (cached && new Date().getTime() - cached.timestamp < this.cacheTimeout) {\n            return cached.data;\n        }\n        return null;\n    },\n    \n    _addToCache: function(key, data, ttl) {\n        this.cache[key] = {\n            data: data,\n            timestamp: new Date().getTime(),\n            ttl: ttl || this.cacheTimeout\n        };\n    },\n    \n    /**\n     * Parse response headers\n     * @private\n     */\n    _parseHeaders: function(response) {\n        var headers = {};\n        var headerKeys = response.getHeaders();\n        for (var i = 0; i < headerKeys.size(); i++) {\n            var key = headerKeys.get(i);\n            headers[key] = response.getHeader(key);\n        }\n        return headers;\n    },\n    \n    /**\n     * Extract error message from response\n     * @private\n     */\n    _extractErrorMessage: function(data, statusCode) {\n        if (typeof data === 'object') {\n            return data.error || data.message || data.error_description || 'HTTP ' + statusCode;\n        }\n        return 'HTTP ' + statusCode + ': ' + data;\n    },\n    \n    /**\n     * Log request/response for debugging\n     * @private\n     */\n    _logRequest: function(method, url, body, statusCode, responseBody) {\n        if (gs.getProperty('x_company.' + this.integrationName + '.debug', 'false') === 'true') {\n            gs.info('[RESTIntegrationHandler] ' + method + ' ' + url);\n            if (body) {\n                gs.debug('[RESTIntegrationHandler] Request: ' + JSON.stringify(body));\n            }\n            gs.info('[RESTIntegrationHandler] Response: ' + statusCode);\n            gs.debug('[RESTIntegrationHandler] Body: ' + responseBody);\n        }\n    },\n    \n    type: 'RESTIntegrationHandler'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Approval Rule Builder/ApprovalRuleBuilder.js",
    "content": "var ApprovalRuleBuilder = Class.create();\n\n// Valid Rulesets\nApprovalRuleBuilder.RULESET_APPROVES = \"Approves\";\nApprovalRuleBuilder.RULESET_REJECTS = \"Rejects\";\nApprovalRuleBuilder.RULESET_APPROVEREJECTS = \"ApproveReject\";\n\n// Valid Rules\nApprovalRuleBuilder.RULE_ANY = \"Any\";  // Anyone approves\nApprovalRuleBuilder.RULE_ALL = \"All\";  // All users approve\nApprovalRuleBuilder.RULE_RESPONDED = \"Res\";  // All responded and anyone approves\nApprovalRuleBuilder.RULE_PERCENT = \"%\";  // % of users approve\nApprovalRuleBuilder.RULE_NUMBER = \"#\";  // number of users approve\n\nApprovalRuleBuilder.prototype = {\n\n    /**\n     * Main ApprovalRuleBuilder class\n     * @example\n     * var query = new ApprovalRuleBuilder();\n     * @constructor\n     * @param {boolean} (optional) Enable debug output\n     */\n    initialize: function (debug) {\n        this._debug = debug | false;\n        this._approval_rules = '';\n\n        // keep track of required steps\n        this._ruleset_added = false;  // additional rulesets can be added once the current ruleset is complete (has rules and users/groups)\n        this._rule_added = false;  // rule can only be added to an open ruleset, Or/And rules can be added once users/groups have been set for current rule\n        this._users_added = false;  // users/groups can only be added to an open rule but not if manual users has been set\n        this._manual_users = false;  // manual users cannot be added to a rule if users/groups already applied\n\n        this._users = [];  // temporary store for users, allows multiple .addUsers calls\n        this._groups = [];  // temporary store for groups, allows multiple .addGroups calls\n    },\n\n    /**\n     * Starts a new ruleset\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     * @param {string} ruleset to create (APPROVES|REJECTS|APPROVEREJECTS)\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addRuleSet: function (ruleset) {\n\n        if (!this._isValidRuleSet(ruleset)) {\n            NiceError.raise('Unknown ruleset (' + ruleset + ')');\n        }\n\n        if (this._approval_rules != '' && !this._users_added) {\n            NiceError.raise('Cannot add ruleset (' + ruleset + ') as previous set not complete');\n        }\n\n        this._commitUsersAndGroups();\n\n        if (this._approval_rules != '') {\n            if (this._debug) gs.info('- [RuleSet] Or' + ruleset);\n            this._approval_rules += \"Or\";\n        } else {\n            if (this._debug) gs.info('- [RuleSet] ' + ruleset);\n        }\n\n        this._approval_rules += ruleset;\n\n        this._ruleset_added = true;\n        this._rule_added = false;\n        this._users_added = false;\n        this._manual_users = false;\n\n        return this;\n    },\n\n    /**\n     * Starts a new rule\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     *                 .addRule(ApprovalRuleBuilder.RULE_ANY)\n     * @param {string} rule to create (ANY|ALL|RES|%|#)\n     * @param {integer} number to use for percentage and number of users rule (optional)\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addRule: function (rule, value) {\n        value = value | 0;\n\n        if (!this._isValidRule(rule)) {\n            NiceError.raise('Unknown rule (' + rule + ')');\n        }\n\n        if (!this._ruleset_added) {\n            NiceError.raise('Cannot add rule (' + rule + ') as no ruleset defined.');\n        }\n\n        if (this._rule_added) {\n            NiceError.raise('Cannot add rule (' + rule + '), use addAndRule or addOrRule instead');\n        }\n\n        if (rule == ApprovalRuleBuilder.RULE_PERCENT || rule == ApprovalRuleBuilder.RULE_NUMBER) {\n            if (value > 0) {\n                this._approval_rules += value;\n            } else {\n                NiceError.raise(\"Cannot add rule (' + rule + ') as no value specified\");\n            }\n        }\n\n        if (this._debug) gs.info('-- [Rule] ' + (value > 0 ? value : '') + rule);\n\n        this._approval_rules += rule;\n\n        this._rule_added = true;\n        this._users_added = false;\n        this._manual_users = false;\n        return this;\n    },\n\n    /**\n     * Adds users to a rule\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     *                 .addRule(ApprovalRuleBuilder.RULE_ANY)\n     *                 .addUsers(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])\n     * @param {array} sys_id's of users to add\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addUsers: function (user_sys_id_list) {\n        if (this._rule_added) {\n            if (!this._manual_users) {\n                if (this._debug) gs.info('--- [Users] (temporary) ' + user_sys_id_list.join(','));\n                var au = new ArrayUtil();\n                this._users = au.union(this._users, user_sys_id_list);\n                this._users_added = this._users_added || this._users.length > 0;\n            } else {\n                NiceError.raise('Cannot add groups as manual users have already been added.');\n            }\n        } else {\n            NiceError.raise('Cannot add users as no rule in progress');\n        }\n        return this;\n    },\n\n    /**\n     * Adds groups to a rule\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     *                 .addRule(ApprovalRuleBuilder.RULE_ANY)\n     *                 .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])\n     * @param {array} sys_id's of groups to add\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addGroups: function (group_sys_id_list) {\n        if (this._rule_added) {\n            if (!this._manual_users) {\n                if (this._debug) gs.info('--- [Groups] (temporary)' + group_sys_id_list.join(','));\n                var au = new ArrayUtil();\n                this._groups = au.union(this._groups, group_sys_id_list);\n                this._users_added = this._users_added || this._groups.length > 0;\n\n            } else {\n                NiceError.raise('Cannot add groups as manual users have already been added.');\n            }\n        } else {\n            NiceError.raise('Cannot add groups as no rule in progress');\n        }\n        return this;\n    },\n\n    /**\n     * Adds manual users to a rule\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     *                 .addRule(ApprovalRuleBuilder.RULE_ANY)\n     *                 .addManualUsers()\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addManualUsers: function () {\n        if (this._rule_added) {\n            if (this._debug) gs.info('--- [Manual Users]');\n            if (!this._users_added) {\n                this._approval_rules += 'M';\n                this._users_added = true;\n                this._manual_users = true;\n            } else {\n                NiceError.raise('Cannot add manual users as users/groups have already been added.');\n            }\n        } else {\n            NiceError.raise('Cannot add manual users as no rule in progress');\n        }\n        return this;\n    },\n\n    /**\n     * Adds an Or rule to a ruleset\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     *                 .addRule(ApprovalRuleBuilder.RULE_ANY)\n     *                 .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])\n     *                 .addOrRule(ApprovalRuleBuilder.RULE_RESPONDED)\n     *                 .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])\n     * @param {string} rule to create (ANY|ALL|RES|%|#)\n     * @param {integer} number to use for percentage and number of users rule (optional)\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addOrRule: function (rule, value) {\n        if (this._rule_added && this._users_added) {\n            this._commitUsersAndGroups();\n            this._rule_added = false;\n            this._approval_rules += '|';\n            if (this._debug) gs.info('-- [Or]');\n            return this.addRule(rule, value);\n        } else {\n            NiceError.raise('Cannot add Or rule as previous rule not complete');\n        }\n        return this;\n    },\n\n    /**\n     * Adds an And rule to a ruleset\n     * @example\n     * var rules = new ApprovalRuleBuilder()\n     *                 .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n     *                 .addRule(ApprovalRuleBuilder.RULE_ANY)\n     *                 .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])\n     *                 .addAndRule(ApprovalRuleBuilder.RULE_RESPONDED)\n     *                 .addGroups(['a8f98bb0eb32010045e1a5115206fe3a','a2826bf03710200044e0bfc8bcbe5ded'])\n     * @param {string} rule to create (ANY|ALL|RES|%|#)\n     * @param {integer} number to use for percentage and number of users rule (optional)\n     * @returns {ApprovalRuleBuilder} New ApprovalRuleBuilder containing approval rules\n     */\n    addAndRule: function (rule, value) {\n        if (this._rule_added && this._users_added) {\n            this._commitUsersAndGroups();\n            this._rule_added = false;\n            this._approval_rules += '&';\n            if (this._debug) gs.info('-- [And]');\n            return this.addRule(rule, value);\n        } else {\n            NiceError.raise('Cannot add And rule as previous rule not complete');\n        }\n        return this;\n    },\n\n    /**\n     * Returns the built approval rule\n     * @example\n     * ApprovesAllU[a8f98bb0eb32010045e1a5115206fe3a,a2826bf03710200044e0bfc8bcbe5ded]G[b85d44954a3623120004689b2d5dd60a,287ee6fea9fe198100ada7950d0b1b73] \n     *     |10%G[db53580b0a0a0a6501aa37c294a2ba6b,74ad1ff3c611227d01d25feac2af603f]\n     * @returns {string} encoded rule string for use in Flow Designer\n     */\n    getApprovalRules: function () {\n        this._commitUsersAndGroups();\n        return this._approval_rules;\n    },\n\n    /*\n     * Internal methods\n     */\n\n    _isValidRuleSet: function (ruleset) {\n        return (ruleset == ApprovalRuleBuilder.RULESET_APPROVES ||\n            ruleset == ApprovalRuleBuilder.RULESET_REJECTS ||\n            ruleset == ApprovalRuleBuilder.RULESET_APPROVEREJECT);\n    },\n\n    _isValidRule: function (rule) {\n        return (rule == ApprovalRuleBuilder.RULE_ANY ||\n            rule == ApprovalRuleBuilder.RULE_ALL ||\n            rule == ApprovalRuleBuilder.RULE_RESPONDED ||\n            rule == ApprovalRuleBuilder.RULE_PERCENT ||\n            rule == ApprovalRuleBuilder.RULE_NUMBER);\n    },\n\n    _commitUsersAndGroups: function () {\n        if (this._users.length > 0) {\n            this._approval_rules += 'U[' + this._users.join(',') + ']';\n            if (this._debug) gs.info('--- [Users] ' + this._users.join(','));\n            this._users = [];\n        }\n\n        if (this._groups.length > 0) {\n            if (this._debug) gs.info('--- [Groups] ' + this._groups.join(','));\n            this._approval_rules += 'G[' + this._groups.join(',') + ']';\n            this._groups = [];\n        }\n    },\n\n    type: 'ApprovalRuleBuilder'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Approval Rule Builder/README.md",
    "content": "# Approval Rule Builder for Flow Designer\n\n*This script was originally posted on the Share site but thought it useful to post here as well.*\n\n## Overview\nThis project includes a script include \"ApprovalRuleBuilder\" which can be used to script, via the f(x) icon, an \"Ask for approval\" action within Flow Designer.\n\nThe \"Ask for approval\" provides a condition builder like interface for selecting the approve and reject rules and allows for multiple combinations of user and group approvals. The selections can be dynamic using the data pill picker.\n\nThis project provides the ability to script the approvals/rejections should the inbound data driving the action not be predictable. One use case is a data-driven catalog workflow which could have many approval rules and levels which may be hard to implement in the interface. The project was also a way for me to understand the data driving the approval action and a coding challenge to replicate it.\n\n## Example\n- Approval - Anyone approves - (User) Abraham Lincoln, (Group) Application Development\n- Rejects - Anyone rejects - (User) Abraham Lincoln, (Group) Application Development\n\nThe resulting data used by Flow Designer looks like the following;\n```\nApprovesAnyU[a8f98bb0eb32010045e1a5115206fe3a]G[0a52d3dcd7011200f2d224837e6103f2]OrRejectsAnyU[a8f98bb0eb32010045e1a5115206fe3a]G[0a52d3dcd7011200f2d224837e6103f2]\n```\n\nThe code to replicate this using 'ApprovalRuleBuilder' is as follows;\n\n```javascript\nvar approval_rules = new ApprovalRuleBuilder()\n    .addRuleSet(ApprovalRuleBuilder.RULESET_APPROVES)\n    .addRule(ApprovalRuleBuilder.RULE_ANY)\n    .addUsers(['a8f98bb0eb32010045e1a5115206fe3a'])\n    .addGroups(['0a52d3dcd7011200f2d224837e6103f2'])\n    .addRuleSet(ApprovalRuleBuilder.RULESET_REJECTS)\n    .addRule(ApprovalRuleBuilder.RULE_ANY)\n    .addUsers(['a8f98bb0eb32010045e1a5115206fe3a'])\n    .addGroups(['0a52d3dcd7011200f2d224837e6103f2'])\n    .getApprovalRules();\n```    \nEach method call is chained together to build the rules, and the final 'getApprovalRules()' call will return a string containing the data required by Flow Designer.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Array prototypes/ArrayPrototypes.js",
    "content": "(function() {\n\n    function DefaultEqualityComparer(a, b) {\n        return a === b || a.valueOf() === b.valueOf();\n    };\n\n    function DefaultSortComparer(a, b) {\n        if (a === b) return 0;\n        if (a == null) return -1;\n        if (b == null) return 1;\n        if (typeof a == \"string\") return a.toString().localeCompare(b.toString());\n        return a.valueOf() - b.valueOf();\n    };\n\n    function DefaultPredicate() {\n        return true;\n    };\n\n    function DefaultSelector(t) {\n        return t;\n    };\n\n\n    Array.prototype.select = Array.prototype.map || function(selector, context) {\n        context = context || this;\n        var arr = [];\n        var l = this.length;\n        for (var i = 0; i < l; i++)\n            arr.push(selector.call(context, this[i], i, this));\n        return arr;\n    };\n\n    Array.prototype.take = function(c) {\n        return this.slice(0, c);\n    };\n\n    Array.prototype.skip = function(c) {\n        return this.slice(c);\n    };\n\n    Array.prototype.first = function(predicate, def) {\n        var l = this.length;\n        if (!predicate) return l ? this[0] : def == null ? null : def;\n        for (var i = 0; i < l; i++)\n            if (predicate(this[i], i, this))\n                return this[i];\n        return def == null ? null : def;\n    };\n\n    Array.prototype.last = function(predicate, def) {\n        var l = this.length;\n        if (!predicate) return l ? this[l - 1] : def == null ? null : def;\n        while (l-- > 0)\n            if (predicate(this[l], l, this))\n                return this[l];\n        return def == null ? null : def;\n    };\n\n    Array.prototype.union = function(arr) {\n        return this.concat(arr).distinct();\n    };\n\n    Array.prototype.where = Array.prototype.filter || function(predicate, context) {\n        context = context || window;\n        var arr = [];\n        var l = this.length;\n        for (var i = 0; i < l; i++)\n            if (predicate.call(context, this[i], i, this) === true) arr.push(this[i]);\n        return arr;\n    };\n\n    Array.prototype.contains = function(o, comparer) {\n        comparer = comparer || DefaultEqualityComparer;\n        var l = this.length;\n        while (l-- > 0)\n            if (comparer(this[l], o) === true) return true;\n        return false;\n    };\n\n    Array.prototype.distinct = function(comparer) {\n        var arr = [];\n        var l = this.length;\n        for (var i = 0; i < l; i++) {\n            if (!arr.contains(this[i], comparer))\n                arr.push(this[i]);\n        }\n        return arr;\n    };\n\n    Array.prototype.intersect = function(arr, comparer) {\n        comparer = comparer || DefaultEqualityComparer;\n        return this.distinct(comparer).where(function(t) {\n            return arr.contains(t, comparer);\n        });\n    };\n\n    Array.prototype.except = function(arr, comparer) {\n        if (!(arr instanceof Array)) arr = [arr];\n        comparer = comparer || DefaultEqualityComparer;\n        var l = this.length;\n        var res = [];\n        for (var i = 0; i < l; i++) {\n            var k = arr.length;\n            var t = false;\n            while (k-- > 0) {\n                if (comparer(this[i], arr[k]) === true) {\n                    t = true;\n                    break;\n                }\n            }\n            if (!t) res.push(this[i]);\n        }\n        return res;\n    };\n\n    Array.prototype.indexOf = Array.prototype.indexOf || function(o, index) {\n        var l = this.length;\n        for (var i = Math.max(Math.min(index, l), 0) || 0; i < l; i++)\n            if (this[i] === o) return i;\n        return -1;\n    };\n\n\n    Array.prototype.remove = function(item) {\n        var i = this.indexOf(item);\n        if (i != -1)\n            this.splice(i, 1);\n    };\n\n    Array.prototype.removeAll = function(predicate) {\n        var item;\n        var i = 0;\n        while (item = this.first(predicate)) {\n            i++;\n            this.remove(item);\n        }\n        return i;\n    };\n\n    Array.prototype.orderBy = function(selector, comparer) {\n        comparer = comparer || DefaultSortComparer;\n        var arr = this.slice(0);\n        var fn = function(a, b) {\n            return comparer(selector(a), selector(b));\n        };\n\n        arr.thenBy = function(selector, comparer) {\n            comparer = comparer || DefaultSortComparer;\n            return arr.orderBy(DefaultSelector, function(a, b) {\n                var res = fn(a, b);\n                return res === 0 ? comparer(selector(a), selector(b)) : res;\n            });\n        };\n\n        arr.thenByDescending = function(selector, comparer) {\n            comparer = comparer || DefaultSortComparer;\n            return arr.orderBy(DefaultSelector, function(a, b) {\n                var res = fn(a, b);\n                return res === 0 ? -comparer(selector(a), selector(b)) : res;\n            });\n        };\n\n        return arr.sort(fn);\n    };\n\n\n    Array.prototype.orderByDescending = function(selector, comparer) {\n        comparer = comparer || DefaultSortComparer;\n        return this.orderBy(selector, function(a, b) {\n            return -comparer(a, b)\n        });\n    };\n\n\n    Array.prototype.innerJoin = function(arr, outer, inner, result, comparer) {\n        comparer = comparer || DefaultEqualityComparer;\n        var res = [];\n\n        this.forEach(function(t) {\n            arr.where(function(u) {\n                    return comparer(outer(t), inner(u));\n                })\n                .forEach(function(u) {\n                    res.push(result(t, u));\n                });\n        });\n\n        return res;\n    };\n\n\n\n    Array.prototype.groupBy = function(selector, comparer) {\n        var grp = [];\n        var l = this.length;\n        comparer = comparer || DefaultEqualityComparer;\n        selector = selector || DefaultSelector;\n\n        for (var i = 0; i < l; i++) {\n            var k = selector(this[i]);\n            var g = grp.first(function(u) {\n                return comparer(u.key, k);\n            });\n\n            if (!g) {\n                g = [];\n                g.key = k;\n                grp.push(g);\n            }\n\n            g.push(this[i]);\n        }\n        return grp;\n    };\n\n    Array.prototype.toDictionary = function(keySelector, valueSelector) {\n        var o = {};\n        var l = this.length;\n        while (l-- > 0) {\n            var key = keySelector(this[l]);\n            if (key == null || key == \"\") continue;\n            o[key] = valueSelector(this[l]);\n        }\n        return o;\n    };\n\n    Array.prototype.min = function(s) {\n        s = s || DefaultSelector;\n        var l = this.length;\n        var min = s(this[0]);\n        while (l-- > 0)\n            if (s(this[l]) < min) min = s(this[l]);\n        return min;\n    };\n\n    Array.prototype.max = function(s) {\n        s = s || DefaultSelector;\n        var l = this.length;\n        var max = s(this[0]);\n        while (l-- > 0)\n            if (s(this[l]) > max) max = s(this[l]);\n        return max;\n    };\n\n    Array.prototype.sum = function(s) {\n        s = s || DefaultSelector;\n        var l = this.length;\n        var sum = 0;\n        while (l-- > 0) sum += s(this[l]);\n        return sum;\n    };\n\n\n    Array.prototype.any = function(predicate, context) {\n        ;\n        var f = this.some || function(p, c) {\n            var l = this.length;\n            if (!p) return l > 0;\n            while (l-- > 0)\n                if (p.call(c, this[l], l, this) === true) return true;\n            return false;\n        };\n        return f.apply(this, [predicate, context]);\n    };\n\n    Array.prototype.all = function(predicate, context) {\n        context = context || window;\n        predicate = predicate || DefaultPredicate;\n        var f = this.every || function(p, c) {\n            return this.length == this.where(p, c).length;\n        };\n        return f.apply(this, [predicate, context]);\n    };\n\n\n    Array.prototype.takeWhile = function(predicate) {\n        predicate = predicate || DefaultPredicate;\n        var l = this.length;\n        var arr = [];\n        for (var i = 0; i < l && predicate(this[i], i) === true; i++)\n            arr.push(this[i]);\n\n        return arr;\n    };\n\n    Array.prototype.skipWhile = function(predicate) {\n        predicate = predicate || DefaultPredicate;\n        var l = this.length;\n        var i = 0;\n        for (i = 0; i < l; i++)\n            if (predicate(this[i], i) === false) break;\n\n        return this.skip(i);\n    };\n\n    Array.prototype.defaultIfEmpty = function(val) {\n        return this.length == 0 ? [val == null ? null : val] : this;\n    };\n\n})();"
  },
  {
    "path": "Server-Side Components/Script Includes/Array prototypes/README.md",
    "content": "# Add many helpful helper functions to array object\n\n# How to use it?\nCreate a new Script Include\nCopy and Paste the content of the JavaScript file here\nInclude the Script Include in your code: gs.include(\"VF_ArrayPrototypes\");\nEnjoy the extra utility functions\n\n\n# Example 1: Joining two arrays\n\n```\n\nvar countries = [\n    { name: \"USA\", population: 300 },\n    { name: \"Canada\", population: 200 },\n    { name: \"France\", population: 100 }\n];\n\nvar people = [\n    { name: \"John\", country: \"USA\" },   \n    { name: \"Peter\", country: \"France\" },\n    { name: \"Anna\", country: \"France\" }\n];\n\nvar res1 = countries.innerJoin(people,\n    function (c) { return c.name },                                         // arr1 selector\n    function (u) { return u.country },                                      // arr2 selector\n    function (t, u) { return { country: t.name, person: u.name }});         // result selector\n\ngs.log(JSON.stringify(res1));\n\n```\n\n# Example 2: Other helpful functions\n\n```\n\nvar arr = [1, 2, 3, 4, 5];\nvar arr2 = [2, 4, 6, 8, 10, 12, 14, 16, 18, 20];\n\nvar first = arr.first(function(i){return i > 3;});\ngs.log(first);\n\n\nvar last = arr.last();\ngs.log(last); \n\n\nvar dist = arr.distinct();\ngs.log(dist);\n\n\nvar interSect = arr.intersect(arr2);\ngs.log(interSect);\n\n\nvar except = arr.except(arr2);\ngs.log(except);\n\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/ArrayUtil/README.md",
    "content": "ArrayUtil API is a script include with useful functions for working with JavaScript arrays.\nThe example shared helps removes duplicate items from an array using the 'unique' method\n"
  },
  {
    "path": "Server-Side Components/Script Includes/ArrayUtil/script.js",
    "content": "//Example Usage of the Script Include ArrayUtil  \nvar obj=[];\t\n\tvar arrayUtil = new ArrayUtil();\n\tvar gr= new GlideRecord('incident');\t\n\tgr.addOrderBy('category');\n\tgr.query();\n\twhile(gr.next()){\t\t\t\n\t\tobj.push(gr.getValue('category'));\n\t}\n\tobj = arrayUtil.unique(obj);\n\tgs.info(obj);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Assign role for a day Util/AssignRoleToUserForADay.js",
    "content": "function assignRoleToUserForADay(userSysId, roleSysId) {\n    try {\n        var user = getUserById(userSysId);\n        var role = getRoleById(roleSysId);\n        var userRoleSysId = assignRoleToUser(user.sys_id, role.sys_id);\n        scheduleRoleRemoval(userRoleSysId, 1); // Schedule for 1 day later\n    } catch (error) {\n        gs.error(error.message);\n    }\n}\n\nfunction getUserById(userSysId) {\n    var user = new GlideRecord('sys_user');\n    if (!user.get(userSysId)) {\n        throw new Error('User not found: ' + userSysId);\n    }\n    return user;\n}\n\nfunction getRoleById(roleSysId) {\n    var role = new GlideRecord('sys_user_role');\n    if (!role.get(roleSysId)) {\n        throw new Error('Role not found: ' + roleSysId);\n    }\n    return role;\n}\n\nfunction assignRoleToUser(userSysId, roleSysId) {\n    var userRole = new GlideRecord('sys_user_has_role');\n    userRole.initialize();\n    userRole.user = userSysId;\n    userRole.role = roleSysId;\n\n    var userRoleSysId = userRole.insert();\n    if (!userRoleSysId) {\n        throw new Error('Failed to assign role to user');\n    }\n    return userRoleSysId;\n}\n\nfunction scheduleRoleRemoval(userRoleSysId, days) {\n    var job = new GlideRecord('sys_trigger');\n    job.initialize();\n    job.name = 'Remove user role after ' + days + ' days';\n    job.script = 'var userRole = new GlideRecord(\"sys_user_has_role\"); userRole.get(\"' + userRoleSysId + '\"); userRole.deleteRecord();';\n    job.next_action = new GlideDateTime();\n    job.next_action.addDaysUTC(days);\n    job.insert();\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Assign role for a day Util/README.md",
    "content": "# Utility: AssignRoleToUserForADay.js\n\n## Overview\n`AssignRoleToUserForADay.js` is a utility script designed to temporarily assign a role to a user for a single day. This can be useful for granting temporary permissions or access within an application.\n\n## Features\n- Assign a role to a user for 24 hours.\n- Automatically revoke the role after the time period expires.\n- Log actions for auditing purposes.\n\n## Usage\n1. **Import the script**:\n    ```javascript\n    var assignRoleToUserForADay = require('./Utility/AssignRoleToUserForADay');\n    ```\n\n2. **Call the function**:\n    ```javascript\n    assignRoleToUserForADay(userId, roleId)\n        .then(() => {\n            console.log('Role assigned successfully.');\n        })\n        .catch((error) => {\n            console.error('Error assigning role:', error);\n        });\n    ```\n\n## Parameters\n- `userId` (String): The ID of the user to whom the role will be assigned.\n- `roleId` (String): The ID of the role to be assigned.\n\n## Example\n```javascript\nvar assignRoleToUserForADay = require('./Utility/AssignRoleToUserForADay');\n\nvar userId = '12345';\nvar roleId = 'admin';\n\nassignRoleToUserForADay(userId, roleId)\n    .then(() => {\n        console.log('Role assigned successfully.');\n    })\n    .catch((error) => {\n        console.error('Error assigning role:', error);\n    });\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Auto Execute Import Set on File Attachment/CreateImportSetAndRunTransform.js",
    "content": "var createImportSet = Class.create();\ncreateImportSet.prototype = {\n    initialize: function() {\n    },\n\n  //The below function can be called from an Action/Business Rule or a Script Action\n\tloadImportSet: function(dataSourceID) {  \n        // Get Datasource Record  \n        var dataSource = new GlideRecord(\"sys_data_source\");  \n        dataSource.get(dataSourceID);  \n          \n        // Process data source file  \n        var loader = new GlideImportSetLoader();  \n        var importSetRec = loader.getImportSetGr(dataSource);  \n        var ranload = loader.loadImportSetTable(importSetRec, dataSource);  \n        importSetRec.state = \"loaded\";  \n        importSetRec.update();  \n          \n        // Transform import set  \n        this._doTransform(importSetRec); \n    },  \n      \n    _doTransform: function(set){\n        var importSetRun = new GlideImportSetRun(set.getUniqueValue());\n        var importLog = new GlideImportLog(importSetRun, set.data_source.name);\n        var ist = new GlideImportSetTransformer();\n\n        ist.setLogger(importLog);\n        ist.setImportSetRun(importSetRun);\n        ist.transformAllMaps(set);\n     },\n    type: 'createImportSet'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Auto Execute Import Set on File Attachment/CreateSysTrigger.js",
    "content": "(function execute(inputs, outputs) {\n// ... code ...\n  var schRec = new GlideRecord(\"sys_trigger\");  \n        schRec.name = \"Load Data Source: \" + inputs.dataSourceID;  \n        schRec.trigger_type = 0; // Run Once  \n        schRec.script = \"new global.LoadIncidents().loadImportSet('\" + inputs.dataSourceID + \"')\";  \n          \n        var nextAction = new GlideDateTime();  \n        nextAction.addSeconds(30); \n        schRec.next_action = nextAction;  \n        schRec.insert();  \n  \n  \n})(inputs, outputs);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Auto Execute Import Set on File Attachment/README.md",
    "content": "# Auto Execute Import Set on File Attachment\nYou have to create a data source and a transform map first. After that you can use Flow Designer with the trigger condition Attachment is added to the data source. Once the flow triggers, it will call the action which in turn will trigger the Script Include to create an Import Set and Transform Map.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Autopopulate caller location in short description/README.md",
    "content": "GlideAjax: This is a ServiceNow-specific class used to make asynchronous calls to server-side scripts (Script Includes).\nScript Include: 'getCallerLocation' is the name of the Script Include being called.\nParameters:\n'sysparm_name': The name of the function to be called in the Script Include ('getLocation').\n'sysparm_user': The user parameter being passed to the function, which is the new value of the control (newValue).\ngetXML: This method sends the request to the server and specifies a callback function (setLocation) to handle the response.\nFunction: setLocation\nThis function processes the response from the server.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Autopopulate caller location in short description/getCallerLocation.js",
    "content": "//The client script related to this script include is added under Glide Ajax folder named updateCallerLocationinShortDesc\nvar getCallerLocation = Class.create();\ngetCallerLocation.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    getLocation: function() {\n        var userId = this.getParameter('sysparm_user');\n        var gr = new GlideRecord('sys_user');  //user table\n        gr.addQuery('sys_id', userId); //Adding query for sys ID of user's ID\n        gr.query();\n        if (gr.next()) {\n            var a = gr.location.name; //Fetching the current record location\n            return a;\n        }\n    },\n    type: 'getCallerLocation'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Autopopulate caller location in short description/updateCallerLocationinShortDesc.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    var ga = new GlideAjax('getCallerLocation'); //Script include function call\n    ga.addParam('sysparm_name', 'getLocation');\n    ga.addParam('sysparm_user', newValue);\n    ga.getXML(setLocation);\n}\n\nfunction setLocation(response) {\n\tvar answer = response.responseXML.documentElement.getAttribute(\"answer\");\n    g_form.setValue('short_description',answer);\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/BackfillAssignmentGroup/BackfillAssignmentGroup.js",
    "content": "var BackfillAssignmentGroup = Class.create();\nBackfillAssignmentGroup.prototype = {\n  initialize: function () {},\n\n  BackfillAssignmentGroup: function () {\n    var groups = \" \";\n    var assignee = current.assigned_to;\n\n    //return all relevant groups (active, itil) if the assigned_to value is empty\n\n    if (!assignee) {\n      var allGrp = new GlideRecord(\"sys_user_group\");\n      allGrp.addActiveQuery();\n      allGrp.addEncodedQuery(\"typeLIKE1cb8ab9bff500200158bffffffffff62\"); //group is constrained to 'itil' type. You can change this to meet your requirements\n      allGrp.query();\n      while (allGrp.next()) {\n        //build a comma separated string of groups if there is more than one\n        if (groups.length > 0) {\n          groups += \",\" + rgrp.sys_id;\n        } else {\n          groups = rgrp.sys_id;\n        }\n      }\n      return \"sys_idIN\" + groups;\n    }\n    //sys_user_grmember has the user to group relationship\n    var userGrp = new GlideRecord(\"sys_user_grmember\");\n    userGrp.addQuery(\"user\", assignee);\n    userGrp.addEncodedQuery(\"group.typeLIKE1cb8ab9bff500200158bffffffffff62\"); //group is constrained to 'itil' type. You can change this to meet your requirements\n    userGrp.query();\n    while (userGrp.next()) {\n      if (groups.length > 0) {\n        //build a comma separated string of groups if there is more than one\n        groups += \",\" + userGrp.group;\n      } else {\n        groups = userGrp.group;\n      }\n    }\n    // return Groups where assigned to is in those groups we use IN for lists\n    return \"sys_idIN\" + groups;\n  },\n  type: \"BackfillAssignmentGroup\",\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/BackfillAssignmentGroup/README.md",
    "content": "# BackFillAssignmentGroup\n\nThis script include is an advanced reference qualifier for the Assignment group field. It restricts the Assignment group choices to only relevant groups of which the current Assigned to user is a member.\n\n## Current Configuration\n\nThe script include filters for groups with the OOTB **type** of \"itil\" assigned to them (generally considered fulfiller groups). As such, it's ideal for use on the base Task and its extended tables (Incident, Problem, Change). Note that the **type** field is a list and the values should be queried by sys_id. It would be simple to modify the script to constrain the field to different roles or other attribute.\n\n## Use\n\n1. Create a script include called **BackfillAssignmentGroup**.\n2. Set **Accessible from** to **This application scope only**.\n3. Add description **Links assignment group field to assigned to field so that only the groups of which the assigned to is a member will be displayed for selection.** or write your own.\n4. Paste the entire contents of the script file into the **Script** field.\n5. Save.\n\nConfigure the dictionary entry of the assignment_group field on Task table.\n\n1. Type **task.list** into the navigator and press **Enter**.\n2. Right-click any column name and select **Configure>Table**.\n3. Select **Advanced View** under **Related Links** if not already.\n4. Under the **Reference Specification** tab, set **Use reference qualifier** to **Advanced**.\n5. In the **Reference qual** field, enter: **javascript:new BackfillAssignmentGroup().BackfillAssignmentGroup()**\n6. Save\n"
  },
  {
    "path": "Server-Side Components/Script Includes/BenchmarkRunner/BenchmarkRunner.js",
    "content": "var BenchmarkRunner = Class.create();\nBenchmarkRunner.prototype = {\n\tinitialize : function () {\n\t\tthis.timers = {};\n\t},\n\t\n\t/**\n\t * Execute two functions with the specified arguments, and get info about which was faster\n\t *  (and by how much).\n\t *\n\t * @param {function} methodOne - The first method to test, for comparison with the second.\n\t * @param {function} methodTwo - The second method to test, for comparison with the first.\n\t * @param {number} iterations - The number of times to run each function. Very quick functions,\n\t *  you might want to run hundreds or even thousands of times.\n\t * This is to make sure that the average duration is meaningful by eliminating any random\n\t *  variations in performance.\n\t * @param {Array} [args=[]] - An optional array of arguments to pass into your test functions.\n\t * The same array will be passed to both test function. You can have your function accept an\n\t *  argument called args, and access its elements in your test function like so:\n\t * var grSomeRecord = args[0];\n\t * @param {boolean} [runToCache=true] - If runToCache is set to true (or unspecified), run each\n\t *  function one time BEFORE beginning the timer, in order to ensure that any cachable data is\n\t *  cached.\n\t * There are times when you may not want to do this, such as if it's the \"first-run\" time of\n\t * each function that you want to compare.\n\t * @return {{method_two : {duration_ms : number, ms_per_iteration : number, begin_ms : number,\n\t *     end_ms : number, duration_sec : number}, comparison : {iteration_faster_by_ms : number,\n\t *     which_was_faster : number, slower_multiple : number, total_faster_by_ms : number},\n\t *     method_one : {duration_ms : number, ms_per_iteration : number, begin_ms : number, end_ms\n\t *     : number, duration_sec : number}}}\n\t *\n\t * method_one/method_two:\n\t *\n\t *  begin_ms: The current epoch time (in ms) at which the test of that function began.\n\t *\n\t *  end_ms: The current epoch time (in ms) at which the test of that function ended.\n\t *\n\t *  duration_ms: The total number of milliseconds it took to run the passed function\n\t *   the specified number of times.\n\t *\n\t *  duration_sec: The total number of seconds it took to run the passed function the\n\t *   specified number of times.\n\t *\n\t *  ms_per_iteration: The average time (in ms) it took to run the function one time.\n\t *\n\t * comparison:\n\t *\n\t *  which_was_faster: Will be integer 0, 1, or 2. If 1, the first passed function\n\t *   was faster. If 1, the function in the first argument was faster; if 2, the\n\t *   second was faster. If this is set to zero, then either something went wrong,\n\t *   or both functions took EXACTLY the same amount of time. This would be... rare,\n\t *   even when passing the exact same function in twice, just due to random variations.\n\t *\n\t *   iteration_faster_by_ms: The number of milliseconds by which the faster method was\n\t *    faster. This may well be set to zero or near zero if the difference per-iteration\n\t *    is relatively negligible, but that doesn't mean that there is literally no\n\t *    difference between the two. For example, if the specific operation you're testing\n\t *    is very fast, the faster method may be faster by only 0.1ms, but if it's a\n\t *    commonly used function, you may end up doing it tens of thousands of times per day.\n\t *\n\t *  total_faster_by_ms: The TOTAL number of milliseconds by which the faster method was\n\t *   faster, compounded across all iterations. For example, if the second method was\n\t *   faster by 2ms per iteration and you specified 100 iterations, then which_was_faster\n\t *   would be set to 2, iteration_faster_by_ms would be set to 2, and total_faster_by_ms\n\t *   would be set to 200.\n\t */\n\tcompareFunctions : function (methodOne, methodTwo, iterations, args, runToCache) {\n\t\tvar benchmarkData = {\n\t\t\t'method_one' : {\n\t\t\t\t'begin_ms' : 0,\n\t\t\t\t'end_ms' : 0,\n\t\t\t\t'duration_ms' : 0,\n\t\t\t\t'duration_sec' : 0,\n\t\t\t\t'ms_per_iteration' : 0\n\t\t\t},\n\t\t\t'method_two' : {\n\t\t\t\t'begin_ms' : 0,\n\t\t\t\t'end_ms' : 0,\n\t\t\t\t'duration_ms' : 0,\n\t\t\t\t'duration_sec' : 0,\n\t\t\t\t'ms_per_iteration' : 0\n\t\t\t},\n\t\t\t'comparison' : {\n\t\t\t\t'which_was_faster' : 0,\n\t\t\t\t'iteration_faster_by_ms' : 0,\n\t\t\t\t'total_faster_by_ms' : 0,\n\t\t\t\t'slower_multiple' : 0\n\t\t\t}\n\t\t};\n\t\t\n\t\t//Set default args value if not set\n\t\targs = (typeof args == 'undefined') ? [] : args;\n\t\trunToCache = (typeof runToCache == 'undefined') ? true : runToCache;\n\t\t\n\t\tbenchmarkData.method_one = this.benchmarkSingleFunction(\n\t\t\tmethodOne,\n\t\t\titerations,\n\t\t\targs,\n\t\t\trunToCache\n\t\t);\n\t\tbenchmarkData.method_two = this.benchmarkSingleFunction(\n\t\t\tmethodTwo,\n\t\t\titerations,\n\t\t\targs,\n\t\t\trunToCache\n\t\t);\n\t\t\n\t\tbenchmarkData.comparison.which_was_faster = this._calculateWhichWasFaster(\n\t\t\tbenchmarkData\n\t\t);\n\t\tbenchmarkData.comparison.total_faster_by_ms = Math.abs(\n\t\t\tbenchmarkData.method_one.duration_ms - benchmarkData.method_two.duration_ms\n\t\t);\n\t\tbenchmarkData.comparison.iteration_faster_by_ms = (\n\t\t\tbenchmarkData.comparison.total_faster_by_ms / iterations\n\t\t);\n\t\tbenchmarkData.comparison.slower_multiple = this._calculateSlowerMultiple(benchmarkData);\n\t\t\n\t\treturn benchmarkData;\n\t},\n\t\n\tbenchmarkSingleFunction : function (functionToBenchmark, iterations, args) {\n\t\tvar i;\n\t\tvar benchmarkData = {\n\t\t\t'begin_ms' : 0,\n\t\t\t'end_ms' : 0,\n\t\t\t'duration_ms' : 0,\n\t\t\t'duration_sec' : 0,\n\t\t\t'ms_per_iteration' : 0\n\t\t};\n\t\t\n\t\t//Set default args value if not set\n\t\targs = (typeof args == 'undefined') ? [] : args;\n\t\t//runToCache = (typeof runToCache == 'undefined') ? true : runToCache;\n\t\t\n\t\t/* SETUP\n\t\t* If runToCache is set to true (or unspecified), run the method one time BEFORE\n\t\t*  beginning the timer, in order to ensure that any cachable data is cached.\n\t\t* There are times when you may not want to do this, such as if it's the \"first-run\"\n\t\t*  time of each function that you want to compare.\n\t\t* */\n\t\tfunctionToBenchmark(args);\n\t\t\n\t\t//Set begin_ms to begin benchmark\n\t\tbenchmarkData.begin_ms = new GlideDateTime().getNumericValue();\n\t\t\n\t\t//Run the passed function however many times indicated in the iterations arg.\n\t\tfor (i = 0; i < iterations; i++) {\n\t\t\tfunctionToBenchmark(args);\n\t\t}\n\t\t\n\t\t//Set end_ms to end benchmark\n\t\tbenchmarkData.end_ms = new GlideDateTime().getNumericValue();\n\t\t\n\t\t//Calculate durations\n\t\tbenchmarkData.duration_ms = benchmarkData.end_ms - benchmarkData.begin_ms;\n\t\tbenchmarkData.duration_sec = benchmarkData.duration_ms / 1000;\n\t\tbenchmarkData.ms_per_iteration = benchmarkData.duration_ms / iterations;\n\t\t\n\t\treturn benchmarkData;\n\t},\n\t\n\t/**\n\t * Pass in the info about the comparison you just did, and have that info printed to the\n\t *  info logs.\n\t * @param {Object} benchmarkData - The benchmark data, as returned from calling the\n\t *  .compareFunctions() method.\n\t * @param {Number} iterations - The number of iterations this comparison was done for\n\t *  (passed in as the third argument to .compareFunctions()).\n\t * @returns {string} The message to be logged (which will also be logged using gs.info()).\n\t */\n\tprintComparisonResults : function (benchmarkData, iterations) {\n\t\tvar logMsg;\n\t\t\n\t\tif (benchmarkData.comparison.which_was_faster === 0) {\n\t\t\tlogMsg = 'Both methods took the exact same amount of time.\\n' +\n\t\t\t\t'Complete benchmark details: \\n' +\n\t\t\t\tJSON.stringify(\n\t\t\t\t\tbenchmarkData, null, 2\n\t\t\t\t);\n\t\t\tgs.info(logMsg);\n\t\t\treturn logMsg;\n\t\t}\n\t\tlogMsg = '\\nMethod 1 took ' + benchmarkData.method_one.duration_ms + 'ms. Method 2 took ' +\n\t\t\tbenchmarkData.method_two.duration_ms + 'ms.\\n' +\n\t\t\t'Method ' + benchmarkData.comparison.which_was_faster + ' was faster by ' +\n\t\t\tbenchmarkData.comparison.total_faster_by_ms + 'ms total, over ' + iterations +\n\t\t\t' iterations (or ' + benchmarkData.comparison.iteration_faster_by_ms +\n\t\t\t'ms faster per-iteration). \\nThe slower function takes ' +\n\t\t\tbenchmarkData.comparison.slower_multiple + ' times as long as the faster function ' +\n\t\t\t'to run.\\n\\n' +\n\t\t\t'Complete benchmark details: \\n' +\n\t\t\tJSON.stringify(\n\t\t\t\tbenchmarkData, null, 2\n\t\t\t);\n\t\tgs.info(logMsg);\n\t\t\n\t\treturn logMsg;\n\t},\n\t\n\t/**\n\t * Starts the specified timer by ID (or the default timer if timer ID is not specified)\n\t * @param {string} [timerID=\"default\"] The ID of the timer to start or stop.\n\t * If an ID is not specified, it will be set to \"default\".\n\t * @return {BenchmarkRunner}\n\t */\n\tstartTimer : function (timerID) {\n\t\ttimerID = (typeof timerID == 'undefined') ? 'default' : timerID;\n\t\t\n\t\tif (this.timers.hasOwnProperty(timerID)) {\n\t\t\tthrow new Error(\n\t\t\t\t'Timer with ID ' + timerID + ' already exists. Unable to start ' +\n\t\t\t\t'multiple times with the same ID.'\n\t\t\t);\n\t\t}\n\t\t\n\t\tthis.timers[timerID] = new this._Timer(timerID);\n\t\tthis.timers[timerID].startTimer();\n\t\treturn this;\n\t},\n\t\n\t/**\n\t * Stops the specified timer by ID (or the default timer if timer ID is not specified)\n\t * @param {string} [timerID=\"default\"] The ID of the timer to start or stop.\n\t * If an ID is not specified, it will be set to \"default\".\n\t * @return {this._Timer} An instance of the _Timer class, on which you can call\n\t *  .getDurationMS() or .getDurationSec() to get the timer duration.\n\t */\n\tstopTimer : function (timerID) {\n\t\ttimerID = (typeof timerID == 'undefined') ? 'default' : timerID;\n\t\t\n\t\tif (!this.timers.hasOwnProperty(timerID)) {\n\t\t\tthrow new Error(\n\t\t\t\t'Timer with ID ' + timerID + ' does not exist. Unable to stop ' +\n\t\t\t\t'a timer that has not been created. Please call the .startTimer() ' +\n\t\t\t\t'method to start a new timer.'\n\t\t\t);\n\t\t}\n\t\t\n\t\tthis.timers[timerID].stopTimer();\n\t\treturn this.timers[timerID];\n\t},\n\t\n\t_calculateWhichWasFaster : function (benchmarkData) {\n\t\t//If method one duration was more than method 2 duration, return 2.\n\t\tif (benchmarkData.method_one.duration_ms > benchmarkData.method_two.duration_ms) {\n\t\t\treturn 2;\n\t\t}\n\t\t//If method one duration was less than method 2 duration, return 1.\n\t\tif (benchmarkData.method_one.duration_ms < benchmarkData.method_two.duration_ms) {\n\t\t\treturn 1;\n\t\t}\n\t\t\n\t\t//If method 1 and 2 durations were identical (or if something has gone terribly wrong),\n\t\t// return 0.\n\t\treturn 0;\n\t},\n\t\n\t_calculateSlowerMultiple : function (benchmarkData) {\n\t\tvar msFasterDuration, msSlowerDuration;\n\t\t\n\t\tvar comparisonData = benchmarkData.comparison;\n\t\tvar methodOneData = benchmarkData.method_one;\n\t\tvar methodTwoData = benchmarkData.method_two;\n\t\t\n\t\tif (benchmarkData.comparison.which_was_faster === 0) {\n\t\t\t//If both were the same speed, multiple is 1.\n\t\t\treturn 1;\n\t\t}\n\t\t\n\t\tmsFasterDuration = (comparisonData.which_was_faster === 1) ? methodOneData.duration_ms : methodTwoData.duration_ms;\n\t\tmsSlowerDuration = (comparisonData.which_was_faster === 1) ? methodTwoData.duration_ms : methodOneData.duration_ms;\n\t\t\n\t\treturn (msSlowerDuration / msFasterDuration);\n\t},\n\t\n\t/**\n\t *\n\t * @param {string} [timerID=\"default\"] The ID of the timer. Used for reference later.\n\t * @private\n\t * @constructor\n\t */\n\t_Timer : function (timerID) {\n\t\tthis.timer_id = timerID || 'default';\n\t\tthis.start_ms = 0;\n\t\tthis.stop_ms = 0;\n\t\tthis.duration_ms = 0;\n\t\tthis.duration_sec = 0;\n\t\tthis.timer_running = false;\n\t\t\n\t\tthis.startTimer = function () {\n\t\t\tthis.start_ms = new GlideDateTime().getNumericValue();\n\t\t\tthis.timer_running = true;\n\t\t};\n\t\tthis.stopTimer = function () {\n\t\t\tif (!this.timer_running || !this.start_ms) {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t'Attempted to stop a timer that either isn\\'t running, or that has ' +\n\t\t\t\t\t'an invalid start time.'\n\t\t\t\t);\n\t\t\t}\n\t\t\t\n\t\t\tthis.stop_ms = new GlideDateTime().getNumericValue();\n\t\t\tthis.duration_ms = (this.stop_ms - this.start_ms);\n\t\t\tthis.duration_sec = (this.duration_ms / 1000);\n\t\t\tthis.timer_running = false;\n\t\t\t\n\t\t\treturn this;\n\t\t};\n\t\t\n\t\t//Getters\n\t\tthis.getDurationMS = function () {\n\t\t\treturn this.duration_ms;\n\t\t};\n\t\tthis.getDurationSec = function () {\n\t\t\treturn this.duration_sec;\n\t\t};\n\t\tthis.getTimerID = function () {\n\t\t\treturn this.timer_id;\n\t\t};\n\t\t\n\t\t//Setters\n\t\tthis.setTimerID = function (timerID) {\n\t\t\tthis.timer_id = timerID;\n\t\t\treturn this;\n\t\t};\n\t},\n\t\n\t__example : function (iterations) {\n\t\tvar benchmarkUtil, benchmarkData;\n\t\tvar methodOne = function (args) {\n\t\t\t//Testing \"=\" query without setLimit\n\t\t\tvar grAudit = new GlideRecord('sys_audit');\n\t\t\tgrAudit.addEncodedQuery('newvalue=Random number: ' + args[0] + '^oldvalue=Random number: ' + args[1]);\n\t\t\tgrAudit.query();\n\t\t};\n\t\tvar methodTwo = function (args) {\n\t\t\t//Testing \"CONTAINS\" query without setLimit\n\t\t\tvar grAudit = new GlideRecord('sys_audit');\n\t\t\tgrAudit.addEncodedQuery('newvalueLIKE' + args[0] + '^oldvalueLIKE' + args[1]);\n\t\t\tgrAudit.query();\n\t\t};\n\t\titerations = iterations || 10;\n\t\t\n\t\tbenchmarkUtil = new BenchmarkRunner();\n\t\tbenchmarkData = benchmarkUtil.compareFunctions(\n\t\t\tmethodOne,\n\t\t\tmethodTwo,\n\t\t\titerations,\n\t\t\t[271, 488], //Random #s in audit table that match a contrived record for this test\n\t\t\ttrue\n\t\t);\n\t\t\n\t\treturn benchmarkUtil.printComparisonResults(benchmarkData, iterations);\n\t},\n\t\n\t__example2 : function () {\n\t\tvar bmr;\n\t\tvar argsArray = [\n\t\t\t{\n\t\t\t\t\"some_object\": {\n\t\t\t\t\t\"some_object\": {\n\t\t\t\t\t\t\"some_object\": {\n\t\t\t\t\t\t\t\"some_number\": 123,\n\t\t\t\t\t\t\t\"some_string\": \"abc123\",\n\t\t\t\t\t\t\t\"some_boolean\": 123,\n\t\t\t\t\t\t\t\"some_null\": null,\n\t\t\t\t\t\t\t\"some_method\": function() {\n\t\t\t\t\t\t\t\treturn \"some_string\";\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t];\n\t\t\n\t\targsArray.push(JSON.stringify(argsArray[0]));\n\t\t\n\t\t/*\n\t\t\targsArray[0] now contains the object, and argsArray[1] contains the\n\t\t\t stringified object.\n\t\t\tWe'll test whether it's faster to stringify an object, or to parse an\n\t\t\t object string.\n\t\t */\n\t\t\n\t\tbmr = new BenchmarkRunner()\n\t\t\n\t\tbmr.printComparisonResults(\n\t\t\tbmr.compareFunctions(\n\t\t\t\tthingOne,\n\t\t\t\tthingTwo,\n\t\t\t\t20000,\n\t\t\t\targsArray\n\t\t\t),\n\t\t\t20000\n\t\t);\n\t\t\n\t\tfunction thingOne(arrArgs) {\n\t\t\tvar strObj = JSON.stringify(arrArgs[0]);\n\t\t\treturn strObj;\n\t\t}\n\t\t\n\t\tfunction thingTwo(arrArgs) {\n\t\t\tvar objObj = JSON.parse(arrArgs[1]);\n\t\t\treturn objObj;\n\t\t}\n\t\t\n\t},\n\t\n\ttype : 'BenchmarkRunner'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/BenchmarkRunner/README.md",
    "content": "# BenchmarkRunner\nThis Script Include is a tool for benchmarking individual functions, or comparing the execution time or two functions, to determine whether one method of doing something is faster than another, and by how much. \n\nFor example (as you can see in the example below), comparing whether it's faster to *stringify* an object, or *parse* a JSON string into an object. \n\n## compareFunctions(methodOne, methodTwo, iterations, args)\nExecute two functions with the specified arguments, and get info about which was faster\n(and by how much).\n\n## benchmarkSingleFunction(functionToBenchmark, iterations, args)\nBenchmark a single function, and return information about how long it took for the specified number of iterations, and how long each iteration took on average. \n\n## Example server-side call (background script)\n```javascript\nvar bmr;\nvar argsArray = [\n\t{\n\t\t\"some_object\": {\n\t\t\t\"some_object\": {\n\t\t\t\t\"some_object\": {\n\t\t\t\t\t\"some_number\": 123,\n\t\t\t\t\t\"some_string\": \"abc123\",\n\t\t\t\t\t\"some_boolean\": 123,\n\t\t\t\t\t\"some_null\": null,\n\t\t\t\t\t\"some_method\": function() {\n\t\t\t\t\t\treturn \"some_string\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n];\n\nargsArray.push(JSON.stringify(argsArray[0]));\n\n/*\n\targsArray[0] now contains the object, and argsArray[1] contains the\n\t stringified object.\n\tWe'll test whether it's faster to stringify an object, or to parse an\n\t object string.\n */\n\nbmr = new BenchmarkRunner()\n\nbmr.printComparisonResults(\n\tbmr.compareFunctions(\n\t\tthingOne,\n\t\tthingTwo,\n\t\t20000,\n\t\targsArray\n\t),\n\t20000\n);\n\nfunction thingOne(arrArgs) {\n\tvar strObj = JSON.stringify(arrArgs[0]);\n\treturn strObj;\n}\n\nfunction thingTwo(arrArgs) {\n\tvar objObj = JSON.parse(arrArgs[1]);\n\treturn objObj;\n}"
  },
  {
    "path": "Server-Side Components/Script Includes/CSV Parser/CSVParser.js",
    "content": "var CSVParser = Class.create();\nCSVParser.prototype = {\n    initialize: function() {},\n\n    //\n    // Parses a CSV string into array of objects\n    // Example:\n    // var csv = \"John, Doe, 33\\nJane, Doe, 32\\nJack, Doe, 11\\nJosh, Doe, 13\" \n    // var delimiter = \",\"\n    // var headers = [\"first_name\", \"last_name\", \"age\"]\n    // var result = parser.parse(csv, headers, delimiter);\n    //\n    parse: function(csv, headers, delimiter, quoteCharacter) {\n\n        // Validate all input parameters\n        this._validateInput(csv, headers, delimiter, quoteCharacter);\n\n        // Split based on carriage return\n        var lines = csv.split(\"\\n\");\n\n        var jsonArray = [];\n\n        // Populate the array from JSON objects\n        for (var i = 0; i < lines.length; i++) {\n            if (i == 0) {\n                continue; // Ignore the header line\n            }\n\n            var csvLine = lines[i];\n\n            if (csvLine) {\n                var jsonObject = new sn_impex.CSVParser().parseLineToObject(csvLine, headers, delimiter, quoteCharacter);\n                jsonArray.push(jsonObject);\n            }\n\n        }\n\n        return jsonArray;\n\n    },\n\n    _validateInput: function(csv, headers, delimiter, quoteCharacter) {\n\n        if (!csv) {\n            throw new Error(\"The field 'csv' is required. This is the concatenated CSV text that needs to be parsed.\");\n        }\n\n        if (!delimiter) {\n            throw new Error(\"The field 'delimiter' is required. This is the character that splits the columns in the CSV file e.g. a comma.\");\n        }\n\n        if (headers) {\n            if (!Array.isArray(headers)) {\n                throw new Error(\"The field 'headers' should be an array in the form of ['field1','field2'] etc.\");\n            }\n        }\n\n    },\n\n    type: 'CSVParser'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/CSV Parser/README.md",
    "content": "# Script Include: CSVParser\n\nA script includes that parses concatenated CSV string and returns and array of the JSON objects for each row of the CSV data.\n\n## Example usage\n\n```\nvar csv = \"John, Doe, 33\\nJane, Doe, 32\\nJack, Doe, 11\\nJosh, Doe, 13\"  // Your CSV data\nvar delimiter = \",\"\nvar headers = [\"first_name\", \"last_name\", \"age\"] // Your CSV data headers\nvar result = parser.parse(csv, headers, delimiter);\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/CacheHelper/CacheHelper.js",
    "content": "var CacheHelper = Class.create();\nCacheHelper.prototype = {\n\n    logIt : false,\n\n    initialize: function(log) {\n        this.logIt = log;\n    },\n\n    /**\n     * Adds data to the cache\n     * \n     * @param {string} cacheName : cache name\n     * @param {string} key : unique cache key \n     * @param {any} data : any data to be cached\n     */\n    addToCache: function(cacheName, key, data) {\n        this._validateCacheName(cacheName);\n        this._validateCacheKey(key);\n\n        GlideCacheManager.put(cacheName, key, data);\n    },\n\n    /**\n     * Removes data from cache\n     * \n     * @param {string} cacheName : cache name\n     * @param {string} key : unique cache key \n     * @returns cached data\n     */\n    getFromCache: function(cacheName, key) {\n        this._validateCacheName(cacheName);\n        this._validateCacheKey(key);\n\n        var data = GlideCacheManager.get(cacheName, key);\n        return data;\n    },\n\n    removeFromCache: function(cacheName) {\n        this._validateCacheName(cacheName);\n        \n        GlideCacheManager.flush(cacheName);\n    },\n\n    /**\n     * Either gets the data from cache or calls the callback functions, get the data and then adds it to the cache\n     * @param {string} cacheName : cache name\n     * @param {string} key : unique cache key \n     * @param {function} dataCallBack : call back function that returns the data to be cached\n     * @returns data from the cache or based on call back function\n     */\n    getOrAddToCache: function(cacheName, key, dataCallBack) {\n        this._validateCacheName(cacheName);\n        this._validateCacheKey(key);\n\n        var data = GlideCacheManager.get(cacheName, key);\n\n        if (gs.nil(data)) {\n            data = dataCallBack();\n            GlideCacheManager.put(cacheName, key, data);\n            if(this.logIt) gs.debug(\"Data from source.\");\n            return data;\n        }\n\n        if(this.logIt) gs.debug(\"Data from cache.\");\n        return data;\n    },\n\n    _validateCacheName :function(cacheName){\n        if(!cacheName) throw new Error(\"cacheName is required\");\n    },\n\n    _validateCacheKey :function(cacheKey){\n        if(!cacheKey) throw new Error(\"cacheKey is required\");\n    },\n\n    type: 'CacheHelper'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/CacheHelper/README.md",
    "content": "# BenchmarkRunner\nJust a wrapper around GlideCacheManager with methods to enable validation of Cache key and data and ability to use the GlideCacheManager easily.\n\n## Example server-side call (background script)\n```javascript\nvar cacheName = \"rahman_test\";\nvar cacheKey = \"1\";\n\nvar helper = new CacheHelper(false);\n\n// Either get the data from cache or add it\nvar data = helper.getOrAddToCache(cacheName, cacheKey, function(){\n    gs.log(\"This will be called if the data is not in the cache. The second time will not be called.\");\n\n    // This will be called if the data is not in cache!!!\n    var data = {\n        name: \"rahman\",\n    }\n\n    return data;\n})\n\ngs.log(JSON.stringify(data));\n\n//helper.removeFromCache(cacheName)"
  },
  {
    "path": "Server-Side Components/Script Includes/Calculate Business days dynamically/readme.md",
    "content": "# Business Day Calculator for ServiceNow\n\nThis JavaScript function calculates a future date by adding a specified number of **business days** (excluding weekends) to a given date.\n\n\n##  Features\n- Skips weekends (Saturday and Sunday)\n- Works with any number of business days\n- Uses ServiceNow's `GlideDateTime` API\n\n##  Usage\n1. Copy the function into a **Script Include**, **Business Rule**, or **Scheduled Job** in ServiceNow.\n2. Call the function with:\n   - A valid date string (e.g., `'2025-10-24 12:00:00'`)\n   - The number of business days to add (e.g., `5`)\n\n##  Example\n```javascript\ngs.print(add_business_days('2025-10-24 12:00:00', 5));\n// Output: 2025-10-31 (skips weekend)\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Calculate Business days dynamically/script.js",
    "content": "// Function to add any number of business days to a given date\nfunction add_business_days(date, num_days) {\n    var result_date = new GlideDateTime(date);\n    var added_days = 0;\n\n    while (added_days < num_days) {\n        result_date.addDaysUTC(1);\n        var day_of_week = result_date.getDayOfWeekUTC();\n        if (day_of_week != 6 && day_of_week != 7) { // Skip Saturday (6) and Sunday (7)\n            added_days++;\n        }\n    }\n\n    return result_date.getDate();\n}\n\n// Example usage:\ngs.print(add_business_days('2025-10-24 12:00:00', 5)); // Adds 5 business days\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Catalog Item Pricing/README.md",
    "content": "Catalog Item Pricing\n\nDescription:\nThis script includes provides a dynamic and configurable approach to calculating the final price of catalog items in ServiceNow.\nIt utilizes system properties for pricing parameters, enabling administrators to adjust them without modifying the code itself.\n\nFunctionality:\n\nDynamic Pricing: Retrieves discount and cost values from system properties, offering flexibility in pricing configuration.\nCustomer Discounts: Applies discounts based on customer type (e.g., Enterprise customers might receive a discount).\nQuantity Discounts: Provides discounts for orders exceeding a predefined quantity threshold.\nAdditional Service Costs: Incorporates costs for additional services selected in the order (e.g., installation fees).\nPrice Calculation: Calculates the final price by applying discounts and adding any applicable service costs to the base price of the catalog item.\nBenefits:\n\nCentralized Configuration: Simplifies management of pricing parameters through system properties.\nFlexibility: Adapts to different pricing scenarios by modifying system property values.\nUser-Friendliness: Empowers administrators to configure pricing rules without code changes.\nImproved Efficiency: Streamlines the process of calculating final catalog item prices.\nUsage:\n\nInclude this script (calculate_catalog_item_price.js) in your desired business rule or workflow.\nPass the catalogItem and currentItem records as arguments to the calculatePrice function.\nThe function will return the final calculated price.\nExample:\n\nJavaScript\n// In a business rule or workflow script\n\nvar catalogItem = GlideRecord('sc_catalog_item');\ncatalogItem.get('your_catalog_item_sys_id');\n\nvar currentItem = current.sc_catalog_item;\n\nvar finalPrice = calculateCatalogItemPrice.calculatePrice(catalogItem, currentItem);\n\n// Use the finalPrice variable for further processing\n\n\nSystem Properties:\n\ncatalog_item_enterprise_discount: Discount percentage for Enterprise customers (e.g., 0.1 for 10% discount)\ncatalog_item_quantity_discount_threshold: Minimum quantity required to qualify for a quantity-based discount (e.g., 10)\ncatalog_item_quantity_discount_percentage: Discount percentage for orders exceeding the threshold (e.g., 0.05 for 5% discount)\ncatalog_item_installation_cost: Cost of the \"Installation\" service (e.g., 100)\n\nAdditional Notes:\nYou can extend this script to include additional pricing factors or logic as needed.\nRefer to the ServiceNow documentation for more information on system properties and script includes.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Catalog Item Pricing/calculate_catalog_item_price.js",
    "content": "var calculateCatalogItemPrice = Class.create();\n/**\n * Helper class for dealing with catalogs Item Pricing.\n * @class calculateCatalogItemPrice\n * @author ankit shukla \n */\ncalculateCatalogItemPrice.prototype = {\n    initialize: function() {\n    },\n    /**\n     * Returns the final price for the catalog.\n     *\n     * @param {catalogItem} Represents the base catalog item record.\n     * @return {integer} rwhich represents the final calculated price of the catalog item.\n     */\n    calculatePrice: function(catalogItem, currentItem) {\n        var basePrice = catalogItem.price;\n        var finalPrice = basePrice;\n\n        // Get system properties for dynamic pricing configuration\n        var enterpriseDiscount = gs.getProperty('catalog_item_enterprise_discount'); // Discount for Enterprise customers\n        var quantityDiscountThreshold = gs.getProperty('catalog_item_quantity_discount_threshold'); // Quantity threshold for discount\n        var quantityDiscountPercentage = gs.getProperty('catalog_item_quantity_discount_percentage'); // Discount percentage for qualifying quantities\n        var installationCost = gs.getProperty('catalog_item_installation_cost'); // Cost of installation service\n\n        // Apply discounts based on customer type\n        if (currentItem.customer.type == 'Enterprise') {\n            finalPrice = finalPrice * (1 - enterpriseDiscount); // Apply Enterprise discount\n        }\n\n        // Apply quantity-based discounts\n        if (currentItem.quantity > quantityDiscountThreshold) {\n            finalPrice = finalPrice * (1 - quantityDiscountPercentage); // Apply quantity-based discount\n        }\n\n        // Add costs for additional services\n        if (currentItem.additional_services.includes('Installation')) {\n            finalPrice += installationCost; // Add installation cost\n        }\n\n        // Set the final price on the current item\n        currentItem.price = finalPrice;\n        currentItem.update();\n\n        return finalPrice;\n    }\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CatalogUtils/CatalogUtils.js",
    "content": "var CatalogUtils = Class.create();\n\n/**\n * Helper class for dealing with catalogs and catalog items.\n * All methods can be called both server-side and client-side.\n *\n * @class CatalogUtils\n * @author Maik Skoddow\n */\nCatalogUtils.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    /**\n     * Returns all variables of a given RITM as stringified array of JSON objectss.\n     *\n     * @param {Object} objParameter Valid `sc_req_item` record or String-based Sys ID of a RITM.\n     * @return {String} Stringified array of JSON objects with various variable values and information or an empty array if no valid data could be found.\n     */\n    getVariables: function(objParameter) {\n        var _grRITM = null;\n\n        //server-side call with Sys ID\n        if (typeof objParameter == 'string' && objParameter.length == 32) {\n            _grRITM = new GlideRecord('sc_req_item');\n\n            if (!_grRITM.get(objParameter)) {\n                _grRITM = null;\n            } \n        }\n\n        //server-side call with initialized RITM GlideRecord \n        if (typeof objParameter == 'object' && objParameter instanceof GlideRecord) {\n            if (objParameter.isValidRecord() && objParameter.getTableName() == 'sc_req_item') {\n                _grRITM = objParameter;\n            }\n        }\n\n        //client-side Ajax call with RITM Sys ID as parameter\n        if (this.getParameter('sysparm_ritm_sys_id')) {\n            var _strSysID = String(this.getParameter('sysparm_ritm_sys_id')).trim();\n\n            if (_strSysID.length == 32) {\n                _grRITM = new GlideRecord('sc_req_item');\n\n                if (!_grRITM.get(_strSysID)) {\n                    _grRITM = null;\n                }\n            }\n        }\n\n        //no valid RITM could be loaded\n        if (_grRITM == null) {\n            return '[]';\n        }\n\n        //could be improved by offering a configuration method for excluded variable types\n        var _strExcludedTypes = '|11|12|14|15|17|19|20|24|'; \n        var _arrResult        = [];\n        var _grVariables      = new GlideRecord('sc_item_option_mtom');\n\n        //load all catalog variables for given RITM\n        _grVariables.addQuery('request_item', _grRITM.sys_id);\n        _grVariables.orderBy('sc_item_option.order');\n        _grVariables.query();\n\n        while (_grVariables.next()) {\n            var _strType         = _grVariables.sc_item_option.item_option_new.type.toString();\n            var _strName         = _grVariables.sc_item_option.item_option_new.name.toString();\n            var _strQuestionText = _grVariables.sc_item_option.item_option_new.question_text.toString();\n            var _strValue        = _grVariables.sc_item_option.value.toString();\n            var _strDisplayValue = _grRITM.getDisplayValue('variables.' + _strName);\n\n            //if type is not excluded fill result array with variable values\n            if (_strExcludedTypes.indexOf('|' + _strType + '|') == -1) {\n                _arrResult.push({\n                    strType:         _strType,\n                    strName:         _strName,\n                    strQuestionText: _strQuestionText,\n                    strValue:        _strValue,\n                    strDisplayValue: _strDisplayValue,\n                });\n            }\n        }\n\n        return JSON.stringify(_arrResult);\n    },\n\n    /**SNDOC\n    @name _variablesToText\n    @description Parses over the variables of a given record, returning Question Label and Display Value of answers\n    @param {object} [pRecord] GlideRecord object with variables key\n    @returns {string} A formatted string of variable questions and corresponding answer\n    */\n    variablesToText: function (pRecord) {\n        var oVariables = pRecord.variables;\n        var aPayload = [];\n        for (key in oVariables) {\n            var sQuestion = oVariables[key].getLabel();\n            var sAnswer = oVariables[key].getDisplayValue();\n            if (JSUtil.notNil(sQuestion) && JSUtil.notNil(sAnswer)) {\n                aPayload.push(sQuestion + ':\\n>>' + sAnswer);\n            }\n        }\n        return aPayload.join('\\n\\n');\n    },\n\n    type: 'CatalogUtils',\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CatalogUtils/README.md",
    "content": "# CatalogUtils\nThis Script Include is a helper class for dealing with Service Catalog Items\nand Requested Items.\n## getCatalogVariables()\nReturns all variables of a given RITM as stringified array of JSON objects. \nThis method thus corresponds to the \"Get Catalog Variables\" action in a flow and \nit can be used both server-side and client-side.\n\nA single JSON object has the following properties:\n\n**Note:**\\\nVariable types with no values (e.g. containers) are excluded.\n\n### Example for server-side call\n```javascript\nvar grRITM = new GlideRecord('sc_req_item');\n\nif (grRITM.get('485ae5bf2fc930105bd5d0e62799b662')) {\n    var jsonVariables = JSON.parse((new CatalogUtils()).getVariables(grRITM));\n}\n```\n### Example for client-side call\n```javascript\nvar gaCatalogUtils = new GlideAjax('CatalogUtils');\n\t\ngaCatalogUtils.addParam('sysparm_name', 'getVariables');\ngaCatalogUtils.addParam('sysparm_ritm_sys_id', '485ae5bf2fc930105bd5d0e62799b662');\n\ngaCatalogUtils.getXMLAnswer(function(strAnswer) {\n\tvar jsonVariables = JSON.parse(strAnswer);\n});\n\nvar jsonVariables = JSON.parse(gaCatalogUtils.getAnswer() || \"\");\n```\n### Example of returned values\n```javascript\n[\n  {\n    \"strType\": \"8\",\n    \"strName\": \"issued_by_name\",\n    \"strQuestionText\": \"Issued by\",\n    \"strValue\": \"62826bf03710200044e0bfc8bcbe5df1\",\n    \"strDisplayValue\": \"Abel Tuter\"\n  },\n  {\n    \"strType\": \"6\",\n    \"strName\": \"issued_by_phone\",\n    \"strQuestionText\": \"Phone\",\n    \"strValue\": \"170\",\n    \"strDisplayValue\": \"170\"\n  }\n]\n```\n\n## Variables to Text\n\nReturns all Variables with an answer as a formatted string.\nUseful for 'exporting' question:answers to primitive fields such as description or notifications \nExpected use is Server side.\n\n### Example of server-side call\n```javascript\nvar grRITM = new GlideRecord('sc_req_item');\n\nif (grRITM.get('8c135e0647b1b110da816241e36d437e')) {\n    var jsonVariables = JSON.parse((new CatalogUtils()).variablesToText(grRITM));\n}\n```\n\n### Example of returned values\n```\nCopier paper (reams):\n>>3\n\nPens (box of 10):\n>>3\n\nScreen wipes (tube of 20):\n>>4\n\nAdditional requirements:\n>>I need this yesterday\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Check User Criteria for Catalog Item/README.md",
    "content": "# CheckCriteria Script Include\n\nThis script include is used to check if a user has access to a specific catalog item based on \"Available for\" and \"Not Available for\" user criteria in ServiceNow. It supports admin overrides and custom user checks.\n\n\n## Usage\n\nThe `CheckCriteria` script include provides a method `itemCriteria` which checks if a user meets the criteria to access a catalog item.\n\n### Syntax\n\n```javascript\nvar check = new CheckCriteria();\nvar result = check.itemCriteria(item, adminOverride, userToCheck);\n```\n\n### Parameters\n\n1. **`item`** (string): \n   - The sys_id of the catalog item you want to check access for.\n   - This parameter is **required**.\n\n2. **`adminOverride`** (boolean, optional): \n   - Specifies whether admin override should be taken into account.\n   - If `true`, users with the `admin` role will always have access to the item, even if they do not match the user criteria.\n   - Defaults to `false` if not provided.\n\n3. **`userToCheck`** (string, optional): \n   - The user ID of the user you want to check access for.\n   - If not provided, the currently logged-in user (`gs.getUser()`) will be used by default.\n\n### Return\n\n- **`true`** if the user has access to the catalog item.\n- **`false`** if the user does not have access to the catalog item.\n\n### Example\n\n```javascript\nvar check = new CheckCriteria();\n\n// Example 1: Check if the current user has access to the catalog item\nvar hasAccess = check.itemCriteria('12345abcdef'); // '12345abcdef' is the sys_id of the catalog item\n\n// Example 2: Check access for a specific user with an admin override\nvar hasAccess = check.itemCriteria('12345abcdef', true, 'abc123user'); // 'abc123user' is the user ID of the user\n```\n\nIn the first example, the script checks if the current user can access the specified catalog item. In the second example, it checks if the specified user can access the item and allows admin override.\n\n## Error Handling\n\n- If the `item` parameter is not provided or is `null`, an error message will be logged in the system logs.\n- The script also logs errors when unable to retrieve user criteria for the catalog item.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Check User Criteria for Catalog Item/checkUserCriteria.js",
    "content": "var CheckCriteria = Class.create();\nCheckCriteria.prototype = {\n    initialize: function() {},\n\n    /**\n     * Checks if the user meets the criteria to access the catalog item.\n     * \n     * @param {string} item - The sys_id of the catalog item.\n     * @param {boolean} [adminOverride] - Optional. Whether admin role should override the criteria check. Defaults to false if not provided.\n     * @param {string} [userToCheck] - Optional. The user ID of the user whose access is being checked. Defaults to the current user if not specified.\n     * \n     * @returns {boolean} - True if the user has access to the catalog item, otherwise false.\n     */\n    itemCriteria: function(item, adminOverride, userToCheck) {\n        // Set default value for adminOverride if not provided\n        adminOverride = (typeof adminOverride !== 'undefined') ? adminOverride : false;\n\n        // Early exit if item is nil or missing\n        if (gs.nil(item)) {\n            gs.error('CheckCriteria().itemCriteria() failed: item parameter is missing or null, item: ' + item);\n            return false;\n        }\n\n        // Get the user object and user ID, defaulting to the current user if userToCheck is not provided\n        var userObj = !gs.nil(userToCheck) ? gs.getUser().getUserByID(userToCheck) : gs.getUser();\n        var userId = userObj.getID();\n\n        // Admin override: if the user is an admin and adminOverride is true, return true\n        if (adminOverride && userObj.hasRole('admin')) {\n            return true;\n        }\n\n        // Fetch \"Available for\" and \"Not Available for\" user criteria\n        var availableForUC = this.getUserCritria(item, true);\n        var notAvailableForUC = this.getUserCritria(item, false);\n\n        // Check if the user matches the \"Not Available for\" criteria first\n        if (sn_uc.UserCriteriaLoader.userMatches(userId, notAvailableForUC)) {\n            return false;\n        }\n\n        // Check if the user matches the \"Available for\" criteria\n        return sn_uc.UserCriteriaLoader.userMatches(userId, availableForUC);\n    },\n\n    /**\n     * Retrieves the user criteria for a catalog item.\n     * \n     * @param {string} item - The sys_id of the catalog item.\n     * @param {boolean} available - If true, fetch the \"Available for\" criteria. If false, fetch the \"Not Available for\" criteria.\n     * \n     * @returns {Array<string>} - An array of user criteria sys_ids for the catalog item.\n     */\n    getUserCritria: function(item, available) {\n        // Early exit if item is nil or missing\n        if (gs.nil(item)) {\n            gs.error('CheckCriteria().getUserCritria() failed: item parameter is missing or null, item: ' + item);\n            return [];\n        }\n\n        // Define table name constants\n        var TABLE_AVAILABLE = 'sc_cat_item_user_criteria_mtom';\n        var TABLE_NOT_AVAILABLE = 'sc_cat_item_user_criteria_no_mtom';\n\n        // Select appropriate table based on availability flag\n        var tableToCheck = available ? TABLE_AVAILABLE : TABLE_NOT_AVAILABLE;\n\n        // Query user criteria from the appropriate table\n        var ucCheckGr = new GlideRecord(tableToCheck);\n        ucCheckGr.addQuery('sc_cat_item', item);\n        ucCheckGr.query();\n\n        // Store user criteria sys_ids in an array\n        var returnArr = [];\n        while (ucCheckGr.next()) {\n            returnArr.push(ucCheckGr.getValue('user_criteria'));\n        }\n\n        return returnArr;\n    },\n\n    type: 'CheckCriteria'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Check User Has Role/README.md",
    "content": "# User Has Role\n\nThis script checks whether the user has a role or not.\nThis script takes two arguments\n\nArgument #1: userId - userId of the record for which you need to validate the roles exists\nArgument #2: roleId - roleId is the sys_id of the role that we need to check whether exists against the provided userId"
  },
  {
    "path": "Server-Side Components/Script Includes/Check User Has Role/UserHasRole.js",
    "content": "var UserHasRole = Class.create();\nUserHasRole.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    isPublic: function() {\n        return true;\n    },\n\n    userHasRole: function(userId, roleId) {\n        try {\n\t\t\t\n\t\t\tuserId = (userId == '' || userId == undefined) ? gs.getUserID() : userId; \n\n            var grUserHasRole = new GlideRecord('sys_user_has_role');\n            grUserHasRole.addQuery('role', roleId);\n            grUserHasRole.addQuery('user', userId);\n            grUserHasRole.setLimit(1);\n            grUserHasRole.query();\n\t\t\t\n            return sysUserHasRole.hasNext();\n\n        } catch (exception) {\n            gs.info('UserHasRole.userHasRole() - ' + exception);\n        }\n    },\n\n    type: 'UserHasRole'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/Check Valid Choice/README.md",
    "content": "Introduction :\n\nThis script include is a client callable script include which can be used to check if the value of a choice field is valid, optionally given a dependent value. This is helpful when you do transforms and when you want to do some validations in your REST inbound messages.\n\nInputs and Outputs :\n     * @param {object} current - GlideRecord object containing the current record\n     * @param {string} The name of the choice field\n     * @returns {bool}         - Boolean indicating whether the value of a choice field is valid\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Check Valid Choice/script.js",
    "content": "var GenericUtils = Class.create();\nGenericUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n   /**\n     * Checks if the value of a choice field is valid, optionally given a dependent value\n     * @param {object} current - GlideRecord object containing the current record\n     * @param {string} The name of the choice field\n     * @returns {bool}         - Boolean indicating whether the value of a choice field is valid\n     */\n    isChoiceValid: function(current, choiceField) {\n        // Checking if we get valid params\n        if (JSUtil.nil(current) || JSUtil.nil(choiceField)) {\n            return false;\n        }\n\n        var arrUtil = new ArrayUtil();\n        var choiceValue = current.getValue(choiceField);\n        var field = current.getElement(choiceField);\n        var dependentField = field.getDependent();\n        var dependentValue = \"\";\n        if (dependentField != null) {\n            dependentValue = current.getValue(dependentField);\n        }\n\n        var validValues = field.getChoices(dependentValue);\n\n        // Return a boolean indicating whether of not the specific field has a value or not.\n        return (arrUtil.indexOf(validValues, choiceValue) >= 0) ? true : false;\n    },\n  type: \"GenericUtils\"\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Check writer/CheckWriter.js",
    "content": "var CheckWriter = Class.create();\nCheckWriter.prototype = {\n    initialize: function () {\n        this.numberWords = {\n            0: \"zero\",\n            1: \"one\",\n            2: \"two\",\n            3: \"three\",\n            4: \"four\",\n            5: \"five\",\n            6: \"six\",\n            7: \"seven\",\n            8: \"eight\",\n            9: \"nine\",\n            10: \"ten\",\n            11: \"eleven\",\n            12: \"twelve\",\n            13: \"thirteen\",\n            14: \"fourteen\",\n            15: \"fifteen\",\n            16: \"sixteen\",\n            17: \"seventeen\",\n            18: \"eighteen\",\n            19: \"nineteen\",\n            20: \"twenty\",\n            30: \"thirty\",\n            40: \"forty\",\n            50: \"fifty\",\n            60: \"sixty\",\n            70: \"seventy\",\n            80: \"eighty\",\n            90: \"ninety\",\n            100: \"one hundred\",\n            1000: \"one thousand\",\n            1000000: \"one million\",\n            1000000000: \"one billion\",\n            1000000000000: \"one trillion\",\n            10000000000000: \"one quadrillion\"\n        };\n    },\n\n    /**\n     * Writes a check in English\n     * @param {a number to write as check} number \n     * @returns English representation of the number as English words\n     */\n    write: function (number) {\n        return this._numberToWords(number);\n    },\n\n    _numberToWords: function (num) {\n        if (num < 0) return \"minus \" + this._numberToWords(-num);\n        if (num < 20) return this.numberWords[num];\n        if (num < 100)\n            return (\n                this.numberWords[Math.floor(num / 10) * 10] + (num % 10 ? \"-\" + this._numberToWords(num % 10) : \"\")\n            );\n        if (num < 1000)\n            return (\n                this.numberWords[Math.floor(num / 100)] + \" hundred\" + (num % 100 ? \" and \" + this._numberToWords(num % 100) : \"\")\n            );\n        if (num < 1000000)\n            return (\n                this._numberToWords(Math.floor(num / 1000)) + \" thousand\" + (num % 1000 ? \", \" + this._numberToWords(num % 1000) : \"\")\n            );\n        if (num < 1000000000)\n            return (\n                this._numberToWords(Math.floor(num / 1000000)) + \" million\" + (num % 1000000 ? \", \" + this._numberToWords(num % 1000000) : \"\")\n            );\n        if (num < 1000000000000)\n            return (\n                this._numberToWords(Math.floor(num / 1000000000)) + \" billion\" + (num % 1000000000 ? \", \" + this._numberToWords(num % 1000000000) : \"\")\n            );\n        if (num < 1000000000000000)\n            return (\n                this._numberToWords(Math.floor(num / 1000000000000)) + \" trillion\" + (num % 1000000000000 ? \", \" + this._numberToWords(num % 1000000000000) : \"\")\n            );\n        if (num < 1000000000000000000)\n            return (\n                this._numberToWords(Math.floor(num / 1000000000000000)) + \" quadrillion\" + (num % 1000000000000000 ? \", \" + this._numberToWords(num % 1000000000000000) : \"\")\n            );\n        return \"number too large\";\n    },\n\n    type: 'CheckWriter'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Check writer/README.md",
    "content": "# RegexUtils\n\nI am sure ServiceNow users need a check right? This Script Include gets a number and converts to an English style check.\n\ne.g. 123456789 is printed as:\n\none hundred and twenty-three million, four hundred and fifty-six thousand, seven hundred and eighty-nine\n\n## Usage\n\n```javascript\nvar checkWriter = new global.CheckWriter();\n\ngs.log(checkWriter.write(123456789)); // prints: one hundred and twenty-three million, four hundred and fifty-six thousand, seven hundred and eighty-nine\n\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Client and Server Callable Script Include/README.md",
    "content": "# Client and Server Callable Script Include\n\nExample of a script include that can be called via both client and server."
  },
  {
    "path": "Server-Side Components/Script Includes/Client and Server Callable Script Include/script include.js",
    "content": "//Ensure Client is marked as true\n\nvar My_Functions = Class.create();\nMy_Functions.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\tlog_info: function(x0){\n\t\tvar results = {};\n\t\tvar x = this.getParameter('sysparm_x') || x0;\n\t\tgs.info(x);\n\t\tresults.message = 'success';\n\t\treturn JSON.stringify(results);\n\t},\n\t\n    type: 'My_Functions'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/Collect Field Values from Any One Table Record/universalRecordCollector.js",
    "content": "var HM_Record_Details = Class.create();\nDV_Record_Details.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getDetails: function() {\n        var table = this.getParameter('sysparm_table');\n        var recordID = this.getParameter('sysparm_recordID');\n        var fieldNames = this.getParameter('sysparm_fieldNames');\n\t\t\n\tvar fields = fieldNames.split(',');\n\t\t\n        var targetRecord = new GlideRecordSecure(table);\n        targetRecord.addQuery('sys_id', recordID);\n        targetRecord.query();\n\n        var obj = {};\n\n        if (targetRecord.next()) {\n            for (var i = 0; i < fields.length; i++) {\n                if (targetRecord.isValidField(fields[i])) {\n                    obj[fields[i]] = targetRecord.getValue(fields[i]);\n                }\n            }\n        }\n\n        return JSON.stringify(obj);\n    },\n\n    type: 'DV_Record_Details'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Collect Field Values from Any Table/README.md",
    "content": "# Universal Field Collector (Ajax Version Script Include)\n- This SI allows for users to request any field values from any table (except if security restrictions prevent) for any one particular record\n- In Client script, instantiate GlideAjax with this script include\n- Call function `getDetails`\n- Pass in the following parameters in this order\n- (Table_Name, Sys_id, \"field_Name_1,field_name_2\")\n- Note: fields requested from record need to be the format of a commas seperated string\n- XMLAnswer will return stringified JSON object which can then be parsed in client script callback function\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CommandInjectionChecker/CommandInjectionChecker.js",
    "content": "/**\n * CommandInjectionChecker\n * \n * A ServiceNow Script Include that provides security utilities for detecting\n * command injection attack patterns in user input strings.\n * \n * PURPOSE:\n * This utility class is designed to identify potentially malicious command\n * injection payloads by analyzing input strings for common shell metacharacters\n * and command substitution patterns. It serves as a first-line defense against\n * command injection attacks in server-side scripts.\n * \n * USAGE:\n * var checker = new CommandInjectionChecker();\n * var isSuspicious = checker.containsCommandInjection(userInput);\n * if (isSuspicious) {\n *     gs.warn('Potential command injection detected in input');\n * }\n * \n * SECURITY NOTES:\n * - This checker detects PATTERNS, not guaranteed exploits\n * - Use this as ONE layer of defense, not the only layer\n * - Always validate and sanitize user input at multiple levels\n * - Consider using parameterized queries and proper escaping\n * - Log suspicious inputs for security auditing\n * - Never trust user input, even after this check passes\n * \n */\nvar CommandInjectionChecker = Class.create();\n\nCommandInjectionChecker.prototype = {\n    initialize: function() {\n\n        // Regex pattern to detect command-injection attempts.\n        // This pattern matches the following items in the input string:\n        // 1) Command separators:\n        //      - semicolon: `;`\n        //      - single pipe: `|`   (common shell pipe)\n        //      - logical OR chaining: `||`\n        //      - logical AND chaining: `&&`\n        //    Note: this pattern matches both single `|` and the double `||`, and it\n        //    matches `&&` for logical AND chaining.\n        //\n        // 2) Command substitution starts:\n        //      - `$(`   e.g. `$(command)`\n        //      - `${`   some shells/templating forms use this\n        //      - `$[`   other less-common forms or obfuscation using bracket notation\n        //\n        // 3) Backtick execution:\n        //      - `` ` ``  (backtick command substitution)\n        //\n        // 4) Escaped separators (literal backslash before separator):\n        //      - `\\;`  (escaped semicolon)\n        //      - `\\|`  (escaped pipe)\n        //\n        // 5) Line control characters that can be used to inject or terminate commands:\n        //      - newline: `\\n`\n        //      - carriage return: `\\r`\n        //\n        // 6) Null byte:\n        //      - `\\x00` (NULL byte — written as `\\x00` in the regex; commonly shown as `\\0`)\n        this.injectionPattern = /[;\\n\\r\\x00`]|(\\|\\||&&)|\\$\\(|\\$\\{|\\$\\[|\\\\;|\\\\\\||\\|/;\n\n        // Type metadata for ServiceNow framework\n        this.type = 'CommandInjectionChecker';\n    },\n\n    containsCommandInjection: function(input) {\n        // INPUT VALIDATION\n        // Handle null or undefined input - return false (safe default)\n        if (input === null || input === undefined) {\n            gs.debug('CommandInjectionChecker: Null or undefined input provided');\n            return false;\n        }\n        // Handle empty string - return false (safe default)\n        if (input.length === 0) {\n            return false;\n        }\n        // INJECTION DETECTION\n        // Test the input against the compiled regex pattern\n        // Reset the regex lastIndex to ensure proper matching\n        this.injectionPattern.lastIndex = 0;\n\n        // Perform the pattern match\n        var hasInjectionPattern = this.injectionPattern.test(input);\n        // LOGGING FOR SECURITY AUDITING\n        if (hasInjectionPattern) {\n            // Log suspicious input for security monitoring\n            // Truncate very long inputs to prevent log flooding\n            var truncatedInput = input.length > 100 ?\n                input.substring(0, 100) + '...' :\n                input;\n\n            gs.warn('CommandInjectionChecker: Potential command injection detected in input: ' +\n                truncatedInput);\n        }\n        return hasInjectionPattern;\n    },\n    type: 'CommandInjectionChecker'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/CommandInjectionChecker/README.md",
    "content": "The CommandInjectionChecker is a ServiceNow Script Include designed to detect potential command injection attacks in user-supplied strings. It scans for common shell metacharacters and patterns (e.g., ;, |, &, $(), `) using regex, providing a boolean result to flag suspicious inputs. This utility enhances security in server-side automation without relying on external libraries, following ServiceNow best practices for input validation.\n\nExample on how to use it:\n\nvar checker = new CommandInjectionChecker();\n\n// Safe input\nif (!checker.containsCommandInjection('Normal text')) {\n    gs.info('Input is clean.');\n}\n\n// Suspicious input\nif (checker.containsCommandInjection('ls; rm -rf /')) {\n    gs.error('Command injection detected - blocking action.');\n}"
  },
  {
    "path": "Server-Side Components/Script Includes/ConnectionCredentialsUtils/ConnectionCredentialUtils.js",
    "content": "//A script include to retrieve Connection and Credentials Information using Connection Alias (sys_id) in a Scoped Application\n(function () {\n\n    var provider = new sn_cc.ConnectionInfoProvider();\n    var connectionInfo = provider.getConnectionInfo(\"<sys_id of sys_alias record\");\n    if (!gs.nil(connectionInfo)) {\n        // get Connection Record Information (for ex: connection_url)\n        gs.info(\"Connection URL: \" + connectionInfo.getAttribute(\"connection_url\")); // to get other information, replace the connection_url with other field_name available in connection table.\n\n        // get Credential Record Information (for ex: password)\n        gs.info(\"Password: \"+ connectionInfo.getCredentialAttribute(\"password\")); // to get other information, replace password with other field_name available in credenitals table.\n    }\n})();\n"
  },
  {
    "path": "Server-Side Components/Script Includes/ConnectionCredentialsUtils/README.md",
    "content": "# Retrieve Connection and Credentials Information using Connection Alias (sys_id) in a Scoped Application\nI've created a Script Include that enables users to retrieve connection and credentials details associated with a Scoped Application.\n\nIn addition to providing this information, the Script Include also decrypts and returns the value of the password2 field. It supports a wide range of credential types, including:\n\nBasic Authentication credentials\nAPI Key credentials\nWindows credentials\nSSH credentials, and more.\n\nThis tool simplifies access to important connection and credential data for all supported types in the scoped environment.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/ConversationUtils/ConversationUtils.js",
    "content": "var ConversationUtils = Class.create();\nConversationUtils.prototype = {\n\n    // Initialize by creating a new conversation\n    initialize: function(userId, subject) {\n        this.conversationId = this.createConversation(userId, subject);\n\t\t\n    },\n\n    // Create a new conversation with a user\n    // Returns sys_id of the new conversation\n    createConversation: function(userId, subject) {\n\n        // 1. Create the conversation\n        var conversation = sn_connect.Conversation.create({\n            name: subject,\n            type: \"connect\"\n        });\n\n        // 2. Add the provided user to the conversation as a subscriber\n        conversation.addSubscriber(userId);\n\n        // 3. Get the new conversation's Sys ID and return it\n        return this._getConversation(subject);\n    },\n\n    // Send a message to a conversation\n    sendMessage: function(body) {\n\n        // 1. Get the conversation by provided Sys ID\n        var conversation = sn_connect.Conversation.get(this.conversationId);\n\n        // 2. Send the message\n        conversation.sendMessage({\n            body: body\n        });\n\t\t\n    },\n\n    // Since the Conversation API does not provide a GlideRecord object or Sys ID,\n    // look up the most recently created conversation by subject and return the Sys ID\n    _getConversation: function(subject) {\n        var conversationId;\n        var grConvo = new GlideRecord('live_group_profile');\n        grConvo.addQuery('name', 'CONTAINS', subject);\n        grConvo.orderByDesc('sys_created_on');\n        grConvo.query();\n\n        if (grConvo.next()) {\n            conversationId = grConvo.getValue('sys_id');\n        }\n\n        return conversationId;\n    },\n\n    type: 'ConversationUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/ConversationUtils/README.md",
    "content": "# Script Include: ConversationUtils\n\nA simple script include to create a Connect Chat conversation with a single user and send messages to the conversation.\n\n## Example usage\n\n```\nvar conversation = new ConversationUtils(gs.getUserID(), \"Example Conversation\");\nconversation.sendMessage(\"Hello World\");\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Convert base64 to Hex (Object GUID)/README.md",
    "content": "**Description:**\nThis Script Include converts a Base64-encoded Active Directory (AD) Object GUID into its corresponding hexadecimal format.\nWhen importing AD objects from an on-premises directory using LDAP, the object GUIDs are typically stored in Base64 format.\nHowever, in OOB integrations such as the AD V2 Spoke, the GUID must be provided in hexadecimal format. \nThis Script Include bridges that gap by decoding the Base64 string and converting it into the required hex representation.\n\n**Usage:**\nCan be used in the LDAP Transofrm scripts to convert the base64 code to HEX code\n\n**Sample:**\n\nvar base64Code ='ayn8QMpHEGezHQDdAQZi2g==';\ngs.print(new global.LDAP_AD_Utils().base64ToHex(base64Code));\n\n**Output:**\n40fc296b-47ca-6710-b31d-00dd010662da\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Convert base64 to Hex (Object GUID)/script.js",
    "content": "var LDAP_AD_Utils = Class.create();\nLDAP_AD_Utils.prototype = {\n    initialize: function() {},\n\t\n    base64ToHex: function(str) {\n\n        var decoded = GlideStringUtil.base64DecodeAsBytes(str);\n\n        var n = decoded.length;\n\n        if (n < 16) {\n\n            return '';\n\n        }\n\n        var retVal = '';\n\n        retVal = retVal + this.prefixZeros(decoded[3] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[2] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[1] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[0] & 0xFF).toUpperCase();\n\n        retVal = retVal + '-';\n\n        retVal = retVal + this.prefixZeros(decoded[5] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[4] & 0xFF).toUpperCase();\n\n        retVal = retVal + '-';\n\n        retVal = retVal + this.prefixZeros(decoded[7] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[6] & 0xFF).toUpperCase();\n\n        retVal = retVal + '-';\n\n        retVal = retVal + this.prefixZeros(decoded[8] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[9] & 0xFF).toUpperCase();\n\n        retVal = retVal + '-';\n\n        retVal = retVal + this.prefixZeros(decoded[10] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[11] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[12] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[13] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[14] & 0xFF).toUpperCase();\n\n        retVal = retVal + this.prefixZeros(decoded[15] & 0xFF).toUpperCase();\n\n        return retVal.toLowerCase();\n\n    },\n\n\n    prefixZeros: function(value) {\n\n        if (value <= 0xF) {\n\n            return '0' + value.toString(16);\n\n        } else {\n\n            return value.toString(16);\n\n        }\n\n    },\n\n    type: 'LDAP_AD_Utils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Convert image into base64/Convert image into base63 encoded string.js",
    "content": "var BigEncoder64 = Class.create();\n\nBigEncoder64.prototype = {\n\tinitialize: function() {\n    },\n    // This script will help to convert the image into base64 encoded string.\n\tGetBase64EncodedString: function(attachment_sys_id) {\n\t\tvar StringUtil = new GlideStringUtil();\n\t\tvar gsis = GlideSysAttachmentInputStream(attachment_sys_id);  // pass the attahment sys_id\n\t\tvar baos = new Packages.java.io.ByteArrayOutputStream();\n\t\tgsis.writeTo(baos);\n\t\tbaos.close();\n\t\tvar base64Data = StringUtil.base64Encode(baos.toByteArray());\n\t\treturn base64Data;\t// return the base64 encoded string.\n\t},\n\n\ttype: 'BigEncoder64'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Convert image into base64/README.md",
    "content": "This script include helps to convert the image into base64 encoded string easily. Mainly used during the rest integrations when it comes to the handling the attachment.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Copy record Attachment to Email Client/CopyAttachmentToEmailClient.js.js",
    "content": "var CopyAttachmentToEmailClient = Class.create();\n\nCopyAttachmentToEmailClient.prototype = {\n\tinitialize: function() {\n\t},\n\n\tcopyAttachment: function(sourceTable, sourceSysId, targetSysId) {\n\t\tvar attachmentRecord = new GlideRecord('sys_attachment');\n\t\tattachmentRecord.addQuery('table_sys_id', sourceSysId);\n\t\tattachmentRecord.query();\n\t\tif (attachmentRecord.next()){\n\t\t\tGlideSysAttachment.copy(sourceTable, sourceSysId, 'sys_email', targetSysId);\n\t\t}\n\t\n\t}\n\n\ttype: 'CopyAttachmentToEmailClient'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Copy record Attachment to Email Client/ReadMe.md",
    "content": "Use Case: If user click on Compose Email, All the attachment of corresponding record should be attach automatically to email client\n\n\n\n`new global.CopyAttachmentToEmailClient().copyAttachment('incident', current.instance, current.sys\\_id);`\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Count Assigned To Field/README.md",
    "content": "Count Assigned To Field \n\n1. Create  a Script Include\n2. Enable Client Callable\n3. create a Function in the Script Include Class\n4. Do Glide Aggregate to the Incident Table\n5. Get the Parameter from the Client Script\n6. Use the Aggregate - COUNT for the assigned_to field \n7. Use the While Loop\n8. Get the COUNT of the assigned_to field\n9. Return the COUNT to Client Script\n10. Based on the COUNT we can add limit the assignment of the tickets to the assigned users.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Count Assigned To Field/countAssignedUtil.js",
    "content": "var countAssignedUtil = Class.create();\ncountAssignedUtil.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n getCount: function() {\n   var ga = new GlideAggregate('incident');\n   ga.addQuery('assigned_to', this.getParameter('sysparm_assignedto'));\n   ga.addAggregate('COUNT', 'assigned_to');\n   ga.query();\n   while (ga.next()) {\n     var assignedIncident = ga.getAggregate('COUNT', 'assigned_to');\n     return assignedIncident;\n   }\n },\n type: 'countAssignedUtil'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Create Multiple RITMS from MRVS/CreateMultipleRITMSFromMRVS.js",
    "content": "var CreateMultipleRITMSFromMRVS = Class.create();\nCreateMultipleRITMSFromMRVS.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    addRITM: function() {\n        var mrvsList = this.getParameter('sysparm_mrvs_list');\n        var itemSysId = gs.getProperty('property_id'); //System Property containing the sys_id of the Catalog Item\n        var obj = JSON.parse(itemList);\n\n\t\t//Initialize the variables of your Catalog Item\n\t\tvar variables = {\n            'variable1': '',\n            'variable2': '',\n            'variable3': ''\n        };\n\n        var total = obj.length;\n        for (var i = 0; i < total; i++) {\n            variables['variable1'] = obj[i]['mrvs_variable_1'];\n            variables['variable2'] = obj[i]['mrvs_variable_2'];\n            variables['variable3'] = obj[i]['mrvs_variable_3'];\n            var cart = new sn_sc.CartJS();\n            cart.setRequestedFor(gs.getUserID());\n            var item = {\n                'sysparm_id': itemSysId,\n                'sysparm_quantity': '1',\n                'variables': variables\n            };\n            var cartDetails = cart.addToCart(item);\n        }\n        var cartSubmit = cart.checkoutCart();\n        return cartSubmit.request_id;\n    },\n\n    type: 'CreateMultipleRITMSFromMRVS'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Create Multiple RITMS from MRVS/README.md",
    "content": "# Create Multiple RITMS from MRVS\nUse this script to submit multiple Requested Items with data being populated from the MRVS.\nMatch the variables names in the Obj to the respective Catalog Item variable names and the MRVS variables to the variable names for the MRVS\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Custom Discovery Schedule With Freeze Periods/DiscoveryScheduleWithFreezePriod.js",
    "content": "/*\n\tThis script submits the Pre-created discovery schedules (On Demand schedules) but it honours\n\tpre-configured days to be skipped at the begining of the month and days at the end of the month.\n\t\n\tIt also honours the pre-configured times that discovery should be skipped.\n\t\n\tConfigurations required:\n\t\n\t- Standard discovery schedules with Run (On Demand)\n\t- The following sys_properties needs to be configured\n\t\t> VF.Discovery.BOM.DaysToFreeze\n\t\t> VF.Discovery.EOM.DaysToFreeze\n\t\t> VF.Discovery.Freeze.StartTime\n\t\t> VF.Discovery.Freeze.EndTime\n*/\n\nvar VF_DiscoveryScheduleWithFreezePeriod = Class.create();\nVF_DiscoveryScheduleWithFreezePeriod.prototype = {\n\n    // Setting to true will generate a log in System Logs > All\n    _log: false,\n\n    // Prefix to be appended to log files\n    _logPrefix: 'VF: ',\n\n    // Result to be returned\n    _result: {\n        scheduleStatusIds: [],\n        success: false,\n        message: ''\n    },\n\n    _msg: '',\n\n    initialize: function (log) {\n        this._log = log;\n    },\n\n\t/**\n \t* Reads the discovery records where type is 'On Demand'\n \t* Checks the discovery freeze values and calculates if discovery to be submitted or not\n \t* Returns the result including was it successful, the message (if any error or validations) and\n \t* discovery schedule Ids (if schedules were submitted)\n \t*\n \t* @param {GlideDateTime} gdt : Current date as GlideDateTime - Just injecting it for ease of testing\n    */\n    process: function (gdt) {\n\n        try {\n\n            if (!gdt) {\n                throw 'Current date as GlideDateTime is required.';\n            }\n\n            var validation = this.isOutsideFreezePeriodAndTime(gdt);\n            if (!validation.success) {\n                // Its discovery freeze period, so update the result and log if needed\n                this._msg = validation.message;\n                this._logIfApplicable(this._msg);\n                return this._setResult(false, this._msg, null);\n            }\n\n            // **** Get all the schedules that are configured to run 'On Demand' ****\n            var discoverySchedules = new GlideRecord('discovery_schedule');\n            discoverySchedules.addQuery('disco_run_type', 'on_demand');\n            discoverySchedules.query();\n            var totalSchedules = discoverySchedules.getRowCount();\n\n            if (totalSchedules > 0) {\n                // Some schedules are found, verify if it is not freeze period\n                this._logIfApplicable('Discovery Schedules found: ' + totalSchedules);\n\n                // **** Submit the discovieries and hold the status Id ****\n                while (discoverySchedules.next()) {\n                    this._logIfApplicable(\"Processing discovery schedule: \" + discoverySchedules.getValue('name'));\n                    var disco = new Discovery();\n                    var statusId = disco.discoverNow(discoverySchedules);\n                    this._result.scheduleStatusIds.push(statusId);\n                    this._logIfApplicable(\"Discovery StatusId: \" + statusId);\n                }\n\n            } else {\n                // No schedules found, update the result and log if needed\n                this._msg = 'No discovery schedule records where found.';\n                this._logIfApplicable(this._msg);\n                return this._setResult(false, this._msg, null);\n            }\n\n            // If we are here and thing are all good, return the success result\n            return this._setResult(true, '', this._result.scheduleStatusIds);\n\n        } catch (err) {\n            this._msg = this._logPrefix + JSON.stringify(err);\n            gs.error(this._msg);\n            return this._setResult(false, this._msg, null);\n        }\n\n    },\n\n    /**\n    * Function that just validates if the date is outside the freeze period or not\n    * Can be used in UI Actions etc.\n \t*\n \t* @param {GlideDateTime} gdt : Current date as GlideDateTime - Just injecting it for ease of testing\n    */\n    isOutsideFreezePeriodAndTime: function (gdt) {\n\n        if (!gdt) {\n            throw 'Current date as GlideDateTime is required.';\n        }\n\n        var result = {\n            success: false,\n            message: 'It is discovery freeze period and therefore no discovery will be submitted.'\n        };\n\n        try {\n            // Number of days in current month\n            var daysOfTheMonth = gdt.getDaysInMonthLocalTime();\n\n            // Validate system properties are configured and having valid values ****\n            var sysProps = this._readAndValidateSystemProperties(daysOfTheMonth);\n\n            var dayOfMonth = gdt.getDayOfMonthLocalTime();\n\n            // Calculate if its outside the freeze period\n            if (this._isOutsideFreezeDate(daysOfTheMonth, dayOfMonth, sysProps.noOfDaysToFreezeBOM, sysProps.noOfDaysToFreezeEOM)) {\n\n                if (this._isOutsideFreezeTime(sysProps.freezeStartTime, sysProps.freezeEndTime)) {\n                    result.success = true;\n                    result.message = '';\n                } else {\n                    result.message = 'It is discovery freeze time and therefore no discovery will be submitted.';\n                }\n            }\n\n        } catch (err) {\n            this._msg = this._logPrefix + JSON.stringify(err);\n            gs.error(this._msg);\n            result.message = this._msg;\n        }\n\n        return result;\n    },\n\n\t/**\n    * Validates if the time is outside the pre-configured freeze time or not i.e. the time is smaller than start and greater than end times\n \t*\n \t* @param {string} startTime: Freeze start time\n\t* @param {string} endTime: Freeze end time\n    */\n    _isOutsideFreezeTime: function (startTime, endTime) {\n\n        var currentDateTime = new GlideDateTime();\n        var freezeStartTime = this._getDateTime(currentDateTime, startTime);\n        var freezeEndTime = this._getDateTime(currentDateTime, endTime);\n\n        if (currentDateTime.getLocalTime() > freezeStartTime.value && currentDateTime.getLocalTime() < freezeEndTime) {\n            return false;\n        } else {\n            return true;\n        }\n    },\n\n\t/**\n    * Returns GlideDateTime based on string time and current date\n \t*\n \t* @param {GlideDateTime} currentDateTime: base date time\n\t* @param {string} time: time string\n    */\n    _getDateTime: function (currentDateTime, time) {\n        var baseDateTime = currentDateTime.getLocalTime().toString();\n        var baseDate = baseDateTime.split(\" \")[0];\n        var variableTime = new GlideDateTime(baseDate + ' ' + time);\n        return variableTime;\n    },\n\n\n    /**\n    * Determines if the date is outside the freeze period. Returns true if outside freeze period.\n    *\n    * @param {number} daysOfTheMonth : Total days of the month based on GlideDateTime for current date\n    * @param {number} currentDayOfMonth : Current day of the month based on GlideDateTime for current date\n    * @param {number} noOfDaysToFreezeBOM : Total days to freeze discovery schedule at the begining of the month\n    * @param {number} noOfDaysToFreezeEOM : Total days to freeze discovery schedule at the end of the month\n    */\n    _isOutsideFreezeDate: function (daysOfTheMonth, currentDayOfMonth, noOfDaysToFreezeBOM, noOfDaysToFreezeEOM) {\n\n        var startDayOfDiscovery = noOfDaysToFreezeBOM;\n        var endDayOfDiscovery = daysOfTheMonth - noOfDaysToFreezeEOM;\n\n        // For the EOM should be inclusive e.g. 31-2=29 but should include 29 as discovery day\n        if (currentDayOfMonth > startDayOfDiscovery && currentDayOfMonth <= endDayOfDiscovery) {\n            return true;\n        }\n\n        return false;\n    },\n\n    /**\n    * Helper to set the result to be returned\n    *\n    * @param {boolean} successOrFailure : was it successful?\n    * @param {string} message : any message in case of error or validations\n    * @param {array} statusIds : discovery status ids if schedule was submitted successfuly\n    */\n    _setResult: function (successOrFailure, message, statusIds) {\n        this._result.success = successOrFailure;\n        this._result.message = message;\n        this._result.scheduleStatusIds = statusIds;\n\n        return this._result;\n    },\n\n    /**\n    * Reads the system properties and applies the appropriate validations\n    * Throws exception if values are not valid.\n    *\n    * Returns an object containing no of days to freeze BOM and  no of days to freeze EOM\n    *\n    * @param {number} daysOfMonth : Total days of the month based on GlideDateTime for the date\n    */\n    _readAndValidateSystemProperties: function (daysOfMonth) {\n        var noOfDaysToFreezeBOM = parseInt(gs.getProperty('VF.Discovery.BOM.DaysToFreeze'));\n        var noOfDaysToFreezeEOM = parseInt(gs.getProperty('VF.Discovery.EOM.DaysToFreeze'));\n\n        if (noOfDaysToFreezeBOM < 0) {\n            throw 'Please provide a valid value for the System Property: VF.Discovery.BOM.DaysToFreeze';\n        }\n\n        if (noOfDaysToFreezeEOM < 0) {\n            throw 'Please provide a valid value for the System Property: VF.Discovery.EOM.DaysToFreeze';\n        }\n\n        if (noOfDaysToFreezeBOM === 0 && noOfDaysToFreezeEOM === 0) {\n            throw 'Please configure one of the System Properties either VF.Discovery.EOM.DaysToFreeze or VF.Discovery.BOM.DaysToFreeze';\n        }\n\n        if (daysOfMonth > 0 && (noOfDaysToFreezeBOM > 0 || noOfDaysToFreezeEOM > 0)) {\n            if (noOfDaysToFreezeBOM + noOfDaysToFreezeEOM >= daysOfMonth) {\n                throw 'Please configure the System Properties VF.Discovery.EOM.DaysToFreeze and VF.Discovery.BOM.DaysToFreeze to have valid values. This schedule will not run as the sum of days to freeze at BOM and EOM are greater than total days of the month!';\n            }\n        }\n\n        // read start and end times\n        var freezeStartTime = gs.getProperty('VF.Discovery.Freeze.StartTime');\n        var freezeEndTime = gs.getProperty('VF.Discovery.Freeze.EndTime');\n\n        if (!freezeStartTime || !freezeEndTime) {\n            throw 'Please configure a valid discovery freeze start time and end time in sys_properties e.g. VF.Discovery.Freeze.StartTime and VF.Discovery.Freeze.EndTime';\n        }\n\n        return {\n            noOfDaysToFreezeBOM: noOfDaysToFreezeBOM,\n            noOfDaysToFreezeEOM: noOfDaysToFreezeEOM,\n            freezeStartTime: freezeStartTime,\n            freezeEndTime: freezeEndTime\n        };\n    },\n\n    /**\n    * Adds an info log using GlideSystem. Prepends a prefix as needed.\n    * Checks the flag if log is needed or not\n    *\n    * @param {string} message : message to be logged\n    */\n    _logIfApplicable: function (message) {\n\n        if (this._log) {\n            gs.info(this._logPrefix + message);\n        }\n    },\n\n\n    type: 'VF_DiscoveryScheduleWithFreezePeriod'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Custom Discovery Schedule With Freeze Periods/README.md",
    "content": "# Custom Discovery Schedule\n\nOne of the requirements we had from one of our client was that they didn't want discovery schedules to run for few days at the begining of the month (BOM) and few days for the end of the month (EOM) as they internally had to generate some big financial reports and didn't want discovery to take any network resources.\n\nSo this custom discovery script enables to run discovery on daily basis but will ignore the schedules if it was end of month or begining of the month as configured in system properties. There are 4 system properties that are attached as xml and needs to be imported into sys_properties.\n\nThis script submits the Pre-created discovery schedules (On Demand schedules) but it honours\npre-configured days to be skipped at the begining of the month and days at the end of the month.\n\nIt also honours the pre-configured times that discovery should be skipped.\n\n### Configurations required\n\n- Standard discovery schedules with Run (On Demand)\n- The following sys_properties needs to be configured\n    * VF.Discovery.BOM.DaysToFreeze\n    * VF.Discovery.EOM.DaysToFreeze\n    * VF.Discovery.Freeze.StartTime\n    * VF.Discovery.Freeze.EndTime\n\nDetails of sys_properties are below:\n\n| Name | Description  | Type  | Value |\n| :-------------------------:   | :-: | :-: |  :-: |\n| VF.Discovery.Freeze.StartTime | The start time that discovery will not be submitted. Should be in system date/time format e.g. 09:00:00 for 9 am | string | 08:00:00 |\n| VF.Discovery.Freeze.EndTime | The end time that discovery will not be submitted. Should be in system date/time format e.g. 21:00:00 for 9 pm | string | 21:00:00 |\n| VF.Discovery.EOM.DaysToFreeze | Number of days to freeze (skip) discovery at the end of month (EOM) | integer | 2 |\n| VF.Discovery.BOM.DaysToFreeze | Number of days to freeze (skip) discovery at the begin of month (BOM) | integer | 3 |\n\n## Usage\n\n- Create the required sys_properties and set the values as per requirements\n- Create a new schedule job that runs on daily basis\n- Keep the Discovery schedules that you want to be submitted by this script to On Demand\n- Include the following script in the schedule job script\n\n```javascript\n\nvar helper = new VF_DiscoveryScheduleWithFreezePeriod(new GlideDateTime());\nhelper.process();\n\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomArrayUtils/CustomArrayUtils.js",
    "content": "var CustomArrayUtils = Class.create();\nCustomArrayUtils.prototype = {\n\tinitialize: function(useES12 = false) {\n\t\tif (useES12) {\n\t\t\tthis.arrToJSON = this.arrToJSONModern;\n\t\t\tthis.mergeArray = this.mergeArrayModern;\n\t\t\tthis.groupBy = this.groupByModern;\n\t\t}\n\t},\n\n\t// ====================\n\t// ES5 Functions\n\t// ====================\n\n\t/**SNDOC\n    @name arrToJSON\n    @description Convert input array to JSON. If the array is a key-value pair like ['name','John','age','30'], the output would be a JSON Object like {\"name\":\"John\",\"age\":\"30\"}. If the array is not a key-value pair like ['John','30'], the output would be a JSON Object like {\"John\":\"John\",\"30\":\"30\"}.\n    @param  {Array} [array] - Input array to be converted to JSON.\n    @param  {Boolean} [isPair] - Boolean indicating if the array is a key-value pair.\n    @returns {String} JSON string representation of the input array.\n    @throws {Exception} Throws an exception if there's an error during conversion.\n    @example \n        var arrayObj = ['name','John','age','30']; \n        var outputObj = new CustomArrayUtils().arrToJSON(arrayObj, true);\n        gs.info(outputObj);\n        //output: '{\"name\":\"John\",\"age\":\"30\"}'\n    */\n\tarrToJSON: function(array /*Input array */ , isPair /*boolean*/ ) {\n\t\ttry {\n\t\t\tvar jsonObj = {};\n\t\t\tvar myJsonString;\n\t\t\tif (isPair) { // isPair should be true if array is key value pair like ['name','John','age','30']\n\t\t\t\tfor (var i = 0; i < array.length; i = i + 2) {\n\t\t\t\t\tjsonObj[array[i]] = array[i + 1] + '';\n\t\t\t\t}\n\t\t\t\tmyJsonString = JSON.stringify(jsonObj);\n\t\t\t\treturn myJsonString;\n\t\t\t} else { // isPair should be false if array is not a key value pair like ['John','30']\n\t\t\t\tfor (var i = 0; i < array.length; i++) {\n\t\t\t\t\tjsonObj[array[i]] = array[i] + '';\n\t\t\t\t}\n\t\t\t\tmyJsonString = JSON.stringify(jsonObj);\n\t\t\t\treturn myJsonString;\n\t\t\t}\n\t\t} catch (e) {\n\t\t\tgs.info('Exception caught inside  customArrayUtil.arrToJSON: ' + e);\n\t\t}\n\t},\n\n\t/**SNDOC\n\t @name mergeArray\n\t @description Compare two arrays of objects, provide add/remove/edit based on the difference of the two arrays\n\t @param  {Object} [originalArray] - Array of objects is the base of comparison\n\t @param  {Object} [modifiedArray] - Array of objects compared against original to determin add/remove/edit\n\t @param  {String} [uniqueProperty] - Which object property to coalesce typically sys_id\n\t @param  {String} [compareProperty] - Which object property to compare\n\t @returns {Object} Array of objects with included 'action' property indicating add/remove/edit\n      @example \n        // Comparing two arrays of objects\n        var original = [{sys_id: '1', name: 'John'}, {sys_id: '2', name: 'Jane'}];\n        var modified = [{sys_id: '1', name: 'Johnny'}, {sys_id: '3', name: 'Jake'}];\n        var outputObj = new CustomArrayUtils().mergeArray(original, modified, 'sys_id', 'name');\n        gs.info(outputObj);\n        // Returns: \n        // [\n        //    {sys_id: '1', name: 'Johnny', action: 'edit'},\n        //    {sys_id: '2', name: 'Jane', action: 'delete'},\n        //    {sys_id: '3', name: 'Jake', action: 'insert'}\n        // ]\n\t*/\n\tmergeArray: function(originalArray,modifiedArray,uniqueProperty,compareProperty){\n\t\tvar grouped = [originalArray, modifiedArray].reduce(function(acc, arr, i) {\n\t\t\tvar label = i === 0 ? 'orig' : 'mod';\n\t\t\tfor (var j = 0; j < arr.length; j++) {\n\t\t\t\tvar curr = arr[j], id = curr[uniqueProperty];\n\t\t\t\tacc[id] = acc[id] || {orig: null, mod: null };\n\t\t\t\tacc[id][label] = curr;\n\t\t\t}\n\t\t\treturn acc;\n\t\t}, {});\n\n\t\tvar res = [];\n\n\t\tfunction insertObj(o, act) {\n\t\t\tvar newObj = { action: act };\n\t\t\t// iterate existing keys to add to new object\n\t\t\tfor (var k in o) {\n\t\t\t\tif (o.hasOwnProperty(k)) {\n\t\t\t\t\tnewObj[k] = o[k];\n\t\t\t\t}\n\t\t\t}\n\t\t\tres.push(newObj);\n\t\t}\n\n\t\tfor (var key in grouped) {\n\t\t\tvar action, obj;\n\t\t\tif (grouped.hasOwnProperty(key)) { \n\t\t\t\tobj = grouped[key];\n\t\t\t\tif (!obj.orig) {\n\t\t\t\t\tinsertObj(obj.mod, 'insert');\n\t\t\t\t} else if (!obj.mod) {\n\t\t\t\t\tinsertObj(obj.orig, 'delete');\n\t\t\t\t} else if (obj.mod[compareProperty] !== obj.orig[compareProperty] ){\n\t\t\t\t\tinsertObj(obj.mod, 'edit');\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn res;\n\t},\n\n\t/**SNDOC\n    @name groupBy\n    @description Groups array elements based on a key function.\n    @param {Array} array - The array to group.\n    @param {Function} keyFunc - The function that produces the key for grouping.\n    @returns {Map} A map with grouped elements.\n\n    @example\n    var outputObj = new CustomArrayUtils().groupBy(['one', 'two', 'three'], function(word) { return word.length; });\n    var arrayFromMap = Array.from(outputObj.entries());\n    gs.info(JSON.stringify(arrayFromMap));\n    // Returns: Map { 3 => ['one', 'two'], 5 => ['three'] }\n    */\n\tgroupBy: function(array, keyFunc) {\n\t\treturn array.reduce(function(acc, item) {\n\t\t\tvar key = keyFunc(item);\n\t\t\tvar group = acc.get(key) || [];\n\t\t\tgroup.push(item);\n\t\t\tacc.set(key, group);\n\t\t\treturn acc;\n\t\t}, new Map());\n\t},\n\n\t// ====================\n\t// ECMAScript 2021 (ES12)\n\t// ====================\n\n\t/**SNDOC\n    @name arrToJSONModern\n    @description Convert input array to JSON. If the array is a key-value pair like ['name','John','age','30'], the output would be a JSON Object like {\"name\":\"John\",\"age\":\"30\"}. If the array is not a key-value pair like ['John','30'], the output would be a JSON Object like {\"John\":\"John\",\"30\":\"30\"}.\n    @param  {Array} [array] - Input array to be converted to JSON.\n    @param  {Boolean} [isPair] - Boolean indicating if the array is a key-value pair.\n    @returns {String} JSON string representation of the input array.\n    @throws {Exception} Throws an exception if there's an error during conversion.\n    @example \n        let arrayObj = ['name','John','age','30']; \n        let outputObj = new CustomArrayUtils(true).arrToJSON(arrayObj, true);\n        gs.info(outputObj);\n        //output: '{\"name\":\"John\",\"age\":\"30\"}'\n    */\n\tarrToJSONModern: function(array, isPair) {\n\t\ttry {\n\t\t\tconst jsonObj = {};\n\n\t\t\tif (isPair) {\n\t\t\t\tfor (let i = 0; i < array.length; i += 2) {\n\t\t\t\t\tjsonObj[array[i]] = String(array[i + 1]);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tarray.forEach(item => {\n\t\t\t\t\tjsonObj[item] = String(item);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\treturn JSON.stringify(jsonObj);\n\t\t} catch (e) {\n\t\t\tgs.info('Exception caught inside customArrayUtil.arrToJSON: ' + e);\n\t\t}\n\t},\n\n\t/**SNDOC\n\t @name mergeArrayModern\n\t @description Compare two arrays of objects, provide add/remove/edit based on the difference of the two arrays\n\t @note Same as mergeArray just written with ES12!\n     @param  {Object} [originalArray] - Array of objects is the base of comparison\n\t @param  {Object} [modifiedArray] - Array of objects compared against original to determin add/remove/edit\n\t @param  {String} [uniqueProperty] - Which object property to coalesce typically sys_id\n\t @param  {String} [compareProperty] - Which object property to compare\n\t @returns {Object} Array of objects with included 'action' property indicating add/remove/edit\n      @example \n        // Comparing two arrays of objects\n        let original = [{sys_id: '1', name: 'John'}, {sys_id: '2', name: 'Jane'}];\n        let modified = [{sys_id: '1', name: 'Johnny'}, {sys_id: '3', name: 'Jake'}];\n        let outputObj = new CustomArrayUtils(true).mergeArray(original, modified, 'sys_id', 'name');\n        gs.info(outputObj);\n        // Returns: \n        // [\n        //    {sys_id: '1', name: 'Johnny', action: 'edit'},\n        //    {sys_id: '2', name: 'Jane', action: 'delete'},\n        //    {sys_id: '3', name: 'Jake', action: 'insert'}\n        // ]\n\t*/\n\tmergeArrayModern: function(originalArray, modifiedArray, uniqueProperty, compareProperty) {\n\t\tconst grouped = new Map();\n\n\t\t// Process original array\n\t\toriginalArray.forEach(item => grouped.set(item[uniqueProperty], { orig: item }));\n\n\t\t// Process modified array and determine actions\n\t\treturn modifiedArray.map(item => {\n\t\t\tconst origItem = grouped.get(item[uniqueProperty])?.orig;\n\t\t\tif (!origItem) return { ...item, action: 'insert' };\n\t\t\tif (item[compareProperty] !== origItem[compareProperty]) return { ...item, action: 'edit' };\n\t\t\treturn item;\n\t\t})\n\t\t\t.concat(\n\t\t\t// Process deletions\n\t\t\t[...grouped.values()]\n\t\t\t.filter(group => !modifiedArray.some(modItem => modItem[uniqueProperty] === group.orig[uniqueProperty]))\n\t\t\t.map(group => ({ ...group.orig, action: 'delete' }))\n\t\t);\n\t},\n\n\t/**SNDOC\n    @name groupByModern\n    @description Groups array elements based on a key function.\n    @param {Array} array - The array to group.\n    @param {Function} keyFunc - The function that produces the key for grouping.\n    @returns {Map} A map with grouped elements.\n\n    @example\n    let outputObj = new CustomArrayUtils(true).groupBy(['one', 'two', 'three'], word => word.length);\n    let arrayFromMap = Array.from(outputObj.entries());\n    gs.info(JSON.stringify(arrayFromMap));\n    // Returns: Map { 3 => ['one', 'two'], 5 => ['three'] }\n    */\n\tgroupByModern: function(array, keyFunc) {\n\t\treturn array.reduce((acc, item) => {\n\t\t\tconst key = keyFunc(item);\n\t\t\tconst group = acc.get(key) || [];\n\t\t\tgroup.push(item);\n\t\t\tacc.set(key, group);\n\t\t\treturn acc;\n\t\t}, new Map());\n\t},\n\n\ttype: 'CustomArrayUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomArrayUtils/README.md",
    "content": "# CustomArrayUtils\n\n`CustomArrayUtils` is a utility class designed to provide various array manipulation functions. The class offers both ES5 and modern ES12 versions of each function. By default, the ES5 versions are used, but you can opt for the modern ES12 versions by initializing the class with the `useES12` parameter set to `true`.\n\n## Initialization\n\nTo use the ES5 version (default):\n\n```javascript\nvar utils = new CustomArrayUtils();\n```\n\nTo use the ES12 version:\n\n```javascript\nvar utils = new CustomArrayUtils(true);\n```\n\n## Functions\n\n### arrToJSON (ES5)\n\nConverts an input array to JSON.\n\nExample:\n\n```javascript\nvar arrayObj = ['name','John','age','30'];\nvar outputObj = utils.arrToJSON(arrayObj, true);\n```\n\nOutput: `{\"name\":\"John\",\"age\":\"30\"}`\n\n#### arrToJSONModern (ES12)\n\n- Uses `let` and `const` for variable declarations.\n- Adopts arrow functions for concise representation.\n- Utilizes modern array methods like `forEach`.\n\n### mergeArray (ES5)\n\nCompares two arrays of objects and provides add/remove/edit based on the difference of the two arrays.\n\nExample:\n\n```javascript\nvar original = [{sys_id: '1', name: 'John'}, {sys_id: '2', name: 'Jane'}];\nvar modified = [{sys_id: '1', name: 'Johnny'}, {sys_id: '3', name: 'Jake'}];\nvar outputObj = utils.mergeArray(original, modified, 'sys_id', 'name');\n```\n\nOutput:\n\n```\n[\n    {sys_id: '1', name: 'Johnny', action: 'edit'},\n    {sys_id: '2', name: 'Jane', action: 'delete'},\n    {sys_id: '3', name: 'Jake', action: 'insert'}\n]\n```\n\n#### mergeArrayModern (ES12)\n\n- Uses `let` and `const` for variable declarations.\n- Adopts arrow functions for concise representation.\n- Utilizes modern array methods like `map` and `filter`.\n- Uses `Map` for efficient key-value grouping.\n- Utilizes object spread operator (`...`) for merging.\n\n### groupBy (ES5)\n\nGroups array elements based on a key function.\n\nExample:\n\n```javascript\nvar outputObj = utils.groupBy(['one', 'two', 'three'], function(word) { return word.length; });\n```\n\nOutput: `Map { 3 => ['one', 'two'], 5 => ['three'] }`\n\n#### groupByModern (ES12)\n\n- Uses arrow functions for concise representation.\n- Utilizes modern array methods like `reduce`.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomDateUtils/CustomDateUtils.js",
    "content": "var CustomDateUtils = Class.create();\nCustomDateUtils.prototype = {\n    initialize: function() {},\n\n    sortArrayOfDateStrings: function(dates) {\n        dates.sort(function(d1, d2) {\n            var gdt1 = new GlideDate();\n            gdt1.setValue(d1);\n            var gdt2 = new GlideDate();\n            gdt2.setValue(d2);\n            if (gdt1.getValue() < gdt2.getValue())\n                return -1;\n            else if (gdt1.getValue() >= gdt2.getValue())\n                return 1;\n        });\n        return dates;\n    },\n\n    sortArrayOfDateStringsDesc: function(dates) {\n        dates.sort(function(d1, d2) {\n            var gdt1 = new GlideDate();\n            gdt1.setValue(d1);\n            var gdt2 = new GlideDate();\n            gdt2.setValue(d2);\n            if (gdt1.getValue() < gdt2.getValue())\n                return 1;\n            else if (gdt1.getValue() >= gdt2.getValue())\n                return -1;\n        });\n        return dates;\n    },\n\n    sortArrayOfDates: function(dates) {\n        dates.sort(function(d1, d2) {\n            if (d1 < d2)\n                return -1;\n            else if (d1 >= d2)\n                return 1;\n        });\n        return dates;\n    },\n\n    sortArrayOfDatesDesc: function(dates) {\n        dates.sort(function(d1, d2) {\n            if (d1 < d2)\n                return 1;\n            else if (d1 >= d2)\n                return -1;\n        });\n        return dates;\n    },\n\n    type: 'CustomDateUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomDateUtils/README.md",
    "content": "# CustomDateUtils\n\nA collection of scripts related sorting of an array of dates.\n\n## sortArrayOfDateStrings();\n\nThe use of this function is to sort an array of date strings in Ascending order.\n\n### Example\n\n`new CustomDateUtils().sortArrayOfDateStrings([\"2023-06-22\", \"2023-05-04\", \"2023-07-14\", \"2023-04-30\", \"2023-07-14\"])`\n\n## sortArrayOfDateStringsDesc();\n\nThe use of this function is to sort an array of date strings in Descending order.\n\n### Example\n\n`new CustomDateUtils().sortArrayOfDateStringsDesc([\"2023-06-22\", \"2023-05-04\", \"2023-07-14\", \"2023-04-30\", \"2023-07-14\"])`\n\n## sortArrayOfDates();\n\nThe use of this function is to sort an array of GlideDate objects in Ascending order.\n\n### Example\n\n`var d1 = new GlideDate();`\n`d1.setValue(\"2023-06-22\");`\n`var d2 = new GlideDate();`\n`d2.setValue(\"2023-05-04\");`\n`var d3 = new GlideDate();`\n`d3.setValue(\"2023-07-14\");`\n`var d4 = new GlideDate();`\n`d4.setValue(\"2023-04-30\");`\n`var d5 = new GlideDate();`\n`d5.setValue(\"2023-07-14\");`\n\n`new CustomDateUtils().sortArrayOfDates([d1, d2, d3, d4, d5])`\n\n## sortArrayOfDatesDesc();\n\nThe use of this function is to sort an array of GlideDate objects in Descending order.\n\n### Example\n\n`var d1 = new GlideDate();`\n`d1.setValue(\"2023-06-22\");`\n`var d2 = new GlideDate();`\n`d2.setValue(\"2023-05-04\");`\n`var d3 = new GlideDate();`\n`d3.setValue(\"2023-07-14\");`\n`var d4 = new GlideDate();`\n`d4.setValue(\"2023-04-30\");`\n`var d5 = new GlideDate();`\n`d5.setValue(\"2023-07-14\");`\n\n`new CustomDateUtils().sortArrayOfDatesDesc([d1, d2, d3, d4, d5])`\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomObjectUtils/CustomObjectUtils.js",
    "content": "var CustomObjectUtils = Class.create();\nCustomObjectUtils.prototype = {\n\tinitialize: function(useES12 = false) {\n\t\tif (useES12) {\n\t\t\tthis.safeAccess = this.safeAccessModern;\n\t\t}\n\t},\n\n\t// ====================\n\t// ES5 Functions\n\t// ====================\n\n    /**SNDOC\n    @name safeAccess\n    @description Safely accesses nested object properties.\n    @param {Object} obj - The object to access.\n    @param {string} path - The dot-separated path to the property.\n    @returns {*} The accessed value or false if not found.\n \n    @example\n    var myObj = { a: { b: { c: 42 } } };\n    safeAccess(myObj, 'a.b.c');\n    // Returns: 42\n    */\n    safeAccess: function(obj, path) {\n        var parts = path.split('.');\n        for (var i = 0; i < parts.length; i++) {\n            if (obj && obj.hasOwnProperty(parts[i])) {\n                obj = obj[parts[i]];\n            } else {\n                return false;\n            }\n        }\n        return obj;\n    },\n\t\n\n\t// ====================\n\t// ECMAScript 2021 (ES12)\n\t// ====================\n\n   /**SNDOC\n    @name safeAccessModern\n    @description Safely accesses nested object properties.\n    @param {Object} obj - The object to access.\n    @param {string} path - The dot-separated path to the property.\n    @returns {*} The accessed value or false if not found.\n \n    @example\n    const myObj = { a: { b: { c: 42 } } };\n    safeAccess(myObj, 'a.b.c');\n    // Returns: 42\n    */\n    safeAccessModern: function(obj, path) {\n        const value = path.split('.').reduce((o, key) => o?.[key], obj);\n        return value ?? false;\n    },\n\n\t\n\ttype: 'CustomObjectUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomObjectUtils/README.md",
    "content": "# CustomObjectUtils\n\nA utility class to provide methods for safely accessing nested object properties. The class can be initialized to use either ES5 or ES12 (ECMAScript 2021) methods.\n\n## Initialization\n\nTo use the utility, you need to create an instance of the `CustomObjectUtils` class. By default, it uses the ES5 methods. If you want to use the ES12 methods, pass `true` to the constructor.\n\n```javascript\nvar utils = new CustomObjectUtils(); // For ES5\nvar utils = new CustomObjectUtils(true); // For ES12\n```\n\n## Methods\n\n### safeAccess (ES5)\n\nSafely accesses nested object properties.\n\n- **Parameters**:\n  - `obj` (Object): The object to access.\n  - `path` (string): The dot-separated path to the property.\n- **Returns**: The accessed value or `false` if not found.\n- **Example**:\n\n```javascript\nvar myObj = { a: { b: { c: 42 } } };\nvar value = utils.safeAccess(myObj, 'a.b.c');\nconsole.log(value);  // Outputs: 42\n```\n\n### safeAccessModern (ES12)\n\nSafely accesses nested object properties using modern JavaScript features.\n\n#### Modern Features Used:\n\n- **Optional Chaining** (`?.`): Allows reading the value of `key` within a chain of connected objects without having to explicitly check if each reference in the chain is valid.\n- **Nullish Coalescing** (`??`): Returns the right-hand operand when the left operand is `null` or `undefined`.\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomUserUtils/CustomUserUtils.js",
    "content": "var CustomUserUtils = Class.create();\nCustomUserUtils.prototype = {\n    initialize: function() {},\n\n    /*\n    \tParameters:\n    \t\tuser : sys_id of user\n    \t\trole : role name\n    \t\n    \tReturns: boolean\n    \t\t- true \t: if the user have the exact role\n    \t\t- false\t: if the user does not have exact role\n    */\n\n    hasRoleExactly: function(user, role) {\n        var roleGr = new GlideRecord('sys_user_has_role');\n        roleGr.addQuery('user', user);\n        roleGr.addQuery('role.name', role);\n        roleGr.addQuery('state', 'active');\n        roleGr.setLimit(1);\n        roleGr.query();\n\n        if (roleGr.next()) {\n            return true;\n        }\n        return false;\n    },\n\n    /*\n    \tParameters:\n    \t\tuser : sys_id of user\n    \t\trole : list of comma seperated roles\n    \t\n    \tReturns: Boolean\n    \t\t- true \t: if the user have any one of the exact role\n    \t\t- false\t: if the user does not have any one of the exact role\n    */\n    hasAnyRoleExactly: function(user, roles) {\n        var roleGr = new GlideRecord('sys_user_has_role');\n        roleGr.addQuery('user', user);\n        roleGr.addQuery('role.name', 'IN', roles);\n        roleGr.addQuery('state', 'active');\n        roleGr.setLimit(1);\n        roleGr.query();\n\n        if (roleGr.next()) {\n            return true;\n        }\n        return false;\n    },\n\n    /*\n    \tParameters:\n    \t\tuser : sys_id of user\n    \t\trole : list of comma seperated roles\n    \t\n    \tReturns: Boolean\n    \t\t- true \t: if the user have all of the roles exactly\n    \t\t- false\t: if the user does not have any one of the role exactly\n    */\n    hasAllRoles: function(user, roles) {\n        var roleGr = new GlideRecord('sys_user_has_role');\n        roleGr.addQuery('user', user);\n        roleGr.addQuery('role.name', 'IN', roles);\n        roleGr.addQuery('state', 'active');\n        roleGr.query();\n        var actual_roles = [];\n        while (roleGr.next()) {\n            actual_roles.push(roleGr.role.name + '');\n        }\n\t\tvar arrayUtil = new ArrayUtil();\n\t\tactual_roles = arrayUtil.unique(actual_roles);\n        var query_roles = roles.split(',');\n        if (query_roles.length === actual_roles.length) {\n            return true;\n        }\n        return false;\n    },\n\n    /*\n    \tParameters:\n    \t\tuser : sys_id of user\n    \t\tgroup : group name\n    \t\n    \tReturns: Boolean\n    \t\t- true \t: if the user is member of the group\n    \t\t- false\t: if the user is not member of the group\n    */\n    isMemberOf: function(user, group) {\n        var grpGr = new GlideRecord('sys_user_grmember');\n        grpGr.addQuery('user', user);\n        grpGr.addQuery('group.name', group);\n        grpGr.setLimit(1);\n        grpGr.query();\n\n        if (grpGr.next()) {\n            return true;\n        }\n        return false;\n    },\n\n\t/*\n    \tParameters:\n    \t\tuser : sys_id of user\n    \t\tgroups : list of comma seperated group names\n    \t\n    \tReturns: Boolean\n    \t\t- true \t: if the user is member of any of one of the groups\n    \t\t- false\t: if the user is not member of any groups specified\n    */\n    isMemberOfAny: function(user, groups) {\n        var grpGr = new GlideRecord('sys_user_grmember');\n        grpGr.addQuery('user', user);\n        grpGr.addQuery('group.name', 'IN', groups);\n        grpGr.setLimit(1);\n        grpGr.query();\n\n        if (grpGr.next()) {\n            return true;\n        }\n        return false;\n    },\n\n\t/*\n    \tParameters:\n    \t\tuser : sys_id of user\n    \t\tgroups : list of comma seperated group names\n    \t\n    \tReturns: Boolean\n    \t\t- true \t: if the user is member of all the groups specified\n    \t\t- false\t: if the user is not member of all the groups specified \n    */\n    isMemberOfAll: function(user, groups) {\n        var grpGr = new GlideRecord('sys_user_grmember');\n        grpGr.addQuery('user', user);\n        grpGr.addQuery('group.name', 'IN', groups);\n        grpGr.query();\n\t\tvar query_groups = groups.split(',');\n\t\tvar actual_groups = [];\n        while (grpGr.next()) {\n            actual_groups.push(grpGr.group.name + '');\n        }\n\t\tvar arrayUtil = new ArrayUtil();\n\t\tactual_groups = arrayUtil.unique(actual_groups);\n\n\t\tif(query_groups.length === actual_groups.length){\n\t\t\treturn true;\n\t\t}\n        return false;\n    },\n\n    /*\t\n    \tReturns: Array of user SysIDs\n    */\n   \n    lineManagers: function() {\n        var managers = [];\n        var usrGa = new GlideAggregate(\"sys_user\");\n        usrGa.addQuery(\"active\", \"=\", true);\n        usrGa.addQuery(\"manager\", \"!=\", \"\");\n        usrGa.addAggregate(\"COUNT\", \"manager\");\n        usrGa.query();\n        while (usrGa._next()) {\n            managers.push(usrGa.getValue('manager'));\n        }\n        return managers;\n    },\n\n    type: 'CustomUserUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/CustomUserUtils/README.md",
    "content": "# CustomUserUtils\n\nA collection of methods related to user roles, group memberships and other utilities.\n\n## hasRoleExactly();\n\nThe use of this function is to check whether the user have exact role, not admin. This is similar to g_user.hasRoleExactly() used in client side scripting.\n\n### Example\n\n`javascript: new CustomUserUtils().hasRoleExactly('62826bf03710200044e0bfc8bcbe5df1', 'itil'))` \n\n## hasAnyRoleExactly();\n\nThe use of this function is to check whether the user have any one of the exact role specified.\n\n### Example\n\n`javascript: new CustomUserUtils().hasAnyRoleExactly('62826bf03710200044e0bfc8bcbe5df1', 'itil,knowledge'))`\n\n\n## hasAllRoles();\n\nThe use of this function is to check whether the user have if the user have all of the roles specified exactly.\n\n### Example\n\n`javascript: new CustomUserUtils().hasAllRoles('62826bf03710200044e0bfc8bcbe5df1', 'itil,knowledge'))`\n\n\n## isMemberOf();\n\nThe use of this function is to check whether the user is member of the specified group or not.\n\n### Example\n\n`javascript: new CustomUserUtils().isMemberOf('62826bf03710200044e0bfc8bcbe5df1', 'Hardware'))`\n\n## isMemberOfAny();\n\nThe use of this function is to check whether the user is member of any one of the specified groups or not.\n\n### Example\n\n`javascript: new CustomUserUtils().isMemberOfAny('62826bf03710200044e0bfc8bcbe5df1', 'Service Desk, Hardware, Database'))`\n\n## isMemberOfAll();\n\nThe use of this function is to check whether the user is member of all of the specified groups or not.\n\n### Example\n\n`javascript: new CustomUserUtils().isMemberOfAll('62826bf03710200044e0bfc8bcbe5df1', 'Service Desk, Hardware, Database'))`\n\n## lineManagers();\n\nThe use of this function is to get the managers (users who have atleast one direct reportee).\n\n### Example\n\n`new CustomUserUtils().lineManagers()`\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Data Lookup Table Utils/DataLookupUtils.js",
    "content": "var DataLookupUtils = Class.create();\n\nDataLookupUtils.prototype = {\n    initialize: function (tableName, sortByColumn) {\n        this.tableName = tableName;\n        this.columnNames = this._getColumnNames(tableName);\n        this.queryColumns = this.columnNames;\n\n        sortByColumn = sortByColumn || false;\n        this.sortColumn = sortByColumn ? DataLookupUtils.SORT_BY_COLUMN : DataLookupUtils.SORT_BY_ORDER; // optional, use the field being retrieved or the built in order field\n    },\n\n    /**\n     * query lookup data\n     * can accept either an array of keys, or multiple parameters\n     * e.g\n     *    getLookupData('key1', 'key2', 'key3');\n     *    getLookupData(['key1', 'key2', 'key3']);\n     */\n    getLookupData: function (_keys) {\n        var keys = [];\n        if (_keys && typeof _keys != 'object') {\n            for (var _arg in arguments) {\n                keys.push(arguments[_arg]);\n            }\n        } else {\n            keys = _keys;\n        }\n\n        if (keys.length >= this.queryColumns.length) {\n            NiceError.raise(gs.getMessage(\"Too many keys ({0}) provided.  Maximum is {1}\", [keys.length.toString(), (this.queryColumns.length - 1).toString()]));\n        }\n\n        try {\n            var fieldIdx = 0;\n            var fieldName = this.queryColumns[fieldIdx];\n            var gq = new global.GlideQuery(this.tableName)\n                .where('active', true);\n\n            if (keys.length > 0) {\n                // loop through the keys\n                keys.forEach(function (key, idx, arr) {\n                    gq = gq.where(fieldName, key);\n                    fieldName = this.queryColumns[++fieldIdx];\n                }, this);\n            }\n\n            gq = gq.orderBy(this.sortColumn == DataLookupUtils.SORT_BY_COLUMN ? fieldName : 'order')\n                .whereNotNull(fieldName)\n                .select(fieldName)\n                .map(function (_x) {\n                    // just need the data, not the other stuff\n                    return _x[fieldName];\n                })\n                .reduce(function (arr, e) {\n                    // remove duplicates\n                    if (arr.indexOf(e) == -1) arr.push(e);\n                    return arr;\n                }, []);\n\n            return gq;\n        } catch (e) {\n            NiceError.raise(e);\n        }\n    },\n\n    /**\n     * override the columns to use for lookups\n     * \n     */\n    setColumns: function (columns) {\n        if (columns.length == 0) return;\n\n        this.queryColumns = [];\n        columns.forEach(function (_col) {\n            if (this.columnNames.indexOf(_col) > -1) {\n                this.queryColumns.push(_col);\n            } else {\n                NiceError.raise(gs.getMessage('Cannot find column {0}.  Valid columns are {1}', [_col, this.columnNames.join(', ')]));\n            }\n        }, this);\n\n    },\n\n    /**\n     * build the list of column names for the table\n     * use the default system view for the table and exclude all system and inherited fields\n     * column order determines key lookup\n     */\n    _getColumnNames: function () {\n        return new global.GlideQuery('sys_ui_list_element')\n            .where('list_id.name', this.tableName)\n            .where('list_id.view', 'Default view')\n            // ignore any system or inherited fields from the table\n            .where('element', 'NOT IN', ['sys_class_name', 'sys_created_by', 'sys_created_on', 'sys_id', 'sys_mod_count', 'sys_name', 'sys_package', 'sys_policy', 'sys_scope', 'sys_updated_by', 'sys_updated_on', 'sys_update_name', 'active', 'order'])\n            .whereNull('list_id.sys_user') // make sure we get the system view\n            .orderBy('position')\n            .select('element')\n            .reduce(function (arr, e) {\n                arr.push(e.element);\n                return arr;\n            }, []);\n    },\n\n    type: 'DataLookupUtils'\n};\n\nDataLookupUtils.SORT_BY_ORDER = 0;\nDataLookupUtils.SORT_BY_COLUMN = 1;\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Data Lookup Table Utils/README.md",
    "content": "# Data Lookup Table Utils\n\nThis script include provides a quick method of looking up data from any table extended from dl_matcher (Data Lookup Matcher Rules).  \n\nIt will build a list of the columns for the specified table and then allow you to query based on an array of values.\n\nFor example, using the dl_u_priority table we could lookup the Urgency values for a given Impact as follows;\n\n```javascript\nvar lib = new global.DataLookupUtils(\"dl_u_priority\")\n\nvar lookupData = lib.getLookupData(\"1\");\ngs.info(lookupData);\n```\nThis will return *1,2,3*.\n\nBy also passing in a second value we can filter on Urgency and Impact;\n\n```javascript\nvar lib = new global.DataLookupUtils(\"dl_u_priority\")\n\nvar lookupData = lib.getLookupData([\"1\", \"2\"]);\ngs.info(lookupData);\n```\nThis will return *2*.\n\nWe could also ignore the Impact column and lookup Priority for a given Urgency by setting our own lookup columns;\n\n```javascript\nvar lib = new global.DataLookupUtils(\"dl_u_priority\")\nlib.setColumns([\"urgency\", \"priority\"]);\n\nvar lookupData = lib.getLookupData([\"3\"]);\ngs.info(lookupData);\n```\nThis will return *3,4,5*\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Data Normalizer/README.md",
    "content": "DataNormalizer is a reusable Script Include for standardizing and cleaning data in ServiceNow. It normalizes names, emails, phone numbers, addresses, and dates, ensuring consistent formatting across modules. It supports server-side and client-side (GlideAjax) calls.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Data Normalizer/dataNormalizer.js",
    "content": "/**\n * DataNormalizer Script Include\n * ----------------------------------------------------------------\n * Provides reusable utility functions to standardize and sanitize\n * common data fields such as names, emails, phone numbers, addresses,\n * and dates. Supports both server-side and client-side (GlideAjax)\n * for flexible integration across multiple modules.\n * ----------------------------------------------------------------\n */\n\nvar DataNormalizer = Class.create();\nDataNormalizer.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    initialize: function() {\n        this.debug = true; // Toggle debug logging\n    },\n\n    /**\n     * Logs structured error messages to system logs.\n     * @param {String} method - Method name where the error occurred.\n     * @param {Object} error - JavaScript error object.\n     * @param {Object} [context] - Optional additional context data.\n     */\n    logError: function(method, error, context) {\n        gs.error('[DataNormalizer] Error in {0}: {1} | Context: {2}',\n            method, error.message, JSON.stringify(context || {}));\n    },\n\n    /**\n     * Logs informational messages when debug mode is enabled.\n     * @param {String} message - The message to log.\n     */\n    logInfo: function(message) {\n        if (this.debug)\n            gs.info('[DataNormalizer] {0}', message);\n    },\n\n    /**\n     * Normalizes personal names by removing special characters\n     * and applying proper case formatting.\n     * Example: \"JOHN DOE\" → \"John Doe\"\n     *\n     * @param {String} name - Input name value.\n     * @returns {String} - Normalized name.\n     */\n    normalizeName: function(name) {\n        try {\n            if (!name) return '';\n            name = name.trim().replace(/[^a-zA-Z\\s]/g, '');\n            var formatted = name.split(/\\s+/)\n                .map(function(w) {\n                    return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();\n                })\n                .join(' ');\n            this.logInfo('normalizeName: \"' + name + '\" → \"' + formatted + '\"');\n            return formatted;\n        } catch (e) {\n            this.logError('normalizeName', e, { name: name });\n            return name;\n        }\n    },\n\n    /**\n     * Normalizes email addresses by trimming whitespace,\n     * converting to lowercase, and removing invalid characters.\n     *\n     * @param {String} email - Input email value.\n     * @returns {String} - Normalized email.\n     */\n    normalizeEmail: function(email) {\n        try {\n            if (!email) return '';\n            var clean = email.trim().toLowerCase().replace(/[,;]+/g, '');\n            this.logInfo('normalizeEmail: \"' + email + '\" → \"' + clean + '\"');\n            return clean;\n        } catch (e) {\n            this.logError('normalizeEmail', e, { email: email });\n            return email;\n        }\n    },\n\n    /**\n     * Standardizes phone numbers by removing non-numeric characters\n     * and adding country codes where applicable.\n     *\n     * @param {String} phone - Input phone number.\n     * @param {String} [defaultCountryCode] - Optional default country code (e.g., \"91\").\n     * @returns {String} - Normalized phone number.\n     */\n    normalizePhone: function(phone, defaultCountryCode) {\n        try {\n            if (!phone) return '';\n            var digits = phone.replace(/\\D/g, '');\n            var result;\n\n            if (digits.length == 10 && defaultCountryCode)\n                result = '+' + defaultCountryCode + ' ' + digits;\n            else if (digits.length > 10 && digits.startsWith('91'))\n                result = '+' + digits.slice(0, 2) + ' ' + digits.slice(2);\n            else\n                result = '+' + digits;\n\n            this.logInfo('normalizePhone: \"' + phone + '\" → \"' + result + '\"');\n            return result;\n        } catch (e) {\n            this.logError('normalizePhone', e, { phone: phone });\n            return phone;\n        }\n    },\n\n    /**\n     * Cleans and formats postal addresses by removing excess spaces\n     * and applying title case to each word.\n     *\n     * @param {String} address - Input address text.\n     * @returns {String} - Normalized address.\n     */\n    normalizeAddress: function(address) {\n        try {\n            if (!address) return '';\n            address = address.trim().replace(/\\s{2,}/g, ' ');\n            var result = address.split(' ')\n                .map(function(w) {\n                    return w.charAt(0).toUpperCase() + w.slice(1).toLowerCase();\n                })\n                .join(' ');\n            this.logInfo('normalizeAddress: \"' + address + '\" → \"' + result + '\"');\n            return result;\n        } catch (e) {\n            this.logError('normalizeAddress', e, { address: address });\n            return address;\n        }\n    },\n\n    /**\n     * Converts date strings into system-standard display format\n     * using GlideDateTime APIs.\n     *\n     * @param {String} dateStr - Input date string.\n     * @returns {String} - Normalized date in display format.\n     */\n    normalizeDate: function(dateStr) {\n        try {\n            if (!dateStr) return '';\n            var gdt = new GlideDateTime(dateStr);\n            var formatted = gdt.getDate().getDisplayValue();\n            this.logInfo('normalizeDate: \"' + dateStr + '\" → \"' + formatted + '\"');\n            return formatted;\n        } catch (e) {\n            this.logError('normalizeDate', e, { date: dateStr });\n            return dateStr;\n        }\n    },\n\n    /**\n     * Generic text cleanup. Removes invalid placeholders such as\n     * 'N/A', '--', or empty values.\n     *\n     * @param {String} value - Input text value.\n     * @returns {String} - Cleaned text.\n     */\n    cleanValue: function(value) {\n        try {\n            if (!value) return '';\n            var cleaned = value.trim();\n            var invalid = ['n/a', 'none', 'na', '-', '--'];\n            if (invalid.indexOf(cleaned.toLowerCase()) > -1) return '';\n            this.logInfo('cleanValue: \"' + value + '\" → \"' + cleaned + '\"');\n            return cleaned;\n        } catch (e) {\n            this.logError('cleanValue', e, { value: value });\n            return value;\n        }\n    },\n\n    /**\n     * Dynamically detects field type and applies the corresponding\n     * normalization method.\n     *\n     * @param {String} fieldName - The field name or label.\n     * @param {String} value - The value to normalize.\n     * @returns {String} - Normalized value.\n     */\n    smartNormalize: function(fieldName, value) {\n        try {\n            if (!value) return '';\n            var field = fieldName.toLowerCase();\n            var result;\n\n            if (field.indexOf('email') >= 0)\n                result = this.normalizeEmail(value);\n            else if (field.indexOf('phone') >= 0 || field.indexOf('mobile') >= 0)\n                result = this.normalizePhone(value, '91');\n            else if (field.indexOf('name') >= 0)\n                result = this.normalizeName(value);\n            else if (field.indexOf('address') >= 0)\n                result = this.normalizeAddress(value);\n            else if (field.indexOf('date') >= 0)\n                result = this.normalizeDate(value);\n            else\n                result = this.cleanValue(value);\n\n            this.logInfo('smartNormalize: Field=' + fieldName + ', Result=' + result);\n            return result;\n        } catch (e) {\n            this.logError('smartNormalize', e, { fieldName: fieldName, value: value });\n            return value;\n        }\n    },\n\n    /**\n     * Client-callable method exposed via GlideAjax.\n     * Accepts parameters from client scripts and returns\n     * normalized values synchronously.\n     *\n     * @returns {String} - Normalized value for client-side use.\n     */\n    normalizeFromClient: function() {\n        try {\n            var field = this.getParameter('sysparm_fieldName');\n            var value = this.getParameter('sysparm_value');\n            var normalized = this.smartNormalize(field, value);\n            return normalized;\n        } catch (e) {\n            this.logError('normalizeFromClient', e, { field: field, value: value });\n            return value;\n        }\n    },\n\n    type: 'DataNormalizer'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Delete Multiple Records Async/DeleteMultipleRecordsAsync.js",
    "content": "var DeleteMultipleRecordsAsync = Class.create();\nDeleteMultipleRecordsAsync.prototype = {\n    initialize: function() {\n\t\t\n\t\tthis.secondsBetweenChunks = 1;\n\t\t\n    },\n\n\t/* Delete all records asynchronously */\n\tdeleteRecordsAsync : function(tableName, encodedQuery, executeBusinessRules, chunkSize){\t\t\t\t\n\t\t\n\t\tthis._validateVariables(tableName, encodedQuery, chunkSize);\n\t\tthis._scheduleNextJob(tableName, encodedQuery, executeBusinessRules, chunkSize);\n\t\t\n\t},\n\t\n\t/* Delete chunk of records and schedule next chunk asynchronously */\n\tdeleteAndScheduleNext : function(tableName, encodedQuery, executeBusinessRules, chunkSize){\n\t\t\n\t\tthis._validateVariables(tableName, encodedQuery, chunkSize);\n\t\t\n\t\tvar dtStart = new GlideDateTime();\n\t\t\n\t\tvar rowCount = 0;\n\t\tvar grRecordsToDelete = new GlideRecord(tableName);\t\t\n\t\t\n\t\tgrRecordsToDelete.addEncodedQuery(encodedQuery);\n\t\tgrRecordsToDelete.setLimit(chunkSize);\n\t\t\n\t\tif(executeBusinessRules == \"false\"){\t\t\t\n\t\t\tgrRecordsToDelete.setWorkflow(false);\t\t\t\n\t\t}\n\t\t\n\t\tgrRecordsToDelete.query();\n\t\t\n\t\twhile(grRecordsToDelete.next()){\n\t\t\t\n\t\t\trowCount++;\n\t\t\t\n\t\t\tgrRecordsToDelete.deleteRecord();\n\t\t\t\n\t\t}\n\t\t\t\t\n\t\tvar dtEnd = new GlideDateTime();\n\t\tvar duration = GlideDateTime.subtract(dtStart, dtEnd);\n\t\t\n\t\tvar logMessage = \"Deleted \" + rowCount + \" records in \" + duration.getDisplayValue() + \".\";\n\t\t\n\t\tif(rowCount == chunkSize){\t\t\t\n\t\t\t\n\t\t\tthis._scheduleNextJob(tableName, encodedQuery, executeBusinessRules, chunkSize);\n\t\t\t\n\t\t\tlogMessage += \" Next chunk scheduled to run in \" + this.secondsBetweenChunks + \" second(s).\";\n\t\t\t\n\t\t}\n\t\telse{\n\t\t\t\n\t\t\tlogMessage += \" Job completed.\";\n\t\t\t\n\t\t}\n\t\t\n\t\tgs.log(logMessage, \"DeleteMultipleRecordsAsync\");\n\t\t\n\t},\n\t\n\t/* create a new scheduled job for chunk delete */\n\t_scheduleNextJob : function(tableName, encodedQuery, executeBusinessRules, chunkSize){\n\t\t\n\t\tvar scriptString = \"\";\n\t\t\n\t\tscriptString += \"var delScript = new DeleteMultipleRecordsAsync(); \\n\";\n\t\tscriptString += \"delScript.deleteAndScheduleNext('\" + tableName + \"', '\" + encodedQuery + \"', '\" + executeBusinessRules + \"', \" + chunkSize + \");\";\n\t\t\n\t\tvar bulkStartTime = new GlideDateTime();\n\t\t\t\t\n\t\tbulkStartTime.addSeconds(this.secondsBetweenChunks);\n\t\t\n\t\tvar grTrigger = new GlideRecord(\"sys_trigger\");\n\t\t\n\t\tgrTrigger.initialize();\n\t\tgrTrigger.name = \"DeleteMultipleRecordsAsync\";\n\t\tgrTrigger.next_action = bulkStartTime;\n\t\tgrTrigger.script = scriptString;\n\t\tgrTrigger.job_id.setDisplayValue('RunScriptJob');\n\t\tgrTrigger.state = 0; /* Ready */\n\t\tgrTrigger.trigger_type = 0; /* Run Once */\n\t\t\n\t\tgrTrigger.insert();\n\t\t\n\t},\n\t\n\t_validateVariables : function(tableName, encodedQuery, chunkSize){\n\t\t\n\t\tif(tableName == undefined || tableName == \"\"){\n\t\t\tthrow \"Table name must be provided.\";\n\t\t}\n\t\t\n\t\tif(encodedQuery == undefined || encodedQuery == \"\"){\n\t\t\tthrow \"Encoded query must be provided.\";\n\t\t}\n\t\t\n\t\tif(chunkSize == undefined || !parseInt(chunkSize) > 0 ){\n\t\t\tthrow \"Chunk size must be a number higher than 0.\";\n\t\t}\n\t\t\n\t},\n\t\n    type: 'DeleteMultipleRecordsAsync'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Delete Multiple Records Async/README.md",
    "content": "This script include provides a way to delete multiple records asynchronously, with the option to define chunk sizes. \nIt is useful for deleting huge amount of data without overoading the instance.\nSince this is a script for DELETE operations, use with caution!\nNB. The process needs to be triggered by one-time scheduled job.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Deprecate Field/README.md",
    "content": "# Deprecate Field\n\nThis function `deprecateField` is design to depreacte particular field of the table so Customers will not use it for any further use.\n\nExample:\n\n```\ndeprecateField('service_offering', 'task')\n```\n\nResult:\n![Alt text](Result.png)\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Deprecate Field/script.js",
    "content": "function deprecateField(fieldName, tableName) {\n  if (!GlideTableDescriptor.fieldExists(tableName, fieldName)) return;\n  var deprecationLabel = \" (deprecated)\";\n  var dictGr = new GlideRecord(\"sys_documentation\");\n  dictGr.addQuery(\"name\", tableName);\n  dictGr.addQuery(\"element\", fieldName);\n  dictGr.addEncodedQuery(\"labelNOT LIKE(deprecated)\");\n  dictGr.query();\n  if (dictGr.next()) {\n    dictGr.setValue(\"label\", dictGr.label + deprecationLabel);\n    dictGr.setValue(\"plural\", dictGr.plural + deprecationLabel);\n    dictGr.setValue(\"hint\", dictGr.hint + deprecationLabel);\n    dictGr.update();\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Developer Debug Utility/DeveloperDebugUtility.js",
    "content": "var DeveloperDebugUtility = Class.create();\nDeveloperDebugUtility.prototype = {\n    initialize: function() {},\n\n    // Checks if debug logging is enabled via system property\n    \n    _enable_debug: function() {\n        // System Property: enable_debug_for_scripts (true/false)\n        return gs.getProperty('enable_debug_for_scripts') == 'true';\n    },\n\n     // Controlled debug logger\n     // {String} message - The message to log\n    _debug: function(message) {\n        if (this._enable_debug()) {\n            gs.info('[DEBUG LOG] ' + message);\n        }\n    },\n\n     // Example function where controlled debugging can be used\n    addFunction: function(data) {\n        try {\n            // Example logic: simulate some operation\n            this._debug('Executing addFunction with data: ' + JSON.stringify(data));\n\n            // core logic here\n           \n            this._debug('addFunction executed successfully.');\n        } catch (e) {\n            this._debug('Error executing addFunction:\\n' + e);\n        }\n    },\n\n    type: 'DeveloperDebugUtility'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Developer Debug Utility/README.md",
    "content": "# Developer Debug Utility (Controlled Logging)\nCreate a systemProperty - enable_debug_for_scripts (Boolean value)\n\n# Overview\nThis utility provides a centralized, configurable debug logging mechanism for developers.  \nInstead of using gs.info(), gs.log(), or gs.warn() - which create permanent logs in the system, developers can now log messages conditionally through a system property.\n\nWhen the property 'enable_debug_for_scripts' is set to 'true', debug messages are logged; otherwise, all debug calls are ignored.  \nThis makes it ideal for debugging issues in Production without modifying code or flooding system logs.\n\n\n# Objective\nTo provide a reusable, lightweight debugging utility that allows developers to:\n- Enable/disable debug logs globally via a system property.  \n- Avoid unnecessary system log entries when debugging is not needed.  \n- Maintain clean, controlled, and consistent debug output across server-side scripts.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Dynamic Dropdown List/Client.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n  \n  \n        // Do nothing when loading the form or no change in the category.  \n\t      if(isLoading || newValue == oldValue){\n     \t      return;\n    \t  }\n\n        //Hide Subcategory if the selction of category is 'None'.\n        if (newValue == '') {\n            g_form.setMandatory('subcategory', false);\n            g_form.setDisplay('subcategory', false);\n        }\n        //Clear the subcategory before adding new options values.\n        g_form.clearOptions('subcategory');\n\n        //Get the list of Subcategories based on the selection of Category value.\n        var ajax_query = new GlideAjax('DynamicListAjax');\n        ajax_query.addParam('sysparm_name', 'getList');\n        ajax_query.addParam('sysparm_table', 'incident');\n        ajax_query.addParam('sysparm_dependent', newValue);\n        ajax_query.addParam('sysparm_element', 'subcategory');\n        ajax_query.getXML(loadSubCategory);\n\n        function loadSubCategory(response) {\n\n            var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n            //add the choice list to the subcategory if exist.\n            if (answer) {\n                answer = Array.from(new Set(answer.split(','))).toString();\n                var subcat_list = answer.split(\",\");\n\n                g_form.addOption('subcategory', '', '-- None --');\n              \n                //add the list of options retrieved to the Subactegory field\n                for (var i = 0; i < returned_subcat_list.length; i++) {\n                    if (returned_subcat_list[i] != \"\") {\n                        var value_label_pair = subcat_list[i].split(':');\n                        g_form.addOption('subcategory', value_label_pair[0], value_label_pair[1]);\n                    }\n                }\n                g_form.setDisplay('subcategory', true);\n                g_form.setMandatory('subcategory', true);\n            } else { // hide subcategory if no choice list exist\n                g_form.setMandatory('subcategory', false);\n                g_form.setDisplay('subcategory', false);\n            }\n        }\n\n    }\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Dynamic Dropdown List/README.md",
    "content": "The client script and script include can be used to load a options of a dropdown list where dependency exist on another dropdownlist. Like Category - Subcategory.\nAdiitional functionality added in the client script is, when there are no options available for the selected dependent value, we can hide the dropdown field.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Dynamic Dropdown List/UtilScript.js",
    "content": "var DynamicListAjax = Class.create();\nDynamicListAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\t\n\tgetList : function(sysparm_table,sysparm_dependent,sysparm_element){\n\t\tvar list={};\n    \t\tvar table_name = this.getParameter('sysparm_table');\n\t\tvar dependent_value = this.getParameter('sysparm_dependent');\n\t\tvar element = this.getParameter('sysparm_element');\n\t\tvar sys_choice_gr = new GlideRecord('sys_choice');\n\t\tsys_choice_gr.addQuery('name',table_name);\n\t\tsys_choice_gr.addQuery('dependent_value', dependent_value);\n\t\tsys_choice_gr.addQuery('element', element);\n\t\tsys_choice_gr.addQuery('inactive',false);\n\t\tsys_choice_gr.orderByDesc('sequence');\n\t\tsys_choice_gr.query();\n\t\tif(sys_choice_gr.hasNext())\n\t\t\t{\n\t\t\t\twhile(sys_choice_gr.next())\n\t\t\t\t\t{\n\t\t\t\t\t\tlist[sys_choice_gr.label] = sys_choice_gr.value;\n\t\t\t\t\t}\n\t\t\t}\n\t\treturn JSON.stringify(list);\n\t\t\n\t},\n  \n    type: 'DynamicListAjax'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Dynamic Record Archiving/README.md",
    "content": "#  Dynamic Record Archiving Logic\n\n##  Overview\nThis snippet provides a reusable logic to archive or flag records in ServiceNow that are older than a specified threshold. It helps maintain data hygiene, improve performance, and support compliance with data retention policies.\n\nYou can choose between two modes:\n- **Flag Mode**: Marks records as archived using a custom field (`u_archived`).\n- **Move Mode**: Moves records to a corresponding archive table (e.g., `incident_archive`) and deletes the original.\n\n---\n\n##  Use Case\n- Archive incidents older than 1 year.\n- Clean up old tasks, requests, or custom records.\n- Automate data lifecycle management via Scheduled Jobs.\n\n---\n\n##  Parameters\n| Parameter           | Description                                                                 |\n|---------------------|-----------------------------------------------------------------------------|\n| `tableName`         | Name of the table to archive (e.g., `incident`)                             |\n| `dateField`         | Date field to filter by (e.g., `opened_at`, `created_on`)                   |\n| `archiveThresholdDays` | Number of days before today to consider for archiving (e.g., `365`)         |\n| `archiveMode`       | `'flag'` to mark records, `'move'` to transfer to archive table             |\n\n---\n\n##  Example Usage\n```javascript\nvar archiver = new RecordArchiver('incident', 'opened_at', 365, 'flag');\narchiver.archiveRecords();\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Dynamic Record Archiving/code.js",
    "content": "var RecordArchiver = Class.create();\nRecordArchiver.prototype = {\n    initialize: function(tableName, dateField, archiveThresholdDays, archiveMode) {\n        this.tableName = tableName;\n        this.dateField = dateField;\n        this.archiveThresholdDays = archiveThresholdDays;\n        this.archiveMode = archiveMode || 'flag'; // 'flag' or 'move'\n    },\n\n    archiveRecords: function() {\n        var thresholdDate = new GlideDateTime();\n        thresholdDate.addDaysUTC(-this.archiveThresholdDays);\n\n        var gr = new GlideRecord(this.tableName);\n        gr.addQuery(this.dateField, '<=', thresholdDate);\n        gr.query();\n\n        var count = 0;\n        while (gr.next()) {\n            if (this.archiveMode === 'flag') {\n                gr.setValue('u_archived', true); // Assumes a custom field 'u_archived'\n                gr.update();\n            } else if (this.archiveMode === 'move') {\n                this._moveToArchiveTable(gr);\n            }\n            count++;\n        }\n\n        gs.info('Archived ' + count + ' records from ' + this.tableName);\n    },\n\n    _moveToArchiveTable: function(record) {\n        var archiveGr = new GlideRecord(this.tableName + '_archive');\n        archiveGr.initialize();\n\n        var fields = record.getFields();\n        for (var i = 0; i < fields.size(); i++) {\n            var fieldName = fields.get(i).getName();\n            if (archiveGr.isValidField(fieldName)) {\n                archiveGr.setValue(fieldName, record.getValue(fieldName));\n            }\n        }\n\n        archiveGr.insert();\n        record.deleteRecord();\n    },\n\n    type: 'RecordArchiver'\n};\n\n// Example usage:\n//var archiver = new RecordArchiver('incident', 'opened_at', 365, 'flag'); // Archive incidents older than 1 year\n//archiver.archiveRecords();\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Emotion-Aware/EmotionAnalyzer.js",
    "content": "//Scenario: whenever a user submits a ticket (Incident, HR Case, etc.), the system analyzes the tone of the message - like frustrated, urgent, calm, confused \n// and automatically adjusts the ticket priority, or routes it to a specific team (like “Empathy Response Desk)\n\nvar EmotionAnalyzer = Class.create();\nEmotionAnalyzer.prototype = {\n    initialize: function() {},\n\n    detectEmotion: function(text) {\n        try {\n            // ---- Mock Sentiment Logic (No external API) ----\n            text = text.toLowerCase();\n            var score = 0;\n            var negativeWords = ['angry', 'frustrated', 'bad', 'urgent', 'hate', 'delay'];\n            var positiveWords = ['thank', 'happy', 'great', 'awesome', 'love'];\n            \n            negativeWords.forEach(function(word) {\n                if (text.includes(word)) score -= 1;\n            });\n            positiveWords.forEach(function(word) {\n                if (text.includes(word)) score += 1;\n            });\n\n            var sentiment = (score > 0) ? 'positive' : (score < 0) ? 'negative' : 'neutral';\n            return { sentiment: sentiment, score: Math.abs(score / 3) };\n        } catch (e) {\n            gs.error(\"EmotionAnalyzer error: \" + e.message);\n            return { sentiment: 'neutral', score: 0 };\n        }\n    },\n\n    type: 'EmotionAnalyzer'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Emotion-Aware/README.md",
    "content": "### Overview\nThe **Emotion-Aware Ticket Prioritizer** is an AI-driven innovation for ServiceNow that automatically analyzes the tone and emotion of user-submitted tickets (Incidents, HR Cases, etc.) to determine the urgency and emotional state of the user.  \nIf frustration or urgency is detected, the system dynamically increases the **priority**, adds contextual **work notes**, and routes the ticket to the right team — ensuring faster resolution and better user experience.\n\n---\n\n## How It Works\n1. When a ticket is created, a **Business Rule** triggers a **Script Include** (`EmotionAnalyzer`).\n2. The Script Include analyzes the short description and description text.\n3. It detects emotional tone — *positive*, *neutral*, or *negative*.\n4. Based on sentiment, the system:\n   - Adjusts **priority** automatically  \n   - Adds a **work note** with detected emotion  \n   - Optionally, notifies the support team for urgent or frustrated cases  \n\n---\n## How It Trigger Script Include Via Business Rule \n1. Create object of Script Include (Accessible from all scopes)\n    var util = new global.EmotionAnalyzer();\n\n----\n## Example line as input and output\n| User Input                             | Detected Emotion | Auto Priority | Output                |\n| -------------------------------------- | ---------------- | ------------- | --------------------- |\n| “Laptop crashed again, no one helps!”  | Negative         | 1 (Critical)  | Escalate to VIP queue |\n| “Thank you, system working great now!” | Positive         | 4 (Low)       | No action             |\n| “Need help resetting my password.”     | Neutral          | 3 (Moderate)  | Normal SLA            |\n\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/EvtMgmtCustom_PostTransformHandler/EvtMgmtCustom_PostTransformHandler.js",
    "content": "/**\n @Param event - GlideRecord representing the event after transform\n @Param origEventSysId - Id of the event. The GlideRecord event parameter is a temporary object, and therefore does not contain the id of the original event.\n*/\n(function postTransformHandler(event, origEventSysId){\n  gs.log('PostTransformHandler custom script is active'); \n\t// Make any changes to the alert which will be created out of this Event\n\t// Note that the Event itself is immutable, and will not be changed in the database\n\tif(event.source == 'Your Source' && event.resource == 'Your Resource')  //this script to action only in certain source and resource values.\n\t{\n  //Checking if there's already an processed event in the system with same source , resource , message key, again we can add more query paramteters here or modify this query as per the requirement.\n  var checkAlert = new GlideRecord('em_event');\n\tcheckAlert.addQuery('source',event.source);\n\tcheckAlert.addQuery('resource',event.resource);\n\tcheckAlert.addQuery('severity',event.severity);\n\tcheckAlert.addQuery('message_key',event.message_key);\n\tcheckAlert.addQuery('stateNOT INReady,Ignored,Error');\t\n\tcheckAlert.orderByDesc('time_of_event');\n\tcheckAlert.query();\n\tif(checkAlert.next())   //Event found\n \t\t{  \n\t\t\t var limit = '4';  //set limit to 4 hours we can make changes to this as per the requirement. \n\t\t\t var newDate = event.time_of_event.toString();   \n\t\t\t var oldDate = checkAlert.time_of_event.toString(); \n\t\t\t var gDate = new GlideDateTime(newDate); \n\t\t\t var sDate = new GlideDateTime(oldDate); \n\t\t\t var diffSeconds = gs.dateDiff(gDate.getDisplayValue(),sDate.getDisplayValue(),true); //calculating the different between current time of event and previous time of event.\n\t\t\t var diff_sec = Math.abs(diffSeconds);  \n\t\t\t var hours = diff_sec/3600;  //Converting the seconds to hours for comparison with the limit value.\n       if(limit < hours)  //If hours is more than limit we apent the message key with time of event for new event before being processed as alert. This will create a new alert with the updated message key , instead of updating a exisiting one. \n\t\t\t\t{\n\t\t\t\tvar m_key = event.message_key+'+'+event.time_of_event;\n\t\t\t\tevent.setValue('message_key',m_key);\n\t\t\t\t} \n\t\t}\n\t}\n\t// To abort event processing and ignore the event, return false;\n\t// returning a value other than boolean will result in an error\n\treturn true;  \n \n})(event, origEventSysId);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/EvtMgmtCustom_PostTransformHandler/README.md",
    "content": "## EvtMgmtCustom_PostTransformHandler Script Include\n\nI came across a requirement where we wanted to a create a new alert/incident when the same event is encountered after a certain amount of time, even if the existing alert/incident are still open. OOTB if this is the case and same event comes into the system, it will go ahead and append it to the existing alert. \n\nSo, I made the EvtMgmtCustom_PostTransformHandler as active and scripted the code as per my requirement, here I query for an existing event based on certain parameters(check script) and if a event is found, I calculate the difference between current time of event and previous time of event. If difference is more than desired limit, I apent the message key with time of event for new event before being processed as alert. This will create a new alert with the updated message key and create a new incident.\n\nNote: This script is present in the system to make any changes to the alert which will be created out of this Event. The Event itself is immutable, and will not be changed in the database\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Excel Attachment Via script/README.md",
    "content": "Using Excel Attachment feature you can export the table contents in excel. This script include can be use for making an excel sheet from ServiceNow script and adding it to a record as an attachment.\nUsage Example:\n\n```\nvar row = [{'number':'inc000001','short_description':'test'},{'number':'inc000002','short_description':'test2'}];\n\nvar table = 'incident'; //Table name\nvar recordId = '552c48888c033300964f4932b03eb092'; //sysid of the record\nvar fileName = 'ExampleEXL'; //File name\nvar headerColumns = ['Number','Summary']; //Excel Header Columns\n\nvar attach = new excelAttachment();\nattach.addExcelAttachment(table, recordId, fileName, row, headerColumns);\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Excel Attachment Via script/excelAttachment.js",
    "content": " var excelAttachment = Class.create();\nexcelAttachment.prototype = {\n    initialize: function() {},\n    addExcelAttachment: function(table, recordId, fileName, row, headerColumns) //RecordId is a sysid of the record for the attachment\n    {\n        /* headerColumns variable should contains the comma seperated headers for excel header for ex: Number,Summary,Description,Requester\n        Row variable should be a JSON Array for ex: [{'number':'inc000001','short_description':'test'}] */\n\n        var attachment = new Attachment();\n        fileName = fileName + \".csv\";\n        var content_type = 'text/csv';\n        var fileBytes = '';\n        fileBytes += headerColumns.toString() + \"\\n\";\n        \n        for (var i = 0; i < row.length; i++) {\n\t\t\tvar jsRow = row[i];\n            for (var j in jsRow) {\n                fileBytes += jsRow[j] + ',';\n            }\n            fileBytes += '\\n';\n        }\n        var attachmentRec = attachment.write(table, recordId, fileName, content_type, fileBytes);\n\t\t\n\t\treturn attachmentRec;\n    },\n    type: 'excelAttachment'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Excel Parser/README.md",
    "content": "Server side Excel parser utility leveraging the GlideExcelParser API.\n\nPass in the sys_id of an Excel attachment and get back an array of objects, with every row in the sheet being an element in the array.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Excel Parser/excelParser.js",
    "content": "var ExcelParser = Class.create();\nExcelParser.prototype = {\n    initialize: function() {\n    },\n\n    readData: function(attachmentID) {\n        //Initialize the GlideSysAttachment call for retrieval of our sheet\n        var attachment = new GlideSysAttachment();\n        var attachmentStream = attachment.getContentStream(attachmentID);\n\n        //Begin to parse the excel sheet for relevant data\n        var parser = new sn_impex.GlideExcelParser();\n        parser.parse(attachmentStream);\n        parser.setHeaderRowNumber(1);\n        var headers = parser.getColumnHeaders(); // Array of headers\n        var excelData = []; // An array that will hold the data from each line as an object\n\n        while (parser.next()) {\n            var row = parser.getRow();\n            if (JSUtil.notNil(row))\n                excelData.push(row);\n        }\n        return excelData;\n    },\n\n    type: 'ExcelParser'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Execution Time Tracker/README.md",
    "content": "\n# Script Execution Time Tracker\n\nThis snippet helps developers measure how long their server-side scripts take to run in ServiceNow.\nUseful for performance optimization and debugging slow background scripts or Script Includes.\n\n## Example Use Case\n- Measure performance of a GlideRecord query or function execution.\n- Log the execution time to the system logs.\n\n## How It Works\nThe script uses timestamps before and after execution to measure elapsed time.\n\n## Usage\nWrap your logic between `start` and `stop`, or use the Script Include:\n\n```javascript\nvar timer = new ExecutionTimeTracker();\n// ... your code ...\ntimer.stop('My Script');\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Execution Time Tracker/execution_time_tracker.js",
    "content": "var ExecutionTimeTracker = Class.create();\nExecutionTimeTracker.prototype = {\n    initialize: function() {\n        this.startTime = new Date().getTime();\n    },\n\n    stop: function(label) {\n        var endTime = new Date().getTime();\n        var duration = endTime - this.startTime;\n        gs.info((label || 'Execution') + ' completed in ' + duration + ' ms');\n        return duration;\n    },\n\n    type: 'ExecutionTimeTracker'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Extending OOB TableUtils/EXT_TablesUtils.js",
    "content": "var EXT_TableUtils = Class.create();\n\nEXT_TableUtils.prototype = Object.extendsObject(TableUtils, {\n\n    initialize: function(tableName) {\n        TableUtils.prototype.initialize.call(this, tableName);        \n    },\n\n    /**SNDOC\n        @name getFieldsAndAttributes\n        @description Returns a data structure with field name and field properties for a given table.\n        OOB getFields() methods from either GlideRecord() or GlideRecordUtil()\n        only work with an existing record and not just with the table name. This one goes\n        to sys_dictionary directly and therefore does not need a valid GlideRecord to work.\n        The returned object has this structure:\n        {\n            <field_name_1>: {\n               field_label: <label>,\n               field_size: <size>,\n               field_type: <type>,\n               reference_table: <table> (only for reference or glide_list types)\n            },\n            <field_name_2>: {\n               ...\n            }\n        }\n        @example\n        var fields = new EXT_TableUtils('incident').getFieldsAndAttributes();\n        for (var fieldName in fields) {\n            gs.print('Field ' + fieldName + ' is of type ' + fields[fieldName].field_type);\n        }\n        @returns {object} [fields]\n        */\n\n    getFieldsAndAttributes: function() {\n\n        var fields = {};\n\n        // Get all the table names in the hierarchy and turn it into an array\n        // getHierarchy() is a method from the parent class TableUtils\n\n        var tableHierarchy = this.getHierarchy(this.tableName);\n        \n        // Go find all the fields for all the tables of the hierarchy\n\n        var dicGr = new GlideRecord('sys_dictionary');\n\n        dicGr.addQuery('name', 'IN', j2js(tableHierarchy).join(','));       \n        dicGr.addEncodedQuery('internal_type!=collection^ORinternal_type=NULL');\n        dicGr.query();\n\n        while (dicGr.next()) {\n\n            var fieldName = dicGr.getValue('element');\n\n            fields[fieldName] = {};\n            fields[fieldName].field_label = dicGr.getValue('column_label');\n            fields[fieldName].field_size = dicGr.getValue('max_length');\n\n            fields[fieldName].field_type = dicGr.getValue('internal_type');\n            if (fields[fieldName].field_type === 'reference' || fields[fieldName].field_type === 'glide_list') {\n                fields[fieldName].reference_table = dicGr.getValue('reference');\n            }\n            \n        }\n\n        return fields;\n\n    },\n\n    type: 'EXT_TableUtils'\n\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/Extending OOB TableUtils/README.md",
    "content": "# Extending the baseline TableUtils Script Include\n\nThis code snippet extends the out of the box __Table Utils__ Script Include by adding a method to retrieve all fields -and the properties- of a given table. There is already a baseline API called _getFields()_ that does the same, but it required an existing GlideRecord to perform it. There are cases where we want the fields of a table without necessarily having a GlideRecord.\n\nThe code snippet also shows how to extend an existing class and how to invoke the parent class constructor to ensure proper behavior.\n\nIt is invoked like this:\n```\nnew EXT_TableUtils(<table_name>).getFieldsAndAttributes();\n```\n\nSo for example with the _incident_ table:\n```\nvar fields = new EXT_TableUtils('incident').getFieldsAndAttributes();\nfor (var fieldName in fields) {\n    gs.debug('Field ' + fieldName + ' is of type ' + fields[fieldName].field_type);\n}\n```\n\nAnd returns a JSON structure of this format:\n```\n{\n    <field_name_1>: {\n        field_label: <label>,\n        field_size: <size>,\n        field_type: <type>,\n        reference_table: <table> (only for reference or glide_list types)\n    },\n    <field_name_2>: {\n        ...\n    }\n}\n```\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Filter record/read.md",
    "content": "This Script Include is useful for:\n\nFiltering user records based on field from table data.\nPopulating reference fields or dropdowns dynamically via GlideAjax.\nClient-side filtering based on server-side data logic.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Filter record/script.js",
    "content": "/*This Script Include is useful for:\n\nFiltering user records based on field from table data.\nPopulating reference fields or dropdowns dynamically via GlideAjax.\nClient-side filtering based on server-side data logic.*/\n\nvar <Script_include_name> = Class.create();\n<Script_include_name>.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    <functionname>: function() {\n        var validID = [];\n        var gr = new GlideAggregate(\"<table_name>\");\n\t\tgr.addQuery(\"field\",\"value\");\n        gr.addAggregate(\"COUNT\");\n        gr.groupBy(\"fieldname\");\n        gr.query();\n        while (gr.next()) {\n            var id = gr.getValue(\"fieldname\");\n\n            validID.push(id);\n\n        }\n\n        var varname = \"user_nameIN\" + validID;\n        return varname;\n    },\ntype: '<Script_include_name>'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Financial Service Utilities/FinancialServiceUtilities.js",
    "content": "var FinancialServiceUtilities = Class.create();\nFinancialServiceUtilities.prototype = {\n    initialize: function() {\n    },\n\n    /**\n     * Calculates simple interest.\n     *\n     * @param {number} principal - The principal amount.\n     * @param {number} rate - The annual interest rate (in percentage).\n     * @param {number} time - The time period in years.\n     * @returns {number} The calculated simple interest.\n     */\n    calculateInterest: function(principal, rate, time) {\n        // Simple interest calculation\n        return (principal * rate * time) / 100;\n    },\n\n    /**\n     * Calculates compound interest.\n     *\n     * @param {number} principal - The principal amount.\n     * @param {number} rate - The annual interest rate (in percentage).\n     * @param {number} time - The time period in years.\n     * @param {number} compoundingFrequency - The number of times interest is compounded per year.\n     * @returns {number} The calculated compound interest.\n     */\n    calculateCompoundInterest: function(principal, rate, time, compoundingFrequency) {\n        // Compound interest calculation\n        var compoundInterest = principal * Math.pow(1 + (rate / compoundingFrequency), compoundingFrequency * time);\n        return compoundInterest - principal;\n    },\n\n    /**\n     * Formats a currency amount.\n     *\n     * @param {number} amount - The amount to be formatted.\n     * @param {string} currencyCode - The currency code (e.g., \"USD\", \"EUR\").\n     * @returns {string} The formatted currency amount.\n     */\n    formatCurrency: function(amount, currencyCode) {\n        // Format the amount as currency\n        return gs.formatNumber(amount, currencyCode);\n    },\n\n    /**\n     * Calculates monthly loan payments.\n     *\n     * @param {number} principal - The principal loan amount.\n     * @param {number} rate - The annual interest rate (in percentage).\n     * @param {number} term - The loan term in years.\n     * @returns {number} The calculated monthly loan payment.\n     */\n    calculateLoanPayments: function(principal, rate, term) {\n        // Calculate monthly loan payments (assuming monthly compounding)\n        var monthlyInterestRate = rate / 12 / 100;\n        var numberOfPayments = term * 12;\n        var monthlyPayment = principal * monthlyInterestRate * Math.pow(1 + monthlyInterestRate, numberOfPayments) / (Math.pow(1 + monthlyInterestRate, numberOfPayments) - 1);\n        return monthlyPayment;\n    },\n\n    type: 'FinancialServiceUtilities'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Financial Service Utilities/README.md",
    "content": "**Financial Service Utilities Script Include**\n\n\nThis Script Include provides a collection of utility functions commonly used in financial services applications. \nIt includes functions for calculating interest, formatting currency, and calculating loan payments.\n\n**Functions:**\n**calculateInterest(principal, rate, time)**: Calculates simple interest.\nprincipal: The principal amount.\nrate: The annual interest rate (in percentage).\ntime: The time period in years.\nReturns the calculated simple interest.\n\n**calculateCompoundInterest(principal, rate, time, compoundingFrequency)**: Calculates compound interest.\nprincipal: The principal amount.\nrate: The annual interest rate (in percentage).\ntime: The time period in years.\ncompoundingFrequency: The number of times interest is compounded per year.\nReturns the calculated compound interest.\n\n**formatCurrency(amount, currencyCode):** Formats a currency amount.\namount: The amount to be formatted.\ncurrencyCode: The currency code (e.g., \"USD\", \"EUR\").\nReturns the formatted currency amount.\n\n**calculateLoanPayments(principal, rate, term):** Calculates monthly loan payments.\nprincipal: The principal loan amount.\nrate: The annual interest rate (in percentage).\nterm: The loan term in years.\nReturns the calculated monthly loan payment.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Find months between two dates/DateUtil.js",
    "content": "var DateUtils = Class.create();\nDateUtils.prototype = {\n    initialize: function () { },\n\n    getMonths: function (startDate, endDate) {\n\n        var MONTHS_IN_A_YEAR = 12;\n        var totalMonths = 0;\n\n        var startDateGd = new GlideDate();\n        startDateGd.setValue(startDate);\n        var startDateTimeGdt = new GlideDateTime(startDateGd);\n        var startDateMonth = startDateTimeGdt.getMonthLocalTime();\n        var startDateYear = startDateTimeGdt.getYearLocalTime();\n\n        var endDateGt = new GlideDate();\n        endDateGt.setValue(endDate);\n        var endDateTimeGdt = new GlideDateTime(endDateGt);\n        var endDateMonth = endDateTimeGdt.getMonthLocalTime();\n        var endDateYear = endDateTimeGdt.getYearLocalTime();\n\n        if (startDateYear != endDateYear) {\n            totalMonths = MONTHS_IN_A_YEAR - startDateMonth + endDateMonth + (endDateYear - startDateYear - 1) * MONTHS_IN_A_YEAR;\n        } else {\n            totalMonths = endDateMonth - startDateMonth;\n        }\n\n        return totalMonths;\n\n    },\n\n    type: 'DateUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Find months between two dates/README.md",
    "content": "# getMonths\n\nHelper function that calculates total months between two specified dates e.g. total months between 2020-01-01 and\n2022-01-01 is 24!\n\n## Usage\n\n```javascript\nvar helper = new DateUtils();\n\ngs.log(helper.getMonths('2020-01-01', '2022-01-01')); // This returns 24\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Generate QR Code and attach to RITM/README.md",
    "content": "# Create QR Code\nThis sample script uses a free QR Code API and does a REST call to the server and returns the QR code as image. Then attaches\nthe QR code into Requested Item record.\n\nIn the script, replace the Requested Item sys_id with sys_id of your record. Change the text to the text you want to appear in the QR Code."
  },
  {
    "path": "Server-Side Components/Script Includes/Generate QR Code and attach to RITM/script.js",
    "content": "var ritm_sys_id = '4060f68007cd30100779fea89c1ed0a2'; // Replace to your RITM sys_id\nvar qrText = \"Test\"; // Your text to convert to QR code e.g. your URL etc\n\nvar current = new GlideRecord('sc_req_item');\n\nif (current.get(ritm_sys_id)) {\n\n\tvar baseURL = \"https://api.qrserver.com/v1/create-qr-code/\";\n\tvar qrData = \"?data=\" + qrText + \"&size=100x100\";\n\n\tvar requestUrl = encodeURI(baseURL +  qrData);\n\tvar request = new sn_ws.RESTMessageV2();\n\trequest.setHttpMethod('get');\n\trequest.setEndpoint(requestUrl);\n\trequest.setRequestHeader(\"Content-Type\", \"image/jpeg\");\n\trequest.saveResponseBodyAsAttachment(\"sc_req_item\", ritm_sys_id, qrText); // Save the body as attachment\n\tvar pdfContentResponse = request.execute();\n\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Approvers of a Ticket/GetApproversForATicket.js",
    "content": "var GetApproversForATicket = Class.create();\nGetApproversForATicket.prototype = {\n    initialize: function() {},\n\n    getApprovers: function(sysId) {\n\n        var approvalUsers = [];\n        var grApproval = new GlideRecord('sysapproval_approver');\n        grApproval.addQuery('sysapproval.sys_id', sysId);\n        grApproval.query();\n\n        while (grApproval.next()) {\n            approvalUsers.push(grApproval.approver.toString());\n        }\n\n        return approvalUsers;\n    },\n\n    type: 'GetApproversForATicket'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Approvers of a Ticket/README.md",
    "content": "# Get Approvers of a Ticket\n\nThis script returns the number of approvers sys_id that are being requested for Approval"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Choice Display Value/README.md",
    "content": "# Get Selected Choice Display Value\n\nThis scripts gets the display value of the selected choice. \n\n> Method: getChoiceDisplayValue(question, value)\n\n-   @param {String} question: Name of the question that needs to be queried\n-   @param {String} value: Value of that question"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Choice Display Value/getChoiceDisplayValue.js",
    "content": "var GetChoiceDisplayValue = Class.create();\nGetChoiceDisplayValue.prototype = {\n    initialize: function() {},\n\t\n    getChoiceDisplayValue: function(question, value) {\n        try {\n            var displayValue = '';\n            var grQuestionChoice = new GlideRecord('question_choice');\n\n            grQuestionChoice.addEncodedQuery('question=' + question + '^value=' + value);\n            grQuestionChoice.query();\n\n            if (grQuestionChoice.next())\n                displayValue = grQuestionChoice.text + '';\n\n            return displayValue;\n        } catch (exception) {\n            gs.error(\"getChoiceDisplayValue() - \" + exception);\n        }\n    },\n\n    type: 'GetChoiceDisplayValue'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Current User Information/README.md",
    "content": "# Get Current User Information\nWe frequently need essential current user information such as userId, name, sysId, email, and roles on the client side. To streamline this process, I've developed a Script Include that can be invoked directly from the client side.\nThis Script Include fetches and provides an object encapsulating the current user's userId, firstName, lastName, email, roles, and sysId for easy access.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Current User Information/getCurrentUserInformation.js",
    "content": "var GetCurrentUserInfo = Class.create();\nGetCurrentUserInfo.prototype = {\n    initialize: function() {},\n\n    getCurrentUserInfo: function() {\n        var currentUser = gs.getUser(); //This will give the reference to current user object.\n        var userId = currentUser.getName();\n        var firstName = currentUser.getFirstName();\n        var lastName = currentUser.getLastName();\n        var email = currentUser.getEmail();\n        var sysId = currentUser.getID();\n        var roles = currentUser.getRoles();\n\n        return {\n            userId: userId,\n            firstName: firstName,\n            lastName: lastName,\n            email: email,\n            roles: roles,\n            sysId: sysId\n        };\n    },\n    type: 'GetCurrentUserInfo'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Field Label in Specific Language/README.md",
    "content": "# Get Field Label in Specific Language\n\nThis Script Include enables retrieving the label of a certain table field in any given language. \nOften in multi-language instances, the label of a field must be accessed via a script (e.g. to add it to the HTML of an email or add it to another fields like the description or work notes). Instead of retrieving the value each time individually this script include can be re-used (e.g. by multiple Business Rules or Mail Scripts). \n\n### Instruction\n\nCall the Script Include via the following example code from any server-side script (e.g. Business Rule, Mail Script or Flow Action). If you want, you can also make the Script Include client callable and make it available to Client Scripts. Make sure to provide the correct table name, field name and language in your function call. Be aware, that in some cases the field may be located on a parent table. In this case, you would have to provide the parent table name to the script. \n\n```javascript\nvar util =new LanguageUtils();\ngs.log(util.getLabel(\"incident\", \"category\", \"de\"));\n```\n\n\n### Benefits\n- If no label in the provided language can be found, the default English label is returned\n- Re-use this Script Include and call it from different types of scripts (e.g. Business Rule, Mail Script or Flow Action)\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Field Label in Specific Language/script.js",
    "content": "var LanguageUtils = Class.create();\nLanguageUtils.prototype = {\n    getLabel: function(table, variable, language) {\n\n        var grLabel = new GlideRecord(\"sys_documentation\");\n        grLabel.addQuery(\"name\", table);\n        grLabel.addQuery(\"element\", variable);\n        grLabel.addQuery(\"language\", language);\n        grLabel.query();\n\n        if (grLabel.next()) {\n            return grLabel.getValue(\"label\");\n        } else { //if no label in the given language can be found return the default English label instead\n            var grEnLabel = new GlideRecord(\"sys_documentation\");\n            grEnLabel.addQuery(\"name\", table);\n            grEnLabel.addQuery(\"element\", variable);\n            grEnLabel.addQuery(\"language\", \"en\");\n            grEnLabel.query();\n\n            if (grEnLabel.next()) {\n                gs.log(\"The variable \" + variable + \" does not have a label in \" + language + \" associated to it, in the following table: +\" + table);\n                return grEnLabel.getValue(\"label\");\n            } else {\n                gs.log(\"The variable \" + variable + \" can not be found in the following table: +\" + table);\n                return;\n            }\n\n        }\n    },\n\n    type: 'LanguageUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Group Members/README.md",
    "content": "# Get Group Members by Id\n\nA Script utils containing utility functions, to get the group members sys ids"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Group Members/getGroupMembers.js",
    "content": "var GetGroupMembers = Class.create();\nGetGroupMembers.prototype = {\n    initialize: function() {},\n    \n    getGroupMember: function(groupId) {\n        var id = [];\n        var grGroupMember = new GlideRecord('sys_user_grmember');\n\n        grGroupMember.addQuery(\"group.sys_id\", groupId);\n        grGroupMember.query();\n\n        while (grGroupMember.next()) {\n            id.push(grGroupMember.getValue(\"user\"));\n        }\n\n        return id.join();\n    },\n\n    type: 'GetGroupMembers'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Profile Picture/README.md",
    "content": "Retrieves the user photo from the Live Profile, if possible, otherwise gets the photo from the user record if possible.\nReturns the path to the photo or an empty string\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Profile Picture/getProfilePicture.js",
    "content": "var cf_LiveProfile = Class.create();\ncf_LiveProfile.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    /*\n    * Retrieves the user photo from the Live Profile, if possible, otherwise\n    * gets the photo from the user record if possible.\n    *\n    * Returns the path to the photo or an empty string\n    */\n    getPhoto: function(){\n        // Get the Sys ID of the user that we're retrieving the photo for\n        var user_id = this.getParameter('sysparm_user_id');\n        gs.log(\"getPhoto called for: \" + user_id, \"cf_LiveProfile\");\n        var photo_path;\n       \n        // Query for the live profile record\n        var live_profile_gr = new GlideRecord('live_profile');\n        live_profile_gr.addQuery('document', user_id);\n        live_profile_gr.query();\n        if(live_profile_gr.next()) {\n            if(live_profile_gr.photo.getDisplayValue()){\n                photo_path = live_profile_gr.photo.getDisplayValue();\n                gs.log(\"Retrieved photo from live profile: \" + photo_path, \"cf_LiveProfile\");\n\n            }\n        }\n        // Check to see if we have a photo from the profile\n        if(!photo_path){\n            // No profile photo found, query for the user photo\n            var user_gr = new GlideRecord('sys_user');\n            user_gr.addQuery('sys_id', user_id);\n            user_gr.query();\n            if(user_gr.next()) {\n                photo_path = user_gr.photo.getDisplayValue();\n                gs.log(\"Retrieved photo from user record: \" + photo_path, \"cf_LiveProfile\");\n            } else {\n                photo_path = '';\n                gs.log(\"No photo found\", \"cf_LiveProfile\");\n            }\n        }\n        return photo_path;\n    },\n\n    type: 'cf_LiveProfile'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Reference Display Value/README.md",
    "content": "# Get Reference Choice Display Value\n\nThis scripts gets the display value of the selected reference value. \n\n> Method: getChoiceDisplayValue(question, value)\n\n-   @param {String} table: name of the referenced table.\n-   @param {String} value: Value of that reference field"
  },
  {
    "path": "Server-Side Components/Script Includes/Get Reference Display Value/getReferenceDisplayValue.js",
    "content": "var GetReferenceDisplayValue = Class.create();\nGetReferenceDisplayValue.prototype = {\n    initialize: function() {},\n\n    getReferenceDisplayValue: function(table, value) {\n        try {\n            var displayValue = '';\n            var grReferenceTable = new GlideRecord(table);\n\n            grReferenceTable.addQuery('sys_id', 'IN', value);\n            grReferenceTable.query();\n\n            while (grReferenceTable.next())\n                displayValue += grReferenceTable.getDisplayValue() + ', ';\n\n            return displayValue.slice(0, -2);\n        } catch (exception) {\n            gs.error(\"getReferenceDisplayValue() - \" + exception);\n        }\n    },\n\n    type: 'GetReferenceDisplayValue'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Get User Data by Id/README.md",
    "content": "# Get User data by Id\n\nThis Script Include can be called by the client side.\nReceives an Sys ID and return the JSON object containing the respective User record from the sys_user table.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Get User Data by Id/script.js",
    "content": "var GetUserData = Class.create();\nGetUserData.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\t//Receive User sys_id and return User data\n    GetUserBy_id: function() {\n        var obj = {};\n        var userID = this.getParameter('sysparm_userid');\n        var myuser = new GlideRecord('sys_user');\n        myuser.addQuery('sys_id', userID);\n        myuser.query();\n\n        if (myuser.next()) {\n            obj.sys_id = myuser.getValue('sys_id') || '';\n            obj.first_name = myuser.getValue('first_name') || '';\n            obj.last_name = myuser.getValue('last_name') || '';\n            obj.email = myuser.getValue('email') || '';\n        }\n        return JSON.stringify(obj);\n    },\n\n    type: 'GetUserData'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetCallerDetails/Calling Script Include from client.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n   if (isLoading || newValue === '') {\n      return;\n   }\n   var aj = new GlideAjax(\"GetCallerDetails\");  // This is the name of script include, as per the name of your requirement.\n   aj.addParam('sysparm_name',\"demoTest\");  // This is calling a defined function in script include.\n   aj.addParam('sysparm_caller_id',g_form.getValue('caller_id')); // getting a caller_id\n   aj.getXML(callback);\n   function callback(response){  // creating a callback function to store the response getting from script include.\n\tvar answer = response.responseXML.documentElement.getAttribute('answer');\n\t//alert(answer); // This will alert the details.\n\t// Commented above code and replaced it with GlideModal\n        var gm = new GlideModal(\"glide_alert_standard\", false, 600);\n        gm.setTitle(\"Caller Details\");\n        gm.setPreference(\"title\", answer.toString());\n        gm.setPreference(\"warning\", \"false\");\n        gm.render();\n   }\n   \n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetCallerDetails/README.md",
    "content": "This script include will help to get the details of caller Id_name like (email, first_name, last_name user_name etc.) mentioned in incident form with the help of on change client script by iniating a Ajax call.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetCallerDetails/scriptinclude.js",
    "content": "var GetCallerDetails = Class.create(); // This will create a new class.\nGetCallerDetails.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\tdemoTest:function(){\n\t\tvar caller = this.getParameter('sysparm_caller_id');  // this will make the instance for caller id\n\t\tvar user = new GlideRecord('sys_user');\n\t\tuser.addQuery('sys_id',caller); // This will query the parameter, if exist\n\t\tuser.query();\n\t\tif(user.next()){  // If user found\n\t\t\treturn \"Email Id: \" + user.email + \"\\n\" + \"First Name \" + user.first_name + \"\\n\" + \"Last Name: \" + user.last_name + \"\\n\" + \"User Id: \" + user.user_name;\n\t\t}\n\t},\n\n    type: 'GetCallerDetails'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetClickableURL/readme.md",
    "content": "Function that generates clickable HTML links to records in different UI contexts (Native UI, Workspace or Portal)\n\n\n\n\nSample background Script:\n\nvar record_sysid = '';// add the record sys_id here.\ngs.info(new global.GetRecordDetails().getClickableURL('incident', record_sysid, 'INC- Workspace', 'workspace', 'cwf/agent'));\n\ngs.info(new global.GetRecordDetails().getClickableURL('incident', record_sysid, 'INC- Portal', 'portal', 'sp'));\n\ngs.info(new global.GetRecordDetails().getClickableURL('incident', record_sysid, 'INC - NativeUI', 'native'));\n\ngs.info(new global.GetRecordDetails().getClickableURL('', record_sysid, 'INC - NativeUI', 'native'));\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetClickableURL/scipt.js",
    "content": "var GetRecordDetails = Class.create();\nGetRecordDetails.prototype = {\n    initialize: function() {},\n    /*\n    table - table name of the record.\n    record - sysid of the record.\n    display_text - Display text of the clickable URL\n    urlUI - the URL Type - Accepts workspace/portal and native UI as default.\n    uiName - mandatory parameter if urlUI is workspace or portal.\n\n    */\n    getClickableURL: function(table, record, display_text, urlUI, uiName) {\n        try {\n            var grRecord = new GlideRecord(table);\n            if (grRecord.get(record)) {\n                var instance_url = 'https://' + gs.getProperty('instance_name') + '.service-now.com/';\n                var path;\n\n                if (urlUI == 'workspace' && uiName != '') {\n                    path = \"now/\" + uiName + \"/record/\" + table + \"/\" + record;\n                } else if (urlUI == 'portal' && uiName != '') {\n                    path = uiName + \"?sys_id=\" + record + \"&view=sp&id=ticket&table=\" + table;\n                } else {\n                    path = \"nav_to.do?uri=\" + table + \".do?sys_id=\" + record;\n                }\n                // final URL\n                var link = instance_url + path;\n                var refLink = '<a href=\"' + link + '\" target=\"_blank\">' + display_text + '</a>';\n                return refLink;\n\n            } else {\n                gs.info('Record does not exist');\n                return '';\n            }\n        } catch (e) {\n            gs.info(\"Exception occured in getClickableURL method: \" + e.message);\n            return '';\n        }\n    },\n    type: 'GetRecordDetails'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetServiceDeskAgentHelpAIUtil/README.md",
    "content": "GetServiceDeskAgentHelpAIUtil\nOverview\nThis is a client-callable Script Include designed for the ServiceNow platform that integrates with an external Databricks AI endpoint. Its purpose is to assist Service Desk agents by providing AI-generated responses to user queries, which can be used to populate incident information or assist in troubleshooting.\nThe script acts as a server-side proxy, handling the client-side request, invoking a Flow Designer Action to communicate with the Databricks service, and returning the AI's response to the client.\nFeatures\nAI-Powered Responses: Sends user search queries to a Databricks-powered Generative AI model to get intelligent answers.\nClient-Callable: Can be invoked from client-side scripts (e.g., a UI Action or Client Script) using GlideAjax.\nFlow Designer Integration: Uses a Flow Designer Action to execute the REST call to the Databricks endpoint, centralizing the external API logic.\nStructured Output: Returns a JSON object containing the AI's response, model metadata, and a trace ID for debugging.\nPrerequisites\nServiceNow Configuration\nFlow Designer Action: A Flow Designer Action named global.genai_action must be created and configured to handle the REST call to the Databricks AI endpoint. This action must have:\nAn input named search_query (String).\nAn output named model_output (String).\nDatabricks Connection: The Flow Designer Action must be correctly configured with the necessary credentials to connect to the external Databricks API.\nSystem Property: A system property named user.prompt is referenced in the script. It should be created and configured with the required prompt text for the AI.\nHow to use\n1. Calling from a Client Script\nYou can use GlideAjax to call the getSearchResults function from a client-side script, such as a UI Action or a Catalog Client Script.\njavascript\n// Example client-side script using GlideAjax\nvar ga = new GlideAjax('GetServiceDeskAgentHelpAIUtil');\nga.addParam('sysparm_name', 'getSearchResults');\nga.addParam('sysparm_search_key', g_form.getValue('short_description')); // Pass the user's input\nga.getXML(getResponse);\n\nfunction getResponse(response) {\n    var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n    if (answer) {\n        var result = JSON.parse(answer);\n        g_form.setValue('comments', result.modelResponse); // Set the AI response in a field\n    }\n}\nUse code with caution.\n\n2. Using from a Server Script\nThe functions can also be called directly from other server-side scripts (e.g., Business Rules).\njavascript\n// Example server-side script\nvar searchKey = 'What are the steps to reset my password?';\nvar aiUtil = new GetServiceDeskAgentHelpAIUtil();\nvar response = aiUtil.getSearchResults(searchKey);\n\ngs.info(response);\nUse code with caution.\n\nScript details\ngetSearchResults()\nThis is the main function that coordinates the process.\nRetrieves the search term from the client parameters.\nCalls the getDataBricksModelResponse() function to get the AI-generated answer.\nConstructs a JSON object with the AI's response and model information.\nReturns the JSON object as a string.\ngetDataBricksModelResponse(search)\nThis function handles the integration with the Databricks AI.\nTakes the search query as a parameter.\nExecutes the global.genai_action Flow Designer Action.\nParses the model_output from the Flow Action's outputs.\nExtracts the AI's message content and a trace ID for debugging.\nReturns a stringified JSON object containing the AI's response, date, and trace ID.\nIncludes a try/catch block to handle and log potential errors during the integration process.\nDependencies\nFlow Designer Action: global.genai_action\nSystem Property: user.prompt\nTroubleshooting\nCheck the Flow Execution: If the AI response is not received, check the Flow Designer execution logs to ensure global.genai_action is running successfully and the REST call to Databricks is returning a valid response.\nReview System Logs: Examine the System Logs (gs.info and gs.error messages) for debugging information related to the script's execution or potential errors from the Databricks API.\nVerify Databricks Credentials: Ensure that the credentials and configuration within the Flow Designer action for connecting to Databricks are correct.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GetServiceDeskAgentHelpAIUtil/script.js",
    "content": "var GetServiceDeskAgentHelpAIUtil = Class.create();\nGetServiceDeskAgentHelpAIUtil.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\n  /**\n     * Main function that processes a search term from a client-side call,\n     * sends it to a Databricks-powered AI, and returns the response.\n     * This function is intended to assist Service Desk agents.\n     *\n     * @returns {string} A JSON string containing the AI's response, model details, and metrics.\n     */\n    getSearchResults: function() {\n        // Defines the use case for logging and metric purposes.\n        var usecase = \"ServiceDesk Helper\";\n        // Gets the current user's Sys ID, though it is not used in the current implementation.\n        var user = gs.getUserID();\n        // Retrieves the search term passed from the client-side script.\n        var searchText = this.getParameter(\"sysparm_search_key\");\n        // Replaces double quotes with single quotes in the search text to prevent JSON parsing issues.\n        searchText = searchText.replaceAll('\"', \"'\");\n        \n        var searchObj = {\n            \"searchValue\": searchText.toString()\n        };\n\n        // Extracts the raw search value from the object.\n        var search = searchObj[\"searchValue\"];\n\n        // This object is structured to create a prompt for another potential AI endpoint (possibly for a brief statement),\n        // but it is currently not used.\n        var brief_statement_payload = {\n            \"messages\": [{\n                    \"role\": \"system\",\n                    \"content\": \"You are an Expert ServiceNow bot that helps the users to create an incident\"\n                },\n                {\n                    \"role\": \"user\",\n                    \"content\": gs.getProperty('user.prompt') + search\n                }\n            ]\n        };\n\n        var databricks_model_response = {};\n        // Calls the internal method to get the response from the Databricks model.\n        var response = this.getDataBricksModelResponse(search);\n        // UNCOMMENT THIS WHEN WE HAVE A PROPER SOLUTION FOR BRIEF RESPONSE GENERATION\n        // var brief_response = this.getBriefResponse(brief_statement_payload);\n        // The brief response is hardcoded to an empty JSON object\n        // Assigns the model response to the output object.\n        databricks_model_response.modelResponse = response;\n        // Assigns a hardcoded model ID.\n        databricks_model_response.model_id = \"Databricks Runbook\";\n\n        // Converts the final response object to a JSON string for client-side processing.\n        databricks_model_response = JSON.stringify(databricks_model_response);\n        // Logs the final JSON string for debugging purposes.\n        gs.info(\"Service Desk Helper Results: Testing value of the final databricks response being sent: \" + databricks_model_response);\n\n        // Returns the JSON string to the calling client script.\n        return databricks_model_response;\n    },\n\n    /**\n     * This function calls the Databricks endpoint via a Flow Designer action\n     * to generate an answer for the user's query.\n     *\n     * @param {string} search - The user's search query.\n     * @returns {string} A JSON string containing the AI's response, the current date, and a trace ID.\n     */\n    getDataBricksModelResponse: function(search) {\n        try {\n            var inputs = {};\n            // Maps the search query to the input expected by the flow action.\n            inputs['search_query'] = search;\n\n            // Executes the specified Flow Designer action with the provided inputs.\n            // The action is run in the foreground, meaning the script will wait for a response.\n            var result = sn_fd.FlowAPI.getRunner().action('global.genai_action').inForeground().withInputs(inputs).run();\n            // Retrieves the outputs from the completed flow action.\n            var outputs = result.getOutputs();\n\n            // Extracts the model output from the flow action outputs.\n            var model_output = outputs['model_output'];\n            // Attempts to parse and extract vector response data, though the variable is not used after this line.\n            var databricks_vector_response = JSON.parse(model_output).databricks_output.trace.data.spans;\n\n            // Logs the raw response from the Databricks model for debugging.\n            gs.info(\"Helper Results: Databricks flow action response: \" + JSON.stringify(model_output));\n\n            var current_date = new GlideDateTime();\n            var output = {};\n            // Parses the model output to extract the AI's content.\n            output.response = JSON.parse(model_output).choices[0].message.content;\n            // Adds the current date to the output object.\n            output.date = current_date.getDisplayValue();\n            // Logs the trace ID for tracking purposes.\n            gs.info(\"Helper Results: GEN AI flow action TraceID value: \" + JSON.parse(model_output).id);\n            // Adds the trace ID to the output object.\n            output.traceID = JSON.parse(model_output).id;\n            // Returns the constructed output object as a JSON string.\n            return JSON.stringify(output);\n\n        } catch (ex) {\n            // Catches any exceptions during the flow execution and logs an error.\n            var message = ex.getMessage();\n            gs.error(message);\n        }\n    },\n\n  type: 'GetServiceDeskAgentHelpAIUtil'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GlideDateTimeUtils/ClientDateTimeUtils.js",
    "content": "var ClientDateTimeUtils = Class.create();\nClientDateTimeUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    //Takes a Single Date/Time Field and returns its time difference from nowDateTime().\n    //params = sysparm_fdt (the first date/time field), sysparm_difftype (time based format to return result. See \"_calcDateDiff\" function comments)\n    getNowDateTimeDiff: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date-Time Field\n        var diffTYPE = this.getParameter('sysparm_difftype'); // Date-Time Type to return the answer as. Can be second, minute, hour, day\n        var diff = gs.dateDiff(gs.nowDateTime(), firstDT, true);\n        var timediff = this._calcDateDiff(diffTYPE, diff);\n        //return \"getNowDateTimeDiff: FIRST DT: \" + firstDT + \" -DIFFTYPE: \" + diffTYPE + \" -TIME DIFF: \" + timediff;\n        return timediff;\n    },\n\n    //Diff the amount of time between two different Date/Time fields\n    //params = sysparm_fdt (the first date/time field), sysparm_sdt (second date/time field), sysparm_difftype (time based format to return result. See \"_calcDateDiff\" function comments)\n    getDateTimeDiff: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date-Time Field\n        var secondDT = this.getParameter('sysparm_sdt'); // Second Date-Time Field\n        var diffTYPE = this.getParameter('sysparm_difftype'); // Date-Time Type to return the answer as. Can be second, minute, hour, day\n        var diff = gs.dateDiff(firstDT, secondDT, true);\n        var timediff = this._calcDateDiff(diffTYPE, diff);\n        //return \"getDateTimeDiff: FIRST DT: \" + firstDT + \" -SECOND DT: \" + secondDT + \" -DIFFTYPE: \" + diffTYPE + \" -TIME DIFF: \" + timediff;\n        return timediff;\n    },\n\n    //Takes your date/time field and returns the amount of time before now. A positive is time before now, a negative number is after now.\n    //params = sysparm_fdt (the first date/time field), sysparm_difftype (time based format to return result. See \"_calcDateDiff\" function comments)\n    getDateTimeBeforeNow: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date-Time Field\n        var diffTYPE = this.getParameter('sysparm_difftype'); // Date-Time Type to return the answer as. Can be second, minute, hour, day\n        var diff = gs.dateDiff(firstDT, gs.nowDateTime(), true);\n        var timediff = this._calcDateDiff(diffTYPE, diff);\n        //return \"getDateTimeBeforeNow: FIRST DT: \" + firstDT + \" -DIFFTYPE: \" + diffTYPE + \" -TIME DIFF: \" + timediff;\n        return timediff;\n    },\n\n    //Returns true if it is before now, and false if it is after now.\n    //params = sysparm_fdt (the first date/time field)\n    getDateTimeBeforeNowBool: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date-Time Field\n        var diff = gs.dateDiff(firstDT, gs.nowDateTime(), true);\n        var answer = '';\n        if (diff >= 0) {\n            answer = 'true';\n        } else {\n            answer = 'false';\n        }\n        return answer;\n    },\n\n    //Returns the Date/Time of right now.\n    getNowDateTime: function () {\n        var now = gs.nowDateTime(); //Now Date/Time\n        return now;\n    },\n\n    //Returns the Date right now.\n    getNowDate: function () {\n        var now = GlideDate();; //Now Date\n        return now.getLocalDate();\n    },\n\n    //Returns the Time of right now.\n    getNowTime: function () {\n        var now = GlideTime();; //Now Time\n        var modnow = now.getLocalTime().toString().split(' ');\n        return modnow[1];\n    },\n\n    //Takes a date/time field and adds time to it.\n    //params = sysparm_fdt (the first date/time field), sysparm_addtype (type of time to add - second, minute, hour, day, week, month, year), sysparm_addtime (amount of time to add based on the type).\n    addDateTimeAmount: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date-Time Field\n        var addTYPE = this.getParameter('sysparm_addtype'); //What to add - second (addSeconds()), minute (need to add conversion), hour (need to add conversion), day (addDays()), week (addWeeks()), month (addMonths()), year (addYears())\n        var addTIME = this.getParameter('sysparm_addtime'); //How much time to add\n        var day = GlideDateTime(firstDT);\n\n        if (addTYPE == 'second') {\n            day.addSeconds(addTIME);\n        } else if (addTYPE == 'minute') {\n            day.addSeconds(addTIME * 60);\n        } else if (addTYPE == 'hour') {\n            day.addSeconds(addTIME * (60 * 60));\n        } else if (addTYPE == 'day') {\n            day.addDays(addTIME);\n        } else if (addTYPE == 'week') {\n            day.addWeeks(addTIME);\n        } else if (addTYPE == 'month') {\n            day.addMonths(addTIME);\n        } else if (addTYPE == 'year') {\n            day.addYears(addTIME);\n        } else {\n            day.addDays(addTIME);\n        }\n\n        //return \"First Date: \" + firstDT + \" -Time to Add: \" + addTIME + \" -Add Type: \" + addTYPE + \" -Added Time: \" + day;\n        return day;\n    },\n\n    //Takes a glide date field and adds time to it.\n    //params = sysparm_fdt (the first date/time field), sysparm_addtype (type of time to add - day, week, month, year),sysparm_addtime (amount of time to add based on the type).\n    addDateAmount: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date Field\n        var addTYPE = this.getParameter('sysparm_addtype'); //What to add - day (addDays()), week (addWeeks()), month (addMonths()), year (addYears())\n        var addTIME = this.getParameter('sysparm_addtime'); //How much time to add\n        var day = GlideDate();\n        day.setValue(firstDT);\n\n        if (addTYPE == 'day') {\n            day.addDays(addTIME);\n        } else if (addTYPE == 'week') {\n            day.addWeeks(addTIME);\n        } else if (addTYPE == 'month') {\n            day.addMonths(addTIME);\n        } else if (addTYPE == 'year') {\n            day.addYears(addTIME);\n        } else {\n            day.addDays(addTIME);\n        }\n\n        //return \"First Date: \" + firstDT + \" -Time to Add: \" + addTIME + \" -Add Type: \" + addTYPE + \" -Added Time: \" + day;\n        return day;\n    },\n\n    addTimeAmount: function () {\n        var firstDT = this.getParameter('sysparm_fdt'); //First Date-Time Field\n        var addTYPE = this.getParameter('sysparm_addtype'); //What\n        var addTIME = this.getParameter('sysparm_addtime'); //How much time to add\n        var time = GlideTime();\n        time.setValue(firstDT);\n\n        if (addTYPE == 'second') {\n            time.addSeconds(addTIME);\n        } else if (addTYPE == 'minute') {\n            time.addSeconds(addTIME * 60);\n        } else if (addTYPE == 'hour') {\n            time.addSeconds(addTIME * (60 * 60));\n        } else {\n            time.addSeconds(addTIME);\n        }\n\n        var modtime = time.toString().split(' ');\n        //return \"First Date: \" + firstDT + \" -Time to Add: \" + addTIME + \" -Add Type: \" + addTYPE + \" -Added Time: \" + time;\n        return modtime[1];\n    },\n\n    //Private function to calculate the date difference return result in second, minute, hour, day.\n    _calcDateDiff: function (diffTYPE, seconds) {\n        var thisdiff;\n        if (diffTYPE == \"day\") {\n            thisdiff = seconds / 86400;\n        } else if (diffTYPE == \"hour\") {\n            thisdiff = seconds / 3600;\n        } else if (diffTYPE == \"minute\") {\n            thisdiff = seconds / 60;\n        } else if (diffTYPE == \"second\") {\n            thisdiff = seconds;\n        } else {\n            thisdiff = seconds;\n        }\n        return thisdiff;\n    }\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GlideDateTimeUtils/README.md",
    "content": "# ClientDateTimeUtils\nThis Script Include contains useful functions related to date/time calculations that can be called using GlideAjax.\nAs there is very limited javascript functions related to Date & Time, this will be very useful for client side calculations of date & time.\n\n## Example Script\n```javascript\nvar ga = new GlideAjax('ClientDateTimeUtils');\nga.addParam('sysparm_name', 'getNowDateTimeDiff');\nga.addParam('sysparm_fdt', g_form.getValue('last_date'));\nga.addParam('sysparm_difftype', 'day');\nga.getXMLAnswer(function(response){\n\tif(parseInt(response)<2){\n\t\talert(\"Last date cannot be less than 2 days from today\");\n\t}\n});\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GlideRecord to JSON/README.md",
    "content": "# GlideRecord to JSON object converter\n* Description: This script include (gr2obj) converts GlideRecord to a JSON object\n* Sample Usage: \n```javascript\nvar gr = new GlideRecord('incident');\n// Get an incident record\ngr.get('96cef4561b3530108b59a8e5604bcb74');\n// Call the script include\nvar obj = gr2obj(gr);\n// Stringify the object and display\ngs.info(JSON.stringify(obj));\n```\n* Sample Output:\n```json\n{\"active\":\"true\",\"activity_due\":\"\",\"additional_assignee_list\":null,\"approval\":\"not requested\",\"approval_history\":\"\",\"approval_set\":\"\",\"assigned_to\":null,\"assignment_group\":null,\"business_duration\":\"\",\"business_service\":null,\"business_stc\":null,\"calendar_duration\":\"\",\"calendar_stc\":null,\"caller_id\":\"System Administrator\",\"category\":\"inquiry\",\"caused_by\":null,\"child_incidents\":\"0\",\"close_code\":null,\"close_notes\":null,\"closed_at\":\"\",\"closed_by\":null,\"cmdb_ci\":null,\"comments\":\"\",\"comments_and_work_notes\":\"\",\"company\":null,\"contact_type\":null,\"contract\":null,\"correlation_display\":null,\"correlation_id\":null,\"delivery_plan\":null,\"delivery_task\":null,\"description\":null,\"due_date\":\"\",\"escalation\":\"0\",\"expected_start\":\"\",\"follow_up\":\"\",\"group_list\":null,\"hold_reason\":null,\"impact\":\"3\",\"incident_state\":\"2\",\"knowledge\":\"false\",\"location\":null,\"made_sla\":\"true\",\"notify\":\"1\",\"number\":\"INC0010007\",\"opened_at\":\"2021-08-08 03:42:27\",\"opened_by\":\"System Administrator\",\"order\":null,\"parent\":null,\"parent_incident\":null,\"priority\":\"5\",\"problem_id\":null,\"reassignment_count\":\"0\",\"rejection_goto\":null,\"reopen_count\":\"0\",\"reopened_by\":null,\"reopened_time\":\"\",\"resolved_at\":\"\",\"resolved_by\":null,\"rfc\":null,\"route_reason\":null,\"service_offering\":null,\"severity\":\"3\",\"short_description\":\"test2\",\"sla_due\":\"\",\"state\":\"2\",\"subcategory\":null,\"sys_class_name\":\"incident\",\"sys_created_by\":\"admin\",\"sys_created_on\":\"2021-08-08 03:42:56\",\"sys_domain\":\"global\",\"sys_domain_path\":\"/\",\"sys_id\":\"96cef4561b3530108b59a8e5604bcb74\",\"sys_mod_count\":\"1\",\"sys_tags\":\"\",\"sys_updated_by\":\"admin\",\"sys_updated_on\":\"2021-09-27 16:35:00\",\"task_effective_number\":\"INC0010007\",\"time_worked\":\"\",\"u_requested_for\":\"Abel Tuter\",\"universal_request\":null,\"upon_approval\":\"proceed\",\"upon_reject\":\"cancel\",\"urgency\":\"3\",\"user_input\":\"\",\"variables\":null,\"watch_list\":null,\"wf_activity\":null,\"work_end\":\"\",\"work_notes\":\"\",\"work_notes_list\":null,\"work_start\":\"\"}\n```\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GlideRecord to JSON/gr2obj.js",
    "content": "var gr2obj = Class.create();\n/**\n * A function to convert a glide record to a JSON object\n * @param {GlideRecord} gr\n * @out {Object}\n */\ngr2obj = function(gr) {\n    // Get the GlideRecord's array of fields\n    var fieldArr = new GlideRecordUtil().getFields(gr);\n    fieldArr.sort();\n    var recordObj = {};\n    // For each field get the value\n    for (var field in fieldArr) {\n        // For a 'boolean' or a 'journal_input' field type, get the display value\n        var type = gr.getElement(fieldArr[field]).getED().getInternalType();\n        if (type == 'boolean' || type == 'journal_input') {\n            recordObj[fieldArr[field]] = gr.getDisplayValue(fieldArr[field]);\n        } else if (type == 'reference' && gr.getValue(fieldArr[field])) {\n            recordObj[fieldArr[field]] = gr.getDisplayValue(fieldArr[field]);\n            // if reference field sys_id is needed, uncomment the next line\n            // recordObj[fieldArr[field]] = gr.getValue(fieldArr[field]);\n        } else {\n            recordObj[fieldArr[field]] = gr.getValue(fieldArr[field]);\n        }\n    }\n    return recordObj;\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/GlideRecordHelper/README.md",
    "content": "Various functions for working with gliderecords, inspired by some of the methods available in the service portal with $sp. Functions have JSDoc documnetation for use.\n\n@name getField\n@description: Checks the specified field and returns an object.\n\n@name getFields\n@description: Checks the specified fields and returns an array of field objects.\n\n@name getFieldsObject\n@description: Checks the specified fields and returns an object of objects.\n\n@name getFieldsObjectWithQuery\n@description: Checks the specified fields and returns an object of objects with a specified filter\n\n@name getFieldsObjectWithQueryAjax\n@description: Client callable to get fields array of objects with query\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GlideRecordHelper/script.js",
    "content": "var GlideRecordHelper = Class.create();\nGlideRecordHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  \n  \t/**SNDOC\n\t  @name getField\n\t  @description: Checks the specified field and returns an object.\n\t  @param {object} [record] - Glide Record object\n\t  @param {string} [value] - string of field\n\t  @return {object}  An object containing label, value, and display value of provided field\n\t*/\n\tgetField : function (record,value){\n\t\tvar addValue = {};\n\t\taddValue.label = record[value].getLabel();\n\t\taddValue.value = record.getValue(value);\n\t\taddValue.display_value = record.getDisplayValue(value);\n\t\taddValue.type = \"notAssigned\";\n\t\taddValue.type = record && record.getElement(value) && record.getElement(value).getED() && record.getElement(value).getED().getInternalType()?record.getElement(value).getED().getInternalType():null;\n\n\t\treturn addValue;\n\t},\n\t\n\t/**SNDOC\n\t  @name getFields\n\t  @description: Checks the specified fields and returns an array of field objects.\n\t  @param {object} [record] - Glide Record object\n\t  @param {string} [values] - Comma deliminated string of field values\n\t  @return {object}  An array of objects containing label, value, and display value of provided fields\n\t*/\n\tgetFields : function (record,values){\n\t\tvar valueList = values.split(\",\");\n\t\tvar returnList = [];\n\n\t\tvalueList.forEach(function(currentValue){\n\t\t\tvar addValue = {};\n\t\t\taddValue.label = record[currentValue].getLabel();\n\t\t\taddValue.value = record.getValue(currentValue);\n\t\t\taddValue.display_value = record.getDisplayValue(currentValue);\n\t\t\taddValue.type = \"notAssigned\";\n\t\t\taddValue.type = record && record.getElement(currentValue) && record.getElement(currentValue).getED() && record.getElement(currentValue).getED().getInternalType()?record.getElement(currentValue).getED().getInternalType():null;\n\n\t\t\treturnList.push(addValue);\n\t\t});\n\n\t\treturn returnList;\n\t},\n\n\tgetFieldsObject: function(record,values){\n\t\tvar valueList = values.split(\",\");\n\t\tvar returnList = {};\n\n\t\tvalueList.forEach(function(currentValue){\n\t\t\treturnList[currentValue] = {};\n\t\t\treturnList[currentValue].label = record[currentValue].getLabel();\n\t\t\treturnList[currentValue].value = record.getValue(currentValue);\n\t\t\treturnList[currentValue].display_value = record.getDisplayValue(currentValue);\n\t\t\treturnList[currentValue].type = \"notAssigned\";\n\t\t\treturnList[currentValue].type = record && record.getElement(currentValue) && record.getElement(currentValue).getED() && record.getElement(currentValue).getED().getInternalType()?record.getElement(currentValue).getED().getInternalType():null;\n\t\t});\n\n\t\treturn returnList;\n\t},\n\n\tgetFieldsObjectWithQuery: function(table,query,values){\n\t\tvar gr = new GlideRecordSecure(table);\n\t\tgr.addEncodedQuery(query);\n\t\tgr.query();\n\n\t\treturn (gr.next() ? this.getFieldsObject(gr,values) : null);\n\t},\n\t\n\t/**SNDOC\n\t\t@name getFieldsObjectWithQueryAjax\n\t\t@description: Client callable to get fields array of objects with query\n\t\t@param {getParameter}   [sysparm_table] - Valid table\n\t\t@param {getParameter}   [sysparm_query] - Encoded query\n\t\t@param {getParameter}   [sysparm_values] - Fields to return\n\t\t@return {JSON} Array of object with display_value, label, type, and value\n\t\t@example\n\t\tvar gaCi = new GlideAjax('GlideRecordHelper');\n\t\tgaCi.addParam('sysparm_name', 'getFieldsObjectWithQueryAjax');\n\t\tgaCi.addParam('sysparm_table', 'cmdb_ci');\n\t\tgaCi.addParam('sysparm_query', 'sys_id=44c417444f6b2200f92eab99f110c762');\n\t\tgaCi.addParam('sysparm_values', 'state');\n\t\tgaCi.getXML(callBackFuncation);\n\t\t\n\t\tfunction callBackFuncation(response){\n\t\tvar answer = response.responseXML.documentElement.getAttribute(\"answer\");\n\t\tvar ci = JSON.parse(answer);\n\t\t\n\t\t// ci = [state{\n\t\t//\t\t\tdisplay_value: \"Active\",\n\t\t//\t\t\tlabel: \"State\",\n\t\t//\t\t\ttype: \"integer\",\n\t\t//\t\t\tvalue: \"2\"\n\t\t//\t\t}]\t\n\t*/\n\tgetFieldsObjectWithQueryAjax: function(){\n\t\tvar table = this.getParameter('sysparm_table');\n\t\tvar query = this.getParameter('sysparm_query');\n\t\tvar values = this.getParameter('sysparm_values');\n\t\treturn JSON.stringify(this.getFieldsObjectWithQuery(table,query,values));\n\t},\n\n\tgetFieldsMultiObjectWithQuery: function(table,query,values){\n\t\tvar fieldObjectArray = [];\n\t\tvar gr = new GlideRecordSecure(table);\n\t\tgr.addEncodedQuery(query);\n\t\tgr.query();\n\t\twhile(gr.next()){\n\t\t\tfieldObjectArray.push(this.getFieldsObject(gr,values));\n\t\t}\n\t\treturn fieldObjectArray;\n\t},\n\n\t/**SNDOC\n\t\t@name hasNextQuery\n\t\t@description: Checks if the provided query has results, returns true if so.\n\t\t@param {string}   [table] - Valid table\n\t\t@param {string}   [query] - Encoded query\n\t\t@return {boolean}  \n\t*/\n\thasNextQuery: function(table,query){\n\t\ttable = table || this.getParameter('sysparm_table');\n\t\tquery = query || this.getParameter('sysparm_query');\n\t\tvar returnVar = false;\n\t\tvar grGlideRecord = new GlideRecordSecure(table);\n\t\tgrGlideRecord.addEncodedQuery(query);\n\t\tgrGlideRecord.query();\n\t\tif (grGlideRecord.hasNext()) {\n\t\t\treturnVar = true;\n\t\t}\n\t\treturn returnVar;\n\t},\n\n\ttype: 'GlideRecordHelper'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GroupMembershipUtils for client and server/groupMembershipUtils.js",
    "content": "/**\n * This utility script include provides methods for managing and querying user-group memberships\n * in the sys_user_grmember table.\n * Accessible from both server-side and client-side (for AJAX-compatible methods).\n */\nvar GroupMembershipUtils = Class.create();\nGroupMembershipUtils.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    /**\n     * Returns a comma-separated list of user sys_ids who are members of the specified group.\n     * Can be called from both server and client sides.\n     *\n     * @param {string} [groupSysID] - (Optional) sys_id of the group. If not provided, expects 'group_sys_id' parameter (used in client-side call).\n     * @returns {string} Comma-separated sys_ids of users in the group.\n     */\n    getGroupMembers: function(groupSysID) {\n        var group = groupSysID ? groupSysID : this.getParameter('group_sys_id');\n        if (!group) return;\n        var users = [];\n\n        var grGroupMembers = new GlideRecord('sys_user_grmember');\n        grGroupMembers.addQuery('group', group);\n        grGroupMembers.query();\n        while (grGroupMembers.next()) {\n            users.push(grGroupMembers.getValue('user'));\n        }\n\n        return users.join();\n    },\n\n    /**\n     * Returns a comma-separated list of group sys_ids that the specified user is a member of.\n     * Can be called from both server and client sides.\n     *\n     * @param {string} [userSysId] - (Optional) sys_id of the user. If not provided, expects 'user_sys_id' parameter (used in client-side call).\n     * @returns {string} Comma-separated sys_ids of groups the user belongs to.\n     */\n    getUserGroups: function(userSysId) {\n        var user = userSysId ? userSysId : this.getParameter('user_sys_id');\n        if (!user) return;\n        var groups = [];\n\n        var grGroupMembers = new GlideRecord('sys_user_grmember');\n        grGroupMembers.addQuery('user', user);\n        grGroupMembers.query();\n        while (grGroupMembers.next()) {\n            groups.push(grGroupMembers.getValue('group'));\n        }\n\n        return groups.join();\n    },\n\n    /**\n     * Adds multiple users to a specified group.\n     * Prevents unique key violation error by checking if the user is already a member.\n     *\n     * **Server-side only.**\n     *\n     * @param {string} groupSysID - sys_id of the group.\n     * @param {Array<string>} userSysIDs - Array of user sys_ids to be added to the group.\n     * @returns {number} The count of successfully added group memberships.\n     */\n    addGroupMembers: function(groupSysID, userSysIDs) {\n        if (!groupSysID || !userSysIDs) return 0;\n\n        var count = 0;\n\n        for (var i = 0; i < userSysIDs.length; i++) {\n            var grGroupMembers = new GlideRecord('sys_user_grmember');\n            grGroupMembers.addQuery('group', groupSysID);\n            grGroupMembers.addQuery('user', userSysIDs[i]);\n            grGroupMembers.query();\n\n            // Only insert if membership does not already exist\n            if (!grGroupMembers.next()) {\n                grGroupMembers.initialize();\n                grGroupMembers.setValue('group', groupSysID);\n                grGroupMembers.setValue('user', userSysIDs[i]);\n\n                if (grGroupMembers.insert()) {\n                    count++;\n                }\n            }\n        }\n\n        return count;\n    },\n\n    /**\n     * Removes multiple users from a specified group.\n     * Only removes if a membership exists.\n     *\n     * **Server-side only.**\n     *\n     * @param {string} groupSysID - sys_id of the group.\n     * @param {Array<string>} userSysIDs - Array of user sys_ids to be removed from the group.\n     * @returns {number} The count of successfully removed group memberships.\n     */\n    removeGroupMembers: function(groupSysID, userSysIDs) {\n        if (!groupSysID || !userSysIDs) return 0;\n\n        var count = 0;\n\n        for (var i = 0; i < userSysIDs.length; i++) {\n            var grGroupMembers = new GlideRecord('sys_user_grmember');\n            grGroupMembers.addQuery('group', groupSysID);\n            grGroupMembers.addQuery('user', userSysIDs[i]);\n            grGroupMembers.query();\n\n            // Only delete if membership exists\n            if (grGroupMembers.next()) {\n                if (grGroupMembers.deleteRecord()) {\n                    count++;\n                }\n            }\n        }\n\n        return count;\n    },\n\n    type: 'GroupMembershipUtils'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/GroupMembershipUtils for client and server/readme.md",
    "content": "Utility Script Include for managing user-group relationships in ServiceNow (sys_user_grmember table). \n\nIt provides methods to:\nRetrieve users in a group (getGroupMembers)\nRetrieve groups a user belongs to (getUserGroups)\nAdd users to a group (addGroupMembers)\nRemove users from a group (removeGroupMembers)\n\nSupports both client and server-side operations (where applicable), ensures no duplicate group memberships, and simplifies bulk updates.\n\nIdeal for use in server scripts, GlideAjax calls, reference qualifiers, etc to streamline group membership management.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/HTMLUtils/README.md",
    "content": "# Creates an HTML Table from an object\n\n@param  {String} [title] - Title above table\n@param  {Object} [table] - Object with headers attribute and row multi dimension array\n@returns {String} HTML Table\n\n### Example\n```js\nvar table = {\nheader:['col1','col2'],\nrows:[['row1col1','row1col2'],\n        ['row2col1','row2col2']]\n}\n\nvar hU = new HTMLUtils();\nhU.createHTMLTable(\"Test\",table);\n```\n### Output\n```html\n<p style='margin: 10px 0px 10px;'><b>Test</b></p><table class='template_TBL table'><tbody><tr><td>col1</td><td>col2</td></tr><tr><td>row1col1</td><td>row1col2</td></tr><tr><td>row2col1</td><td>row2col2</td></tr></tbody></table>\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/HTMLUtils/script.js",
    "content": "var HTMLUtils = Class.create();\nGlideRecordHelper.prototype = Object.extendsObject(AbstractAjaxProcessor, {\t\n    \n    /**SNDOC\n\t @name createHTMLTable\n\t @description Base method that creates an HTML Table from an object\n\t @param  {String} [title] - Title above table\n\t @param  {Object} [table] - Object with headers attribute and row multi dimension array\n\t @returns {String} HTML Table\n\t @example\n\t var table = {\n\t\theader:['col1','col2'],\n\t\trows:[['row1col1','row1col2'],\n\t\t\t  ['row2col1','row2col2']]\n\t }\n\n\t var hU = new HTMLUtils();\n\t hU.createHTMLTable(\"Test\",table);\n\t*/\n\tcreateHTMLTable: function(title,table){\n\t\tvar html = \"<p style='margin: 10px 0px 10px;'><b>\"+title+\"</b></p><table class='template_TBL table'><tbody>\";\n\t\thtml += \"<tr>\";\n\t\ttable.header.forEach(function(h){html +=\"<td>\"+h+\"</td>\";});\n\t\thtml +=\"</tr>\";\n\t\ttable.rows.forEach(function(row){\n\t\t\thtml += \"<tr>\";\n\t\t\trow.forEach(function(r){\n\t\t\t\thtml += \"<td>\"+r+\"</td>\";\n\t\t\t});\n\t\t\thtml += \"</tr>\";\n\t\t});\n\t\thtml += \"</tbody></table>\";\n\n\t\treturn html;\n\t},\n\n    type: 'HTMLUtils'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/Hybrid Script Include for AJAX or Server Side Parameters/README.md",
    "content": "This example shows how one could code a script include that might be called in two different scenarios. One being a client AJAX call, and the other being a server side call, from another script include perhaps.\n\nExample usage:\nClient script AJAX call:\n\n        var ajax = new GlideAjax('example_hybrid_parameters');\n        ajax.addParam('sysparm_name', 'exampleHybrid');\n        ajax.addParam('sysparm_parm1', parm1);\n        ajax.addParam('sysparm_parm2', parm2);\n        ajax.addParam('sysparm_parm3', parm3);\n        ajax.addParam('sysparm_parm4', parm4);\n        ajax.getXMLAnswer(exampleResponse);\n\n    Call from server side script:\n\n        var pr = new example_hybrid_parameters();\n        result = pr.checkPrereq(parm1, parm2, parm3, parm4);\n        return result;\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Hybrid Script Include for AJAX or Server Side Parameters/ScriptInclude.js",
    "content": "var example_hybrid_parameters = Class.create();\nexample_hybrid_parameters.prototype = Object.extendsObject(\n  AbstractAjaxProcessor,\n  {\n    /*\n        This function shows how you can create a script include that can either be called from a AJAX call, or from direct object creation in a server side script.\n\n    \t@parm: {parm1} Shows if parm1 was a string of values seperated by a \",\"\n    \t@parm: {parm2} Shows directly getting the value from the parameter.\n    \t@parm: {parm3} Shows constructing the parameter value into a string, the one from the AJAX call will already be a string.\n\t\t@parm: {parm4} Shows an example of using a boolean value for the parameter.\n    */\n    exampleHybrid: function (parm1, parm2, parm3, parm4) {\n      parm1 = parm1\n        ? parm1.split(\",\")\n        : this.getParameter(\"sysparm_parm1\").split(\",\");\n      parm2 = parm2 ? parm2 : this.getParameter(\"sysparm_parm2\");\n      parm3 = parm3 ? parm3.toString() : this.getParameter(\"sysparm_parm3\");\n      parm4 = parm4 ? parm4 : true;\n\n      //Do other things after this\n    },\n    type: \"example_hybrid_parameters\",\n  }\n);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Inactive User/InactiveUserCleanup.js",
    "content": "/**\n * This script include provides a utility to identify and deactivate users\n * who have not logged in for a specified number of days.\n * \n * Usage: Call the `deactivateInactiveUsers` method with the number of days\n * as the parameter to deactivate users who have been inactive for that duration.\n */\nvar InactiveUserCleanup = Class.create();\nInactiveUserCleanup.prototype = {\n    initialize: function() {\n        // Initialization code if needed\n    },\n\n    /**\n     * Deactivates users who have not logged in for the specified number of days.\n     * @param {number} days - The number of days of inactivity before deactivation.\n     */\n    deactivateInactiveUsers: function(days) {\n        var cutoffDate = new GlideDateTime();\n        cutoffDate.addDays(-days); // Set the cutoff date to 'days' ago\n\n        // Query for users who have not logged in since the cutoff date\n        var userGr = new GlideRecord('sys_user');\n        userGr.addQuery('last_login_time', '<', cutoffDate);\n        userGr.addQuery('active', 'true'); // Only consider active users\n        userGr.query();\n\n        while (userGr.next()) {\n            userGr.active = false; // Deactivate the user\n            userGr.update();\n            gs.info('Deactivated user: ' + userGr.name);\n        }\n    },\n\n    type: 'InactiveUserCleanup'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Inactive User/README.md",
    "content": "# Inactive User Cleanup Utility for ServiceNow\n\nThis utility is designed for ServiceNow administrators to help maintain user accounts by automatically deactivating users who have not logged in for a specified number of days. \nIt's a Script Include that can be scheduled to run at regular intervals or executed manually.\n\n## Features\n\n- Identifies users who have been inactive for a given number of days.\n- Deactivates these users to keep the user list current and secure.\n- Logs all deactivation actions for auditing purposes.\n\n## Prerequisites\n\nBefore you begin using this utility, ensure that you have the necessary permissions to modify user records in ServiceNow.\n\n## Installation\n\n1. Navigate to `System Definition > Script Includes` in your ServiceNow instance.\n2. Click on `New` to create a new Script Include.\n3. Give it a name, such as `InactiveUserCleanup`.\n4. Copy and paste the code from the `InactiveUserCleanup.js` file into the `Script` field.\n5. Save the new Script Include.\n\n## Usage\n\nTo use the `InactiveUserCleanup` utility:\n\n1. Create an instance of the `InactiveUserCleanup` class in a background script, business rule, or scheduled job.\n2. Call the `deactivateInactiveUsers` method with the number of days of inactivity as the parameter.\n\nExample for a background script:\n\n```javascript\nvar cleanup = new InactiveUserCleanup();\ncleanup.deactivateInactiveUsers(90); // Deactivate users who have been inactive for 90 days\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Install base items with active cases/README.md",
    "content": "# Install base items with active cases\n\nThis script will run **RLQUERY** on the `sn_install_base_item` table and get active cases by the help of `RLQUERYsn_install_base_m2m_affected_install_base` table.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Install base items with active cases/script.js",
    "content": "var ibGr = new GlideRecord(\"sn_install_base_item\");\nibGr.addEncodedQuery(\"RLQUERYsn_install_base_m2m_affected_install_base.install_base,>0,case.active=true^ENDRLQUERY\");\nibGr.query();\n\nwhile (ibGr.next()) {\n  // Do something\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/JSON Mapping for Incident Creation/JsonMapping.js",
    "content": "function createIncidentsFromJSON(payload) {\n    \tvar data = JSON.parse(payload);\n    \t// Check if Data is Array or not, If not then add into array\n    \tvar dataArray = Array.isArray(data) ? data : [data];\t\n\n\n\tdataArray.forEach(function(item) {\t \t\n    \t\tvar gr = new GlideRecord('incident');\n    \t\tgr.initialize();\n\n    \t\tfor (var key in item) {\n        \t\tif (gr.isValidField(key)) {\n            \t\t\tgr[key] = item[key];\n        \t\t}\n    \t\t}\n    \t\tvar incidentSysId = gr.insert();\n\t\tgs.info(\"Incident created with Sys ID: \" + incidentSysId);\t\n\t});\n}\n\n// Usage with a single object\nvar singlePayload = '{\"short_description\":\"System Down\",\"caller_id\":\"durgesh1@example.com\",\"priority\":1,\"assignment_group\":\"IT Support\"}';\ncreateIncidentsFromJSON(singlePayload);\n\n// Usage with an array of objects\nvar arrayPayload = '[{\"short_description\":\"System Down\",\"caller_id\":\"durgesh2@example.com\",\"priority\":1,\"assignment_group\":\"IT Support\"}, {\"short_description\":\"Email Issue\",\"caller_id\":\"durgesh3@example.com\",\"priority\":2,\"assignment_group\":\"IT Support\"}]';\ncreateIncidentsFromJSON(arrayPayload);"
  },
  {
    "path": "Server-Side Components/Script Includes/JSON Mapping for Incident Creation/README.md",
    "content": "# JSON Mapping for Incident Creation\n\nUsecase - \n\nWhen we got the JSON payload (Object/Array of Object) for Incident creation with dynamic fields. You need a reusable script to create an Incident without hardcoding fields.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/JSONPath/JSONPath.js",
    "content": "(function (global, factory) {\n  typeof exports === \"object\" && typeof module !== \"undefined\"\n    ? factory(exports)\n    : typeof define === \"function\" && define.amd\n    ? define([\"exports\"], factory)\n    : ((global =\n        typeof globalThis !== \"undefined\" ? globalThis : global || self),\n      factory((global.JSONPath = {})));\n})(this, function (exports) {\n  \"use strict\";\n\n  function _typeof(obj) {\n    \"@babel/helpers - typeof\";\n\n    if (typeof Symbol === \"function\" && typeof Symbol.iterator === \"symbol\") {\n      _typeof = function (obj) {\n        return typeof obj;\n      };\n    } else {\n      _typeof = function (obj) {\n        return obj &&\n          typeof Symbol === \"function\" &&\n          obj.constructor === Symbol &&\n          obj !== Symbol.prototype\n          ? \"symbol\"\n          : typeof obj;\n      };\n    }\n\n    return _typeof(obj);\n  }\n\n  function _classCallCheck(instance, Constructor) {\n    if (!(instance instanceof Constructor)) {\n      throw new TypeError(\"Cannot call a class as a function\");\n    }\n  }\n\n  function _inherits(subClass, superClass) {\n    if (typeof superClass !== \"function\" && superClass !== null) {\n      throw new TypeError(\"Super expression must either be null or a function\");\n    }\n\n    subClass.prototype = Object.create(superClass && superClass.prototype, {\n      constructor: {\n        value: subClass,\n        writable: true,\n        configurable: true,\n      },\n    });\n    if (superClass) _setPrototypeOf(subClass, superClass);\n  }\n\n  function _getPrototypeOf(o) {\n    _getPrototypeOf = Object.setPrototypeOf\n      ? Object.getPrototypeOf\n      : function _getPrototypeOf(o) {\n          return o.__proto__ || Object.getPrototypeOf(o);\n        };\n    return _getPrototypeOf(o);\n  }\n\n  function _setPrototypeOf(o, p) {\n    _setPrototypeOf =\n      Object.setPrototypeOf ||\n      function _setPrototypeOf(o, p) {\n        o.__proto__ = p;\n        return o;\n      };\n\n    return _setPrototypeOf(o, p);\n  }\n\n  function _isNativeReflectConstruct() {\n    if (typeof Reflect === \"undefined\" || !Reflect.construct) return false;\n    if (Reflect.construct.sham) return false;\n    if (typeof Proxy === \"function\") return true;\n\n    try {\n      Boolean.prototype.valueOf.call(\n        Reflect.construct(Boolean, [], function () {})\n      );\n      return true;\n    } catch (e) {\n      return false;\n    }\n  }\n\n  function _construct(Parent, args, Class) {\n    if (_isNativeReflectConstruct()) {\n      _construct = Reflect.construct;\n    } else {\n      _construct = function _construct(Parent, args, Class) {\n        var a = [null];\n        a.push.apply(a, args);\n        var Constructor = Function.bind.apply(Parent, a);\n        var instance = new Constructor();\n        if (Class) _setPrototypeOf(instance, Class.prototype);\n        return instance;\n      };\n    }\n\n    return _construct.apply(null, arguments);\n  }\n\n  function _isNativeFunction(fn) {\n    return Function.toString.call(fn).indexOf(\"[native code]\") !== -1;\n  }\n\n  function _wrapNativeSuper(Class) {\n    var _cache = typeof Map === \"function\" ? new Map() : undefined;\n\n    _wrapNativeSuper = function _wrapNativeSuper(Class) {\n      if (Class === null || !_isNativeFunction(Class)) return Class;\n\n      if (typeof Class !== \"function\") {\n        throw new TypeError(\n          \"Super expression must either be null or a function\"\n        );\n      }\n\n      if (typeof _cache !== \"undefined\") {\n        if (_cache.has(Class)) return _cache.get(Class);\n\n        _cache.set(Class, Wrapper);\n      }\n\n      function Wrapper() {\n        return _construct(Class, arguments, _getPrototypeOf(this).constructor);\n      }\n\n      Wrapper.prototype = Object.create(Class.prototype, {\n        constructor: {\n          value: Wrapper,\n          enumerable: false,\n          writable: true,\n          configurable: true,\n        },\n      });\n      return _setPrototypeOf(Wrapper, Class);\n    };\n\n    return _wrapNativeSuper(Class);\n  }\n\n  function _assertThisInitialized(self) {\n    if (self === void 0) {\n      throw new ReferenceError(\n        \"this hasn't been initialised - super() hasn't been called\"\n      );\n    }\n\n    return self;\n  }\n\n  function _possibleConstructorReturn(self, call) {\n    if (call && (typeof call === \"object\" || typeof call === \"function\")) {\n      return call;\n    }\n\n    return _assertThisInitialized(self);\n  }\n\n  function _createSuper(Derived) {\n    var hasNativeReflectConstruct = _isNativeReflectConstruct();\n\n    return function _createSuperInternal() {\n      var Super = _getPrototypeOf(Derived),\n        result;\n\n      if (hasNativeReflectConstruct) {\n        var NewTarget = _getPrototypeOf(this).constructor;\n\n        result = Reflect.construct(Super, arguments, NewTarget);\n      } else {\n        result = Super.apply(this, arguments);\n      }\n\n      return _possibleConstructorReturn(this, result);\n    };\n  }\n\n  function _toConsumableArray(arr) {\n    return (\n      _arrayWithoutHoles(arr) ||\n      _iterableToArray(arr) ||\n      _unsupportedIterableToArray(arr) ||\n      _nonIterableSpread()\n    );\n  }\n\n  function _arrayWithoutHoles(arr) {\n    if (Array.isArray(arr)) return _arrayLikeToArray(arr);\n  }\n\n  function _iterableToArray(iter) {\n    if (\n      (typeof Symbol !== \"undefined\" && iter[Symbol.iterator] != null) ||\n      iter[\"@@iterator\"] != null\n    )\n      return Array.from(iter);\n  }\n\n  function _unsupportedIterableToArray(o, minLen) {\n    if (!o) return;\n    if (typeof o === \"string\") return _arrayLikeToArray(o, minLen);\n    var n = Object.prototype.toString.call(o).slice(8, -1);\n    if (n === \"Object\" && o.constructor) n = o.constructor.name;\n    if (n === \"Map\" || n === \"Set\") return Array.from(o);\n    if (n === \"Arguments\" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))\n      return _arrayLikeToArray(o, minLen);\n  }\n\n  function _arrayLikeToArray(arr, len) {\n    if (len == null || len > arr.length) len = arr.length;\n\n    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];\n\n    return arr2;\n  }\n\n  function _nonIterableSpread() {\n    throw new TypeError(\n      \"Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"\n    );\n  }\n\n  function _createForOfIteratorHelper(o, allowArrayLike) {\n    var it =\n      (typeof Symbol !== \"undefined\" && o[Symbol.iterator]) || o[\"@@iterator\"];\n\n    if (!it) {\n      if (\n        Array.isArray(o) ||\n        (it = _unsupportedIterableToArray(o)) ||\n        (allowArrayLike && o && typeof o.length === \"number\")\n      ) {\n        if (it) o = it;\n        var i = 0;\n\n        var F = function () {};\n\n        return {\n          s: F,\n          n: function () {\n            if (i >= o.length)\n              return {\n                done: true,\n              };\n            return {\n              done: false,\n              value: o[i++],\n            };\n          },\n          e: function (e) {\n            throw e;\n          },\n          f: F,\n        };\n      }\n\n      throw new TypeError(\n        \"Invalid attempt to iterate non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.\"\n      );\n    }\n\n    var normalCompletion = true,\n      didErr = false,\n      err;\n    return {\n      s: function () {\n        it = it.call(o);\n      },\n      n: function () {\n        var step = it.next();\n        normalCompletion = step.done;\n        return step;\n      },\n      e: function (e) {\n        didErr = true;\n        err = e;\n      },\n      f: function () {\n        try {\n          if (!normalCompletion && it[\"return\"] != null) it[\"return\"]();\n        } finally {\n          if (didErr) throw err;\n        }\n      },\n    };\n  }\n\n  var hasOwnProp = Object.prototype.hasOwnProperty;\n  /**\n   * @typedef {null|boolean|number|string|PlainObject|GenericArray} JSONObject\n   */\n\n  /**\n   * Copies array and then pushes item into it.\n   * @param {GenericArray} arr Array to copy and into which to push\n   * @param {any} item Array item to add (to end)\n   * @returns {GenericArray} Copy of the original array\n   */\n\n  function push(arr, item) {\n    arr = arr.slice();\n    arr.push(item);\n    return arr;\n  }\n  /**\n   * Copies array and then unshifts item into it.\n   * @param {any} item Array item to add (to beginning)\n   * @param {GenericArray} arr Array to copy and into which to unshift\n   * @returns {GenericArray} Copy of the original array\n   */\n\n  function unshift(item, arr) {\n    arr = arr.slice();\n    arr.unshift(item);\n    return arr;\n  }\n  /**\n   * Caught when JSONPath is used without `new` but rethrown if with `new`\n   * @extends Error\n   */\n\n  var NewError = /*#__PURE__*/ (function (_Error) {\n    _inherits(NewError, _Error);\n\n    var _super = _createSuper(NewError);\n\n    /**\n     * @param {any} value The evaluated scalar value\n     */\n    function NewError(value) {\n      var _this;\n\n      _classCallCheck(this, NewError);\n\n      _this = _super.call(\n        this,\n        'JSONPath should not be called with \"new\" (it prevents return ' +\n          \"of (unwrapped) scalar values)\"\n      );\n      _this.avoidNew = true;\n      _this.value = value;\n      _this.name = \"NewError\";\n      return _this;\n    }\n\n    return NewError;\n  })(/*#__PURE__*/ _wrapNativeSuper(Error));\n  /**\n   * @typedef {PlainObject} ReturnObject\n   * @property {string} path\n   * @property {JSONObject} value\n   * @property {PlainObject|GenericArray} parent\n   * @property {string} parentProperty\n   */\n\n  /**\n   * @callback JSONPathCallback\n   * @param {string|PlainObject} preferredOutput\n   * @param {\"value\"|\"property\"} type\n   * @param {ReturnObject} fullRetObj\n   * @returns {void}\n   */\n\n  /**\n   * @callback OtherTypeCallback\n   * @param {JSONObject} val\n   * @param {string} path\n   * @param {PlainObject|GenericArray} parent\n   * @param {string} parentPropName\n   * @returns {boolean}\n   */\n\n  /* eslint-disable max-len -- Can make multiline type after https://github.com/syavorsky/comment-parser/issues/109 */\n\n  /**\n   * @typedef {PlainObject} JSONPathOptions\n   * @property {JSON} json\n   * @property {string|string[]} path\n   * @property {\"value\"|\"path\"|\"pointer\"|\"parent\"|\"parentProperty\"|\"all\"} [resultType=\"value\"]\n   * @property {boolean} [flatten=false]\n   * @property {boolean} [wrap=true]\n   * @property {PlainObject} [sandbox={}]\n   * @property {boolean} [preventEval=false]\n   * @property {PlainObject|GenericArray|null} [parent=null]\n   * @property {string|null} [parentProperty=null]\n   * @property {JSONPathCallback} [callback]\n   * @property {OtherTypeCallback} [otherTypeCallback] Defaults to\n   *   function which throws on encountering `@other`\n   * @property {boolean} [autostart=true]\n   */\n\n  /* eslint-enable max-len -- Can make multiline type after https://github.com/syavorsky/comment-parser/issues/109 */\n\n  /**\n   * @param {string|JSONPathOptions} opts If a string, will be treated as `expr`\n   * @param {string} [expr] JSON path to evaluate\n   * @param {JSON} [obj] JSON object to evaluate against\n   * @param {JSONPathCallback} [callback] Passed 3 arguments: 1) desired payload\n   *     per `resultType`, 2) `\"value\"|\"property\"`, 3) Full returned object with\n   *     all payloads\n   * @param {OtherTypeCallback} [otherTypeCallback] If `@other()` is at the end\n   *   of one's query, this will be invoked with the value of the item, its\n   *   path, its parent, and its parent's property name, and it should return\n   *   a boolean indicating whether the supplied value belongs to the \"other\"\n   *   type or not (or it may handle transformations and return `false`).\n   * @returns {JSONPath}\n   * @class\n   */\n\n  function JSONPath(opts, expr, obj, callback, otherTypeCallback) {\n    // eslint-disable-next-line no-restricted-syntax\n    if (!(this instanceof JSONPath)) {\n      try {\n        return new JSONPath(opts, expr, obj, callback, otherTypeCallback);\n      } catch (e) {\n        if (!e.avoidNew) {\n          throw e;\n        }\n\n        return e.value;\n      }\n    }\n\n    if (typeof opts === \"string\") {\n      otherTypeCallback = callback;\n      callback = obj;\n      obj = expr;\n      expr = opts;\n      opts = null;\n    }\n\n    var optObj = opts && _typeof(opts) === \"object\";\n    opts = opts || {};\n    this.json = opts.json || obj;\n    this.path = opts.path || expr;\n    this.resultType = opts.resultType || \"value\";\n    this.flatten = opts.flatten || false;\n    this.wrap = hasOwnProp.call(opts, \"wrap\") ? opts.wrap : true;\n    this.sandbox = opts.sandbox || {};\n    this.preventEval = opts.preventEval || false;\n    this.parent = opts.parent || null;\n    this.parentProperty = opts.parentProperty || null;\n    this.callback = opts.callback || callback || null;\n\n    this.otherTypeCallback =\n      opts.otherTypeCallback ||\n      otherTypeCallback ||\n      function () {\n        throw new TypeError(\n          \"You must supply an otherTypeCallback callback option \" +\n            \"with the @other() operator.\"\n        );\n      };\n\n    if (opts.autostart !== false) {\n      var args = {\n        path: optObj ? opts.path : expr,\n      };\n\n      if (!optObj) {\n        args.json = obj;\n      } else if (\"json\" in opts) {\n        args.json = opts.json;\n      }\n\n      var ret = this.evaluate(args);\n\n      if (!ret || _typeof(ret) !== \"object\") {\n        throw new NewError(ret);\n      }\n\n      return ret;\n    }\n  } // PUBLIC METHODS\n\n  JSONPath.prototype.evaluate = function (\n    expr,\n    json,\n    callback,\n    otherTypeCallback\n  ) {\n    var _this2 = this;\n\n    var currParent = this.parent,\n      currParentProperty = this.parentProperty;\n    var flatten = this.flatten,\n      wrap = this.wrap;\n    this.currResultType = this.resultType;\n    this.currPreventEval = this.preventEval;\n    this.currSandbox = this.sandbox;\n    callback = callback || this.callback;\n    this.currOtherTypeCallback = otherTypeCallback || this.otherTypeCallback;\n    json = json || this.json;\n    expr = expr || this.path;\n\n    if (expr && _typeof(expr) === \"object\" && !Array.isArray(expr)) {\n      if (!expr.path && expr.path !== \"\") {\n        throw new TypeError(\n          'You must supply a \"path\" property when providing an object ' +\n            \"argument to JSONPath.evaluate().\"\n        );\n      }\n\n      if (!hasOwnProp.call(expr, \"json\")) {\n        throw new TypeError(\n          'You must supply a \"json\" property when providing an object ' +\n            \"argument to JSONPath.evaluate().\"\n        );\n      }\n\n      var _expr = expr;\n      json = _expr.json;\n      flatten = hasOwnProp.call(expr, \"flatten\") ? expr.flatten : flatten;\n      this.currResultType = hasOwnProp.call(expr, \"resultType\")\n        ? expr.resultType\n        : this.currResultType;\n      this.currSandbox = hasOwnProp.call(expr, \"sandbox\")\n        ? expr.sandbox\n        : this.currSandbox;\n      wrap = hasOwnProp.call(expr, \"wrap\") ? expr.wrap : wrap;\n      this.currPreventEval = hasOwnProp.call(expr, \"preventEval\")\n        ? expr.preventEval\n        : this.currPreventEval;\n      callback = hasOwnProp.call(expr, \"callback\") ? expr.callback : callback;\n      this.currOtherTypeCallback = hasOwnProp.call(expr, \"otherTypeCallback\")\n        ? expr.otherTypeCallback\n        : this.currOtherTypeCallback;\n      currParent = hasOwnProp.call(expr, \"parent\") ? expr.parent : currParent;\n      currParentProperty = hasOwnProp.call(expr, \"parentProperty\")\n        ? expr.parentProperty\n        : currParentProperty;\n      expr = expr.path;\n    }\n\n    currParent = currParent || null;\n    currParentProperty = currParentProperty || null;\n\n    if (Array.isArray(expr)) {\n      expr = JSONPath.toPathString(expr);\n    }\n\n    if ((!expr && expr !== \"\") || !json) {\n      return undefined;\n    }\n\n    var exprList = JSONPath.toPathArray(expr);\n\n    if (exprList[0] === \"$\" && exprList.length > 1) {\n      exprList.shift();\n    }\n\n    this._hasParentSelector = null;\n\n    var result = this._trace(\n      exprList,\n      json,\n      [\"$\"],\n      currParent,\n      currParentProperty,\n      callback\n    ).filter(function (ea) {\n      return ea && !ea.isParentSelector;\n    });\n\n    if (!result.length) {\n      return wrap ? [] : undefined;\n    }\n\n    if (!wrap && result.length === 1 && !result[0].hasArrExpr) {\n      return this._getPreferredOutput(result[0]);\n    }\n\n    return result.reduce(function (rslt, ea) {\n      var valOrPath = _this2._getPreferredOutput(ea);\n\n      if (flatten && Array.isArray(valOrPath)) {\n        rslt = rslt.concat(valOrPath);\n      } else {\n        rslt.push(valOrPath);\n      }\n\n      return rslt;\n    }, []);\n  }; // PRIVATE METHODS\n\n  JSONPath.prototype._getPreferredOutput = function (ea) {\n    var resultType = this.currResultType;\n\n    switch (resultType) {\n      case \"all\": {\n        var path = Array.isArray(ea.path)\n          ? ea.path\n          : JSONPath.toPathArray(ea.path);\n        ea.pointer = JSONPath.toPointer(path);\n        ea.path =\n          typeof ea.path === \"string\"\n            ? ea.path\n            : JSONPath.toPathString(ea.path);\n        return ea;\n      }\n\n      case \"value\":\n      case \"parent\":\n      case \"parentProperty\":\n        return ea[resultType];\n\n      case \"path\":\n        return JSONPath.toPathString(ea[resultType]);\n\n      case \"pointer\":\n        return JSONPath.toPointer(ea.path);\n\n      default:\n        throw new TypeError(\"Unknown result type\");\n    }\n  };\n\n  JSONPath.prototype._handleCallback = function (fullRetObj, callback, type) {\n    if (callback) {\n      var preferredOutput = this._getPreferredOutput(fullRetObj);\n\n      fullRetObj.path =\n        typeof fullRetObj.path === \"string\"\n          ? fullRetObj.path\n          : JSONPath.toPathString(fullRetObj.path); // eslint-disable-next-line node/callback-return\n\n      callback(preferredOutput, type, fullRetObj);\n    }\n  };\n  /**\n   *\n   * @param {string} expr\n   * @param {JSONObject} val\n   * @param {string} path\n   * @param {PlainObject|GenericArray} parent\n   * @param {string} parentPropName\n   * @param {JSONPathCallback} callback\n   * @param {boolean} hasArrExpr\n   * @param {boolean} literalPriority\n   * @returns {ReturnObject|ReturnObject[]}\n   */\n\n  JSONPath.prototype._trace = function (\n    expr,\n    val,\n    path,\n    parent,\n    parentPropName,\n    callback,\n    hasArrExpr,\n    literalPriority\n  ) {\n    var _this3 = this;\n\n    // No expr to follow? return path and value as the result of\n    //  this trace branch\n    var retObj;\n\n    if (!expr.length) {\n      retObj = {\n        path: path,\n        value: val,\n        parent: parent,\n        parentProperty: parentPropName,\n        hasArrExpr: hasArrExpr,\n      };\n\n      this._handleCallback(retObj, callback, \"value\");\n\n      return retObj;\n    }\n\n    var loc = expr[0],\n      x = expr.slice(1); // We need to gather the return value of recursive trace calls in order to\n    // do the parent sel computation.\n\n    var ret = [];\n    /**\n     *\n     * @param {ReturnObject|ReturnObject[]} elems\n     * @returns {void}\n     */\n\n    function addRet(elems) {\n      if (Array.isArray(elems)) {\n        // This was causing excessive stack size in Node (with or\n        //  without Babel) against our performance test:\n        //  `ret.push(...elems);`\n        elems.forEach(function (t) {\n          ret.push(t);\n        });\n      } else {\n        ret.push(elems);\n      }\n    }\n\n    if (\n      (typeof loc !== \"string\" || literalPriority) &&\n      val &&\n      hasOwnProp.call(val, loc)\n    ) {\n      // simple case--directly follow property\n      addRet(\n        this._trace(\n          x,\n          val[loc],\n          push(path, loc),\n          val,\n          loc,\n          callback,\n          hasArrExpr\n        )\n      ); // eslint-disable-next-line unicorn/prefer-switch -- Part of larger `if`\n    } else if (loc === \"*\") {\n      // all child properties\n      this._walk(\n        loc,\n        x,\n        val,\n        path,\n        parent,\n        parentPropName,\n        callback,\n        function (m, l, _x, v, p, par, pr, cb) {\n          addRet(_this3._trace(unshift(m, _x), v, p, par, pr, cb, true, true));\n        }\n      );\n    } else if (loc === \"..\") {\n      // all descendent parent properties\n      // Check remaining expression with val's immediate children\n      addRet(\n        this._trace(x, val, path, parent, parentPropName, callback, hasArrExpr)\n      );\n\n      this._walk(\n        loc,\n        x,\n        val,\n        path,\n        parent,\n        parentPropName,\n        callback,\n        function (m, l, _x, v, p, par, pr, cb) {\n          // We don't join m and x here because we only want parents,\n          //   not scalar values\n          if (_typeof(v[m]) === \"object\") {\n            // Keep going with recursive descent on val's\n            //   object children\n            addRet(\n              _this3._trace(unshift(l, _x), v[m], push(p, m), v, m, cb, true)\n            );\n          }\n        }\n      ); // The parent sel computation is handled in the frame above using the\n      // ancestor object of val\n    } else if (loc === \"^\") {\n      // This is not a final endpoint, so we do not invoke the callback here\n      this._hasParentSelector = true;\n      return {\n        path: path.slice(0, -1),\n        expr: x,\n        isParentSelector: true,\n      };\n    } else if (loc === \"~\") {\n      // property name\n      retObj = {\n        path: push(path, loc),\n        value: parentPropName,\n        parent: parent,\n        parentProperty: null,\n      };\n\n      this._handleCallback(retObj, callback, \"property\");\n\n      return retObj;\n    } else if (loc === \"$\") {\n      // root only\n      addRet(this._trace(x, val, path, null, null, callback, hasArrExpr));\n    } else if (/^(\\x2D?[0-9]*):(\\x2D?[0-9]*):?([0-9]*)$/.test(loc)) {\n      // [start:end:step]  Python slice syntax\n      addRet(this._slice(loc, x, val, path, parent, parentPropName, callback));\n    } else if (loc.indexOf(\"?(\") === 0) {\n      // [?(expr)] (filtering)\n      if (this.currPreventEval) {\n        throw new Error(\"Eval [?(expr)] prevented in JSONPath expression.\");\n      }\n\n      this._walk(\n        loc,\n        x,\n        val,\n        path,\n        parent,\n        parentPropName,\n        callback,\n        function (m, l, _x, v, p, par, pr, cb) {\n          if (\n            _this3._eval(\n              l.replace(\n                /^\\?\\(((?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])*?)\\)$/,\n                \"$1\"\n              ),\n              v[m],\n              m,\n              p,\n              par,\n              pr\n            )\n          ) {\n            addRet(_this3._trace(unshift(m, _x), v, p, par, pr, cb, true));\n          }\n        }\n      );\n    } else if (loc[0] === \"(\") {\n      // [(expr)] (dynamic property/index)\n      if (this.currPreventEval) {\n        throw new Error(\"Eval [(expr)] prevented in JSONPath expression.\");\n      } // As this will resolve to a property name (but we don't know it\n      //  yet), property and parent information is relative to the\n      //  parent of the property to which this expression will resolve\n\n      addRet(\n        this._trace(\n          unshift(\n            this._eval(\n              loc,\n              val,\n              path[path.length - 1],\n              path.slice(0, -1),\n              parent,\n              parentPropName\n            ),\n            x\n          ),\n          val,\n          path,\n          parent,\n          parentPropName,\n          callback,\n          hasArrExpr\n        )\n      );\n    } else if (loc[0] === \"@\") {\n      // value type: @boolean(), etc.\n      var addType = false;\n      var valueType = loc.slice(1, -2);\n\n      switch (valueType) {\n        case \"scalar\":\n          if (!val || ![\"object\", \"function\"].includes(_typeof(val))) {\n            addType = true;\n          }\n\n          break;\n\n        case \"boolean\":\n        case \"string\":\n        case \"undefined\":\n        case \"function\":\n          // eslint-disable-next-line valid-typeof\n          if (_typeof(val) === valueType) {\n            addType = true;\n          }\n\n          break;\n\n        case \"integer\":\n          if (Number.isFinite(val) && !(val % 1)) {\n            addType = true;\n          }\n\n          break;\n\n        case \"number\":\n          if (Number.isFinite(val)) {\n            addType = true;\n          }\n\n          break;\n\n        case \"nonFinite\":\n          if (typeof val === \"number\" && !Number.isFinite(val)) {\n            addType = true;\n          }\n\n          break;\n\n        case \"object\":\n          // eslint-disable-next-line valid-typeof\n          if (val && _typeof(val) === valueType) {\n            addType = true;\n          }\n\n          break;\n\n        case \"array\":\n          if (Array.isArray(val)) {\n            addType = true;\n          }\n\n          break;\n\n        case \"other\":\n          addType = this.currOtherTypeCallback(\n            val,\n            path,\n            parent,\n            parentPropName\n          );\n          break;\n\n        case \"null\":\n          if (val === null) {\n            addType = true;\n          }\n\n          break;\n\n        /* c8 ignore next 2 */\n\n        default:\n          throw new TypeError(\"Unknown value type \" + valueType);\n      }\n\n      if (addType) {\n        retObj = {\n          path: path,\n          value: val,\n          parent: parent,\n          parentProperty: parentPropName,\n        };\n\n        this._handleCallback(retObj, callback, \"value\");\n\n        return retObj;\n      } // `-escaped property\n    } else if (loc[0] === \"`\" && val && hasOwnProp.call(val, loc.slice(1))) {\n      var locProp = loc.slice(1);\n      addRet(\n        this._trace(\n          x,\n          val[locProp],\n          push(path, locProp),\n          val,\n          locProp,\n          callback,\n          hasArrExpr,\n          true\n        )\n      );\n    } else if (loc.includes(\",\")) {\n      // [name1,name2,...]\n      var parts = loc.split(\",\");\n\n      var _iterator = _createForOfIteratorHelper(parts),\n        _step;\n\n      try {\n        for (_iterator.s(); !(_step = _iterator.n()).done; ) {\n          var part = _step.value;\n          addRet(\n            this._trace(\n              unshift(part, x),\n              val,\n              path,\n              parent,\n              parentPropName,\n              callback,\n              true\n            )\n          );\n        } // simple case--directly follow property\n      } catch (err) {\n        _iterator.e(err);\n      } finally {\n        _iterator.f();\n      }\n    } else if (!literalPriority && val && hasOwnProp.call(val, loc)) {\n      addRet(\n        this._trace(\n          x,\n          val[loc],\n          push(path, loc),\n          val,\n          loc,\n          callback,\n          hasArrExpr,\n          true\n        )\n      );\n    } // We check the resulting values for parent selections. For parent\n    // selections we discard the value object and continue the trace with the\n    // current val object\n\n    if (this._hasParentSelector) {\n      for (var t = 0; t < ret.length; t++) {\n        var rett = ret[t];\n\n        if (rett && rett.isParentSelector) {\n          var tmp = this._trace(\n            rett.expr,\n            val,\n            rett.path,\n            parent,\n            parentPropName,\n            callback,\n            hasArrExpr\n          );\n\n          if (Array.isArray(tmp)) {\n            ret[t] = tmp[0];\n            var tl = tmp.length;\n\n            for (var tt = 1; tt < tl; tt++) {\n              t++;\n              ret.splice(t, 0, tmp[tt]);\n            }\n          } else {\n            ret[t] = tmp;\n          }\n        }\n      }\n    }\n\n    return ret;\n  };\n\n  JSONPath.prototype._walk = function (\n    loc,\n    expr,\n    val,\n    path,\n    parent,\n    parentPropName,\n    callback,\n    f\n  ) {\n    if (Array.isArray(val)) {\n      var n = val.length;\n\n      for (var i = 0; i < n; i++) {\n        f(i, loc, expr, val, path, parent, parentPropName, callback);\n      }\n    } else if (val && _typeof(val) === \"object\") {\n      Object.keys(val).forEach(function (m) {\n        f(m, loc, expr, val, path, parent, parentPropName, callback);\n      });\n    }\n  };\n\n  JSONPath.prototype._slice = function (\n    loc,\n    expr,\n    val,\n    path,\n    parent,\n    parentPropName,\n    callback\n  ) {\n    if (!Array.isArray(val)) {\n      return undefined;\n    }\n\n    var len = val.length,\n      parts = loc.split(\":\"),\n      step = (parts[2] && Number.parseInt(parts[2])) || 1;\n    var start = (parts[0] && Number.parseInt(parts[0])) || 0,\n      end = (parts[1] && Number.parseInt(parts[1])) || len;\n    start = start < 0 ? Math.max(0, start + len) : Math.min(len, start);\n    end = end < 0 ? Math.max(0, end + len) : Math.min(len, end);\n    var ret = [];\n\n    for (var i = start; i < end; i += step) {\n      var tmp = this._trace(\n        unshift(i, expr),\n        val,\n        path,\n        parent,\n        parentPropName,\n        callback,\n        true\n      ); // Should only be possible to be an array here since first part of\n      //   ``unshift(i, expr)` passed in above would not be empty, nor `~`,\n      //     nor begin with `@` (as could return objects)\n      // This was causing excessive stack size in Node (with or\n      //  without Babel) against our performance test: `ret.push(...tmp);`\n\n      tmp.forEach(function (t) {\n        ret.push(t);\n      });\n    }\n\n    return ret;\n  };\n\n  JSONPath.prototype._eval = function (\n    code,\n    _v,\n    _vname,\n    path,\n    parent,\n    parentPropName\n  ) {\n    if (code.includes(\"@parentProperty\")) {\n      this.currSandbox._$_parentProperty = parentPropName;\n      code = code.replace(/@parentProperty/g, \"_$_parentProperty\");\n    }\n\n    if (code.includes(\"@parent\")) {\n      this.currSandbox._$_parent = parent;\n      code = code.replace(/@parent/g, \"_$_parent\");\n    }\n\n    if (code.includes(\"@property\")) {\n      this.currSandbox._$_property = _vname;\n      code = code.replace(/@property/g, \"_$_property\");\n    }\n\n    if (code.includes(\"@path\")) {\n      this.currSandbox._$_path = JSONPath.toPathString(path.concat([_vname]));\n      code = code.replace(/@path/g, \"_$_path\");\n    }\n\n    if (code.includes(\"@root\")) {\n      this.currSandbox._$_root = this.json;\n      code = code.replace(/@root/g, \"_$_root\");\n    }\n\n    if (\n      /@([\\t-\\r \\)\\.\\[\\xA0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF])/.test(\n        code\n      )\n    ) {\n      this.currSandbox._$_v = _v;\n      code = code.replace(\n        /@([\\t-\\r \\)\\.\\[\\xA0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF])/g,\n        \"_$_v$1\"\n      );\n    }\n\n    try {\n      return this.vm.runInNewContext(code, this.currSandbox);\n    } catch (e) {\n      // eslint-disable-next-line no-console\n      console.log(e);\n      throw new Error(\"jsonPath: \" + e.message + \": \" + code);\n    }\n  }; // PUBLIC CLASS PROPERTIES AND METHODS\n  // Could store the cache object itself\n\n  JSONPath.cache = {};\n  /**\n   * @param {string[]} pathArr Array to convert\n   * @returns {string} The path string\n   */\n\n  JSONPath.toPathString = function (pathArr) {\n    var x = pathArr,\n      n = x.length;\n    var p = \"$\";\n\n    for (var i = 1; i < n; i++) {\n      if (\n        !/^(~|\\^|@(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])*?\\(\\))$/.test(\n          x[i]\n        )\n      ) {\n        p += /^[\\*0-9]+$/.test(x[i]) ? \"[\" + x[i] + \"]\" : \"['\" + x[i] + \"']\";\n      }\n    }\n\n    return p;\n  };\n  /**\n   * @param {string} pointer JSON Path\n   * @returns {string} JSON Pointer\n   */\n\n  JSONPath.toPointer = function (pointer) {\n    var x = pointer,\n      n = x.length;\n    var p = \"\";\n\n    for (var i = 1; i < n; i++) {\n      if (\n        !/^(~|\\^|@(?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])*?\\(\\))$/.test(\n          x[i]\n        )\n      ) {\n        p += \"/\" + x[i].toString().replace(/~/g, \"~0\").replace(/\\//g, \"~1\");\n      }\n    }\n\n    return p;\n  };\n  /**\n   * @param {string} expr Expression to convert\n   * @returns {string[]}\n   */\n\n  JSONPath.toPathArray = function (expr) {\n    var cache = JSONPath.cache;\n\n    if (cache[expr]) {\n      return cache[expr].concat();\n    }\n\n    var subx = [];\n    var normalized = expr // Properties\n      .replace(\n        /@(?:null|boolean|number|string|integer|undefined|nonFinite|scalar|array|object|function|other)\\(\\)/g,\n        \";$&;\"\n      ) // Parenthetical evaluations (filtering and otherwise), directly\n      //   within brackets or single quotes\n      .replace(\n        /['\\[](\\??\\((?:[\\0-\\t\\x0B\\f\\x0E-\\u2027\\u202A-\\uD7FF\\uE000-\\uFFFF]|[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]|[\\uD800-\\uDBFF](?![\\uDC00-\\uDFFF])|(?:[^\\uD800-\\uDBFF]|^)[\\uDC00-\\uDFFF])*?\\))['\\]]/g,\n        function ($0, $1) {\n          return \"[#\" + (subx.push($1) - 1) + \"]\";\n        }\n      ) // Escape periods and tildes within properties\n      .replace(/\\[[\"']((?:(?!['\\]])[\\s\\S])*)[\"']\\]/g, function ($0, prop) {\n        return \"['\" + prop.replace(/\\./g, \"%@%\").replace(/~/g, \"%%@@%%\") + \"']\";\n      }) // Properties operator\n      .replace(/~/g, \";~;\") // Split by property boundaries\n      .replace(/[\"']?\\.[\"']?(?!(?:(?!\\[)[\\s\\S])*\\])|\\[[\"']?/g, \";\") // Reinsert periods within properties\n      .replace(/%@%/g, \".\") // Reinsert tildes within properties\n      .replace(/%%@@%%/g, \"~\") // Parent\n      .replace(/(?:;)?(\\^+)(?:;)?/g, function ($0, ups) {\n        return \";\" + ups.split(\"\").join(\";\") + \";\";\n      }) // Descendents\n      .replace(/;;;|;;/g, \";..;\") // Remove trailing\n      .replace(/;$|'?\\]|'$/g, \"\");\n    var exprList = normalized.split(\";\").map(function (exp) {\n      var match = exp.match(/#([0-9]+)/);\n      return !match || !match[1] ? exp : subx[match[1]];\n    });\n    cache[expr] = exprList;\n    return cache[expr].concat();\n  };\n\n  /**\n   * @callback ConditionCallback\n   * @param {any} item\n   * @returns {boolean}\n   */\n\n  /**\n   * Copy items out of one array into another.\n   * @param {GenericArray} source Array with items to copy\n   * @param {GenericArray} target Array to which to copy\n   * @param {ConditionCallback} conditionCb Callback passed the current item;\n   *     will move item if evaluates to `true`\n   * @returns {void}\n   */\n\n  var moveToAnotherArray = function moveToAnotherArray(\n    source,\n    target,\n    conditionCb\n  ) {\n    var il = source.length;\n\n    for (var i = 0; i < il; i++) {\n      var item = source[i];\n\n      if (conditionCb(item)) {\n        target.push(source.splice(i--, 1)[0]);\n      }\n    }\n  };\n\n  JSONPath.prototype.vm = {\n    /**\n     * @param {string} expr Expression to evaluate\n     * @param {PlainObject} context Object whose items will be added\n     *   to evaluation\n     * @returns {any} Result of evaluated code\n     */\n    runInNewContext: function runInNewContext(expr, context) {\n      var keys = Object.keys(context);\n      var funcs = [];\n      moveToAnotherArray(keys, funcs, function (key) {\n        return typeof context[key] === \"function\";\n      });\n      var values = keys.map(function (vr, i) {\n        return context[vr];\n      });\n      var funcString = funcs.reduce(function (s, func) {\n        var fString = context[func].toString();\n\n        if (!/function/.test(fString)) {\n          fString = \"function \" + fString;\n        }\n\n        return \"var \" + func + \"=\" + fString + \";\" + s;\n      }, \"\");\n      expr = funcString + expr; // Mitigate http://perfectionkills.com/global-eval-what-are-the-options/#new_function\n\n      if (!/([\"'])use strict\\1/.test(expr) && !keys.includes(\"arguments\")) {\n        expr = \"var arguments = undefined;\" + expr;\n      } // Remove last semi so `return` will be inserted before\n      //  the previous one instead, allowing for the return\n      //  of a bare ending expression\n\n      expr = expr.replace(\n        /;[\\t-\\r \\xA0\\u1680\\u2000-\\u200A\\u2028\\u2029\\u202F\\u205F\\u3000\\uFEFF]*$/,\n        \"\"\n      ); // Insert `return`\n\n      var lastStatementEnd = expr.lastIndexOf(\";\");\n      var code =\n        lastStatementEnd > -1\n          ? expr.slice(0, lastStatementEnd + 1) +\n            \" return \" +\n            expr.slice(lastStatementEnd + 1)\n          : \" return \" + expr; // eslint-disable-next-line no-new-func\n\n      return _construct(\n        Function,\n        _toConsumableArray(keys).concat([code])\n      ).apply(void 0, _toConsumableArray(values));\n    },\n  };\n\n  exports.JSONPath = JSONPath;\n\n  Object.defineProperty(exports, \"__esModule\", { value: true });\n});\nvar JSONPath = JSONPath.JSONPath;\n"
  },
  {
    "path": "Server-Side Components/Script Includes/JSONPath/README.md",
    "content": "# JSONPath Plus\n\nAnalyse, transform, and selectively extract data from JSON documents (and JavaScript objects).\n\njsonpath-plus expands on the original specification to add some additional operators and makes explicit some behaviors the original did not spell out.\n\nYou can try it within the [browser demo](https://jsonpath-plus.github.io/JSONPath/demo/)\n\n## Features\n\n- **Compliant** with the original jsonpath spec\n- Convenient **additions or elaborations** not provided in the original spec:\n  - `^` for grabbing the **parent** of a matching item\n  - `~` for grabbing **property names** of matching items (as array)\n  - **Type selectors** for obtaining:\n    - Basic JSON types: `@null()`, `@boolean()`, `@number()`, `@string()`, `@array()`, `@object()`\n    - `@integer()`\n    - The compound type `@scalar()` (which also accepts `undefined` and\n      non-finite numbers when querying JavaScript objects as well as all of the basic non-object/non-function types)\n    - `@other()` usable in conjunction with a user-defined `otherTypeCallback`\n    - Non-JSON types that can nevertheless be used when querying\n      non-JSON JavaScript objects (`@undefined()`, `@function()`, `@nonFinite()`)\n  - `@path`/`@parent`/`@property`/`@parentProperty`/`@root` **shorthand selectors** within filters\n  - **Escaping**\n    - `` ` `` for escaping remaining sequence\n    - `@['...']`/`?@['...']` syntax for escaping special characters within\n      property names in filters\n  - Documents `$..` (**getting all parent components**)\n- In addition to queried values, **can return various meta-information**\n  including paths or pointers to the value, as well as the parent\n  object and parent property name (to allow for modification).\n- **Utilities for converting** between paths, arrays, and pointers\n- Option to **prevent evaluations** permitted in the original spec or supply\n  a **sandbox** for evaluated values.\n- Option for **callback to handle results** as they are obtained.\n\n## Setup\n\n1. create a script include `JSONPath`\n2. fill in the script field with the content of [JSONPath.js file](JSONPath.js)\n3. you should now be able to call it via globally available function e.g. `JSONPath(path, json)`\n\n## Usage\n\nThe full signature available is:\n\n```\nvar result = JSONPath([options,] path, json, callback, otherTypeCallback);\n```\n\nThe arguments `path`, `json`, `callback`, and `otherTypeCallback`\ncan alternatively be expressed (along with any other of the\navailable properties) on `options`.\n\nNote that `result` will contain all items found (optionally\nwrapped into an array) whereas `callback` can be used if you\nwish to perform some operation as each item is discovered, with\nthe callback function being executed 0 to N times depending\non the number of independent items to be found in the result.\nSee the docs below for more on `JSONPath`'s available arguments.\n\nSee also the [API docs](https://jsonpath-plus.github.io/JSONPath/docs/ts/).\n\n### Properties\n\nThe properties that can be supplied on the options object or\nevaluate method (as the first argument) include:\n\n- **_path_** (**required**) - The JSONPath expression as a (normalized\n  or unnormalized) string or array\n- **_json_** (**required**) - The JSON object to evaluate (whether of\n  null, boolean, number, string, object, or array type).\n- **_autostart_** (**default: true**) - If this is supplied as `false`,\n  one may call the `evaluate` method manually.\n- **_flatten_** (**default: false**) - Whether the returned array of results\n  will be flattened to a single dimension array.\n- **_resultType_** (**default: \"value\"**) - Can be case-insensitive form of\n  \"value\", \"path\", \"pointer\", \"parent\", or \"parentProperty\" to determine\n  respectively whether to return results as the values of the found items,\n  as their absolute paths, as [JSON Pointers](https://tools.ietf.org/html/rfc6901)\n  to the absolute paths, as their parent objects, or as their parent's\n  property name. If set to \"all\", all of these types will be returned on\n  an object with the type as key name.\n- **_sandbox_** (**default: {}**) - Key-value map of variables to be\n  available to code evaluations such as filtering expressions. (Note\n  that the current path and value will also be available to those\n  expressions; see the Syntax section for details.)\n- **_wrap_** (**default: true**) - Whether or not to wrap the results\n  in an array. If `wrap` is set to `false`, and no results are found,\n  `undefined` will be returned (as opposed to an empty array when\n  `wrap` is set to true). If `wrap` is set to `false` and a single\n  non-array result is found, that result will be the only item returned\n  (not within an array). An array will still be returned if multiple\n  results are found, however. To avoid ambiguities (in the case where\n  it is necessary to distinguish between a result which is a failure\n  and one which is an empty array), it is recommended to switch the\n  default to `false`.\n- **_preventEval_** (**default: false**) - Although JavaScript evaluation\n  expressions are allowed by default, for security reasons (if one is\n  operating on untrusted user input, for example), one may wish to\n  set this option to `true` to throw exceptions when these expressions\n  are attempted.\n- **_parent_** (**default: null**) - In the event that a query could be\n  made to return the root node, this allows the parent of that root node\n  to be returned within results.\n- **_parentProperty_** (**default: null**) - In the event that a query\n  could be made to return the root node, this allows the `parentProperty`\n  of that root node to be returned within results.\n- **_callback_** (**default: (none)**) - If supplied, a callback will be\n  called immediately upon retrieval of an end point value. The three arguments\n  supplied will be the value of the payload (according to `resultType`),\n  the type of the payload (whether it is a normal \"value\" or a \"property\"\n  name), and a full payload object (with all `resultType`s).\n- **_otherTypeCallback_** (**default: \\<A function that throws an error**\n  **when @other() is encountered\\>**) - In the current absence of JSON\n  Schema support, one can determine types beyond the built-in types by\n  adding the operator `@other()` at the end of one's query. If such a\n  path is encountered, the `otherTypeCallback` will be invoked with the\n  value of the item, its path, its parent, and its parent's property name,\n  and it should return a boolean indicating whether the supplied value\n  belongs to the \"other\" type or not (or it may handle transformations and\n  return false).\n\n### Instance methods\n\n- **_evaluate(path, json, callback, otherTypeCallback)_** OR\n  **_evaluate({path: \\<path\\>, json: \\<json object\\>, callback:_**\n  **_\\<callback function\\>, otherTypeCallback:_**\n  **_\\<otherTypeCallback function\\>})_** - This method is only\n  necessary if the `autostart` property is set to `false`. It\n  can be used for repeated evaluations using the same configuration.\n  Besides the listed properties, the latter method pattern can\n  accept any of the other allowed instance properties (except\n  for `autostart` which would have no relevance here).\n\n### Class properties and methods\n\n- **_JSONPath.cache_** - Exposes the cache object for those who wish\n  to preserve and reuse it for optimization purposes.\n- **_JSONPath.toPathArray(pathAsString)_** - Accepts a normalized or\n  unnormalized path as string and converts to an array: for\n  example, `['$', 'aProperty', 'anotherProperty']`.\n- **_JSONPath.toPathString(pathAsArray)_** - Accepts a path array and\n  converts to a normalized path string. The string will be in a form\n  like: `$['aProperty']['anotherProperty][0]`. The JSONPath terminal\n  constructions `~` and `^` and type operators like `@string()` are\n  silently stripped.\n- **_JSONPath.toPointer(pathAsArray)_** - Accepts a path array and\n  converts to a [JSON Pointer](https://tools.ietf.org/html/rfc6901).\n  The string will be in a form like: `/aProperty/anotherProperty/0`\n  (with any `~` and `/` internal characters escaped as per the JSON\n  Pointer spec). The JSONPath terminal constructions `~` and `^` and\n  type operators like `@string()` are silently stripped.\n\n## Examples\n\nGiven the following JSON, taken from <http://goessner.net/articles/JsonPath/>:\n\n```json\n{\n  \"store\": {\n    \"book\": [\n      {\n        \"category\": \"reference\",\n        \"author\": \"Nigel Rees\",\n        \"title\": \"Sayings of the Century\",\n        \"price\": 8.95\n      },\n      {\n        \"category\": \"fiction\",\n        \"author\": \"Evelyn Waugh\",\n        \"title\": \"Sword of Honour\",\n        \"price\": 12.99\n      },\n      {\n        \"category\": \"fiction\",\n        \"author\": \"Herman Melville\",\n        \"title\": \"Moby Dick\",\n        \"isbn\": \"0-553-21311-3\",\n        \"price\": 8.99\n      },\n      {\n        \"category\": \"fiction\",\n        \"author\": \"J. R. R. Tolkien\",\n        \"title\": \"The Lord of the Rings\",\n        \"isbn\": \"0-395-19395-8\",\n        \"price\": 22.99\n      }\n    ],\n    \"bicycle\": {\n      \"color\": \"red\",\n      \"price\": 19.95\n    }\n  }\n}\n```\n\n### Filter all books cheaper than 10\n\n```javascript\nvar result = JSONPath(\"$..book[?(@.price<10)]\", json);\n\n// [\n//   {\n//     \"category\": \"reference\",\n//     \"author\": \"Nigel Rees\",\n//     \"title\": \"Sayings of the Century\",\n//     \"price\": 8.95\n//   },\n//   {\n//     \"category\": \"fiction\",\n//     \"author\": \"Herman Melville\",\n//     \"title\": \"Moby Dick\",\n//     \"isbn\": \"0-553-21311-3\",\n//     \"price\": 8.99\n//   }\n// ]\n```\n\n### Grab all authors\n\n```javascript\nvar result = JSONPath(\"$..author\", json);\n\n// [\n//   \"Nigel Rees\",\n//   \"Evelyn Waugh\",\n//   \"Herman Melville\",\n//   \"J. R. R. Tolkien\"\n// ]\n```\n\nMore examples can be found [here](https://github.com/JSONPath-Plus/JSONPath/blob/main/README.md#syntax-through-examples)\n\n## Credits\n\nThe code has been taken from [JSONPath-Plus repo](https://github.com/JSONPath-Plus/JSONPath) and slightly adjusted to be able to run on the platform JavaScript engine.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/JSONtoYAML/README.md",
    "content": "Hi Everyone,\n\nThis code is to convert JSON object into Yaml format.\n\nTo use this please pass the JSON object as below to this function as shown below\n\nvar conYaml = global.SCRIPTINCLUDENAME ('JSON OBJECT');\n\nconYaml will hold the converted Yaml.\n\n**Inputs**:\n{ hello: 'world', hello2: [ 'hello', 'world' ] }\n\n**Outputs**:\n---\nhello: world\nhello2:\n- hello\n- world\n"
  },
  {
    "path": "Server-Side Components/Script Includes/JSONtoYAML/code.js",
    "content": "(function (self) {\n  /*\n   * TODO, lots of concatenation (slow in js)\n   */\n  var spacing = \"  \";\n\n  function getType(obj) {\n    var type = typeof obj;\n    if (obj instanceof Array) {\n      return 'array';\n    } else if (type == 'string') {\n      return 'string';\n    } else if (type == 'boolean') {\n      return 'boolean';\n    } else if (type == 'number') {\n      return 'number';\n    } else if (type == 'undefined' || obj === null) {\n      return 'null';\n    } else {\n      return 'hash';\n    }\n  }\n\n  function convert(obj, ret) {\n    var type = getType(obj);\n\n    switch(type) {\n      case 'array':\n        convertArray(obj, ret);\n        break;\n      case 'hash':\n        convertHash(obj, ret);\n        break;\n      case 'string':\n        convertString(obj, ret);\n        break;\n      case 'null':\n        ret.push('null');\n        break;\n      case 'number':\n        ret.push(obj.toString());\n        break;\n      case 'boolean':\n        ret.push(obj ? 'true' : 'false');\n        break;\n    }\n  }\n\n  function convertArray(obj, ret) {\n    if (obj.length === 0) {\n      ret.push('[]');\n    }\n    for (var i=0; i<obj.length; i++) {\n\n      var ele     = obj[i];\n      var recurse = [];\n      convert(ele, recurse);\n\n      for (var j=0; j<recurse.length; j++) {\n        ret.push((j == 0 ? \"- \" : spacing) + recurse[j]);\n      }\n    }\n  }\n\n  function convertHash(obj, ret) {\n    for (var k in obj) {\n      var recurse = [];\n      if (obj.hasOwnProperty(k)) {\n        var ele = obj[k];\n        convert(ele, recurse);\n        var type = getType(ele);\n        if (type == 'string' || type == 'null' || type == 'number' || type == 'boolean') {\n          ret.push(normalizeString(k) + ': ' +  recurse[0]);\n        } else {\n          ret.push(normalizeString(k) + ': ');\n          for (var i=0; i<recurse.length; i++) {\n            ret.push(spacing + recurse[i]);\n          }\n        }\n      }\n    }\n  }\n\n  function normalizeString(str) {\n    if (str.match(/^[\\w]+$/)) {\n      return str;\n    } else {\n      return '\"'+escape(str).replace(/%u/g,'\\\\u').replace(/%U/g,'\\\\U').replace(/%/g,'\\\\x')+'\"';\n    }\n  }\n\n  function convertString(obj, ret) {\n    ret.push(normalizeString(obj));\n  }\n\n  self.json2yaml = function(obj) {\n    if (typeof obj == 'string') {\n      obj = JSON.parse(obj);\n    }\n\n    var ret = [];\n    convert(obj, ret);\n    return ret.join(\"\\n\");\n  };\n})(this);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/KBArticleExpPDF/ArticlePDFHelper.js",
    "content": "var PolicyPDFHelper = Class.create();\nPolicyPDFHelper.prototype = {\n    initialize: function () {\n    },\n\n    getPDFBase64: function (kbSysId, landscape) {\n        var grKB = new GlideRecord('kb_knowledge'),\n            gsattachment = new GlideSysAttachment(),\n            body = '',\n            text = '';\n\n        grKB.get(kbSysId);\n        text = grKB.getValue('text');\n\n        var att = this.generatePDFAttachment(text, kbSysId, 'kb_knowledge', kbSysId, landscape);\n\n        if (att) {\n            var retval = this.getAttNameAndBase64(att, true);\n\t\t\tif (retval) {\n\t\t\t\tif (retval.base64) {\n\t\t\t\t\treturn retval.base64;\n\t\t\t\t}\n\t\t\t}\n        }\n        return false;\n    },\n\n    generatePDFAttachment: function (html, fileName, tableName, recordSysId, landscape) {\n        var body = '';\n        /* \n        PDF exports don't seem to like '/sys_attachment' for images. \n        It works in the web interface, but will not export correctly.\n        Here I remove the forward slashes which is causing the issue. \n        */\n        if (html.indexOf('/sys_attachment.do?sys_id') > -1) {\n            html = html.replace(/\\/sys_attachment/g, 'sys_attachment');\n        }\n\n        body = (landscape) ? '<style>@page {size: A4 landscape;}</style>' + html : html;\n\n        // Generate the PDF and attach it to the KB record. \n        var att = new sn_pdfgeneratorutils.PDFGenerationAPI().convertToPDF(body, tableName, recordSysId, fileName, '');\n        if (att.attachment_id) {\n            return att.attachment_id;\n        }\n        return false;\n    },\n\n    getAttNameAndBase64: function (attSysId, deleteRecord) {\n        var grAttachment = new GlideRecord('sys_attachment'),\n            gsattachment = new GlideSysAttachment();\n        // Get the PDF we just attached. \n        if (grAttachment.get(attSysId)) {\n            // Get the base64 of the content for the download. \n            var attachmentContent = gsattachment.getContentBase64(grAttachment),\n                fileName = grAttachment.getValue('file_name');\n\n            if (attachmentContent) {\n                if (deleteRecord) {\n                    // Now that we have the base64, let's delete the attachment record. \n                    grAttachment.deleteRecord();\n                }\n                return {\n                    'file_name': fileName, 'base64': attachmentContent\n                };\n            }\n        }\n        return false;\n    },\n\n    type: 'PolicyPDFHelper'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/KBArticleExpPDF/README.md",
    "content": "This utility contains a script include which generates PDF export of knowledge article  and this script include handles all HTML formatting of Knowledge article as well.\nAlso, this utility will handle any images attached in KB article body.\n\nSample Script to call this Script Include:\n\nnew PolicyPDFHelper().getPDFBase64('b10db60e2fc738101d84d2172799b69c','landscape');\n\n// First paramter is sys_id of KB article from kb_knowledge record\n// Second Parameter is PDF Export Mode. Accepted inputs are landscape or portrait.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/ListFieldUtil/ListFieldUtil.js",
    "content": "var ListFieldUtil = function(listFieldVal) {\n    if (listFieldVal == null || listFieldVal == undefined) {\n        listFieldVal = \"\";\n    }\n    var _getIdx = function(id) {\n        return listFieldVal.split(\",\").indexOf(id);\n    };\n    var exists = function(id) {\n        return _getIdx(id) >= 0;\n    };\n    var remove = function(id) {\n        return listFieldVal.split(\",\").filter(function(fid) {\n            return fid != id;\n        }).join(\",\");\n    };\n    var add = function(id) {\n        if (!exists(id)) {\n            var listFieldArr = listFieldVal.split(\",\");\n            listFieldArr.push(id);\n            return listFieldArr.join(\",\");\n        }\n        return listFieldVal;\n    };\n    return {\n        \"exists\": exists,\n        \"remove\": remove,\n        \"add\": add\n    };\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/ListFieldUtil/README.md",
    "content": "# ListFieldUtil\nScript Include that helps with handling list fields, like for example \"Watch List\" on the task table.\n\nIt doesn't use the typical `Class.create`, instead it is a simple javascript function.\nCheck out this blog post for more info about the \"Function Pattern\": https://codecreative.io/blog/interface-design-patterns-function-pattern/\n\n## Example Script\n```javascript\nvar watchListVal = grMyIncident.getValue(\"watch_list\");\n//add current user to watch list\nvar newWatchListVal = ListFieldUtil(watchListVal).add(gs.getUserID());\ngrMyIncident.setValue(\"watch_list\", newWatchListVal);\n\n//remove current user from watch list\nvar newWatchListVal = ListFieldUtil(watchListVal).remove(gs.getUserID());\ngrMyIncident.setValue(\"watch_list\", newWatchListVal);\n\n//check if current user exists in watch list\nvar currentUserInWatchList = ListFieldUtil(watchListVal).exists(gs.getUserID());\ngs.debug(\"Current user is in watch list: \" + currentUserInWatchList);\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Log Utils/Code.js",
    "content": "var log_utils = Class.create();\nlog_utils.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    initialize: function(debug, noDebugMethods) {\n        this.debug = false; // Flag to allow debug or not on this script include\n        this.noDebugMethods = []; // Array of methods to not log from\n\n        if (debug) {\n            this.debug = debug;\n        }\n\n        if (noDebugMethods) {\n            this.noDebugMethods = noDebugMethods.split(',');\n        }\n\n        // Global Variables For Use In Script\n\n    },\n\n /**\n  * Description: Takes in a method name and message and logs the message in gs.info if debug and method isn't in noDebugMethods\n  * Parameters: [string] - methodName: name of method calling log.\n                [string] - msg: message being called in log.\n  * Returns: None.\n  */\n  \n  log: function(methodName, msg) {\n      if (this.debug && this.noDebugMethods.indexOf(methodName) === -1) {\n          gs.debug('[Log Utils - ' + methodName + '] ' + msg);\n      }\n  },\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Log Utils/README.md",
    "content": "This is a lightweight utility for managing logging within Script Includes.\nIt provides a simple, configurable way to enable or disable debug logs during development and to exclude specific methods from logging when necessary.\n\nThis approach helps maintain clean logs, reduces noise during debugging, and allows for quick toggling of logging behavior once development is complete.\n\nUsage:\nYou can call the log method anywhere inside your Script Include to record a log message:\nthis.log(<method_name>, <message>);\n\nExample:\nthis.log('processData', 'Starting data processing...');\n\nInitialization Parameters:\nLogging behavior is controlled through the parameters passed to the initialize method when creating the Script Include instance.\n\nvar utils = new scope.utils(true, 'methodName1,methodName2');\n\nParameters:\n======================================================================================================================\t\n|debug           |\tBoolean\tfalse |\tEnables or disables logging. Set to true to allow logs, false to suppress them.  |\n|noDebugMethods  |\tString\t\"\"\t  | A comma-separated list of method names that should not produce logs.             |\n======================================================================================================================\n\nExample Use Case:\nIf you’re developing a new method that depends on existing, stable methods, you can temporarily disable logging for the known-good methods.\nFor example:\n\nvar utils = new scope.utils(true, 'validatedMethod');\n\nThis enables logs for all methods except validatedMethod, keeping your system logs focused on what you’re currently debugging.\n\nWhen development is complete, you can remove the parameters (or set debug to false) to disable all logs.\n\nNotes:\nThe script uses gs.debug() for logging to keep logs easily filterable within the system logs.\nYou can switch to gs.info(), gs.warn(), or gs.error() depending on your needs.\n\nLogging should typically remain disabled (debug = false) in production to avoid unnecessary system overhead.\n\nThis utility is flexible and can be dropped into almost any Script Include with minimal setup.\n\nExample in Context:\n\nvar example_utils = Class.create();\nexample_utils.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n    initialize: function(debug, noDebugMethods) {\n        this.logger = new log_utils(debug, noDebugMethods);\n    },\n\n    processData: function() {\n        this.logger.log('processData', 'Starting process...');\n        // ...your logic here...\n        this.logger.log('processData', 'Process completed successfully.');\n    }\n});\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Logger/Logger.js",
    "content": "var Logger = Class.create();\nLogger.prototype = {\n    initialize: function(source) {\n       \n            this.logging_enabled = gs.getProperty(source + \" update Logging\"); // Property to verify whether logging enabled for the source for example LogicMonitor\n            this.payload_logging_enabled = gs.getProperty(source + \" API Requests Logging\"); // Property to verify whether Payload logging enabled for this source for example LogicMonitor\n            this.source = source;\n        }\n    \n    },\n    infoLog: function(description) {\n        if (this.logging_enabled == \"true\") {\n            gs.log(description, this.source);\n        }\n    },\n    warnLog: function(description) {\n        if (this.logging_enabled == \"true\") {\n            gs.logWarning(description, this.source);\n        }\n    },\n    errorLog: function(description) {\n        if (this.logging_enabled == \"true\") {\n            gs.logError(description, this.source);\n        }\n    },\n    logPayload: function(description) {\n        if (this.payload_logging_enabled == \"true\") {\n            gs.log(description, this.source);\n        }\n    },\n    type: 'Logger'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Logger/README.md",
    "content": "This is the logger module which can be used to log Payloads easily in a more efficient way. This module can be controlled from properties.\n\nHere's an explanation of the key components and methods of this `Logger` Script Include:\n\n1. **Constructor (`initialize` method)**:\n   - The constructor function `initialize` is used to create instances of the `Logger` class.\n   - It takes one parameter called `source`, which is intended to be a string representing the source or context for the logs.\n   - Inside the constructor, it initializes instance variables:\n     - `this.logging_enabled`: This variable is set by fetching a ServiceNow property based on the provided `source`. It's used to determine whether logging is enabled for this specific source.\n     - `this.payload_logging_enabled`: Similar to `this.logging_enabled`, this variable is set to determine whether payload (API requests) logging is enabled for the source.\n     - `this.source`: This variable is set to the provided `source` parameter, representing the context for the logs.\n     - In order to manage the Logger through properties you should create two properties for \"Text Logger\" & another one is for \"Payload Logger\" with type \"TRUE/FALSE\". So that those properties can be utilised to manage enable or disable to logs in all the scripts for better performance. \n\n2. **Logging Methods**:\n   - The `Logger` class defines several methods for different types of logging:\n     - `infoLog(description)`: Logs an informational message with the provided `description` if logging is enabled.\n     - `warnLog(description)`: Logs a warning message with the provided `description` if logging is enabled.\n     - `errorLog(description)`: Logs an error message with the provided `description` if logging is enabled.\n     - `logPayload(description)`: Logs payload information with the provided `description` if payload logging is specifically enabled for this source.\n\n3. **Conditional Logging**:\n   - Before logging any message (info, warning, error, or payload), each method checks if logging or payload logging is enabled for the current source by comparing the `this.logging_enabled` or `this.payload_logging_enabled` variable to the string `\"true\"`.\n   - If logging is enabled, it uses the `gs.log()`, `gs.logWarning()`, or `gs.logError()` functions to log the message with the source context.\n\n4. **Class Type**:\n   - The class has a `type` property set to `'Logger'`, which could be used for type checking or identifying objects of this class.\n\nTo use this `Logger` class, you would typically do the following:\n\n1. Instantiate a `Logger` object with a specific `source` when you need to log information, warnings, errors, or payload details in your ServiceNow script.\n\n2. Use the appropriate logging method (e.g., `infoLog`, `warnLog`, `errorLog`, or `logPayload`) to log messages as needed within your script. The class takes care of checking whether logging is enabled for that source/context.\n\nHere's an example of how you might use it in a ServiceNow script:\n\n```javascript\n// Instantiate a Logger for a specific source (e.g., \"LogicMonitor\")\nvar myLogger = new Logger(\"LogicMonitor\");\n\n// Log information\nmyLogger.infoLog(\"This is an informational message\");\n\n// Log a warning\nmyLogger.warnLog(\"This is a warning message\");\n\n// Log an error\nmyLogger.errorLog(\"This is an error message\");\n\n// Log payload (if enabled)\nmyLogger.logPayload(\"API request payload: {...}\");\n```\n\nThis code ensures that log messages are only recorded if logging or payload logging is explicitly enabled for the specified source.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/ManagerRecursiveUtil/README.md",
    "content": "Many teams have a  use case to build reports for executives that shows data related all groups underneath them, such as problem tickets assigned to their reports. \n\nThe script include return a list of group sys_ids that roll up to the given leader. \n\nThis is  client callable script includes and there is a limit the maximum amount of reporting depth, such as 7 users for performance reaasons.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/ManagerRecursiveUtil/RecursiveByManager.js",
    "content": "var RecursiveByManager = Class.create();\nRecursiveByManager.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    initialize: function () { },\n    maxDepth: 7,\n\n    getUserSysIds: function (managersName, depth, includeInactive, managerSysId) {\n        var managersId;\n        if (managersName) {\n            managersId = this._getUserSysId(managersName);\n        }\n        else if (managerSysId) {\n            managersId = managerSysId;\n        }\n\n        if (managersId) {\n            // No more than max depth of 7. \n            if (depth) {\n                if (depth > this.maxDepth) {\n                    depth = this.maxDepth;\n                }\n            }\n            else {\n                depth = this.maxDepth;\n            }\n\n            // Add manager's sys_id to array to get their groups too. \n            var arr = [managersId];\n            return arr.concat(this._getSubordinate(managersId, depth, includeInactive));\n        }\n        else { // No user found.\n            return 'Manager\\'s name not found.';\n        }\n    },\n\n    getGroupSysIds: function (managersName, depth, managerSysId) {\n        var arr;\n        if (managersName) {\n            arr = this.getUserSysIds(managersName, depth, true);\n        }\n        else if (managerSysId) {\n            arr = this.getUserSysIds(null, depth, true, managerSysId);\n        }\n        else { // No user found.\n            return 'Manager not found.';\n        }\n\n        if (arr) {\n            return this._getGroups(arr);\n        }\n        else { // No user found.\n            return 'Manager not found.';\n        }\n    },\n\n    _getUserSysId: function (managersName) {\n        // Get manager's sys_id\n        var grUser = new GlideRecord('sys_user');\n        grUser.addQuery('name', managersName);\n        grUser.setLimit(1);\n        grUser.query();\n\n        if (grUser.next()) {\n            return grUser.getValue('sys_id');\n        }\n        return false;\n    },\n\n    _getSubordinate: function (managerSysId, depth, includeInactive) {\n        // Keep digging. \n        if (depth > 0) {\n            var grUser = new GlideRecord('sys_user'),\n                usersSysIds = [],\n                newArray = [];\n\n            depth--;\n\n            grUser.addEncodedQuery('web_service_access_only=false^manager=' + managerSysId);\n            grUser.query();\n            while (grUser.next()) {\n                // Manager shouldn't be what was passed in. e.g use case where CEO is his,her own manager.\n                if (grUser.getValue('sys_id') != managerSysId) {\n                    // Do we want just active users? \n                    if (!includeInactive) {\n                        if (grUser.active) {\n                            usersSysIds.push(grUser.getValue('sys_id'));\n                        }\n                    }\n                    else {\n                        usersSysIds.push(grUser.getValue('sys_id'));\n                    }\n                    newArray = this._getSubordinate(grUser.getValue('sys_id'), depth);\n                    if (newArray) {\n                        usersSysIds = usersSysIds.concat(newArray);\n                    }\n                }\n            }\n\n            return usersSysIds;\n        }\n        else { // No more digging. \n            return false;\n        }\n    },\n\n    _getGroups: function (managerArray) {\n        var grGroup = new GlideRecord('sys_user_group'),\n            groupSysIds = [];\n\n        grGroup.addEncodedQuery('active=true^managerIN' + managerArray.join(','));\n        grGroup.query();\n        while (grGroup.next()) {\n            groupSysIds.push(grGroup.getValue('sys_id'));\n        }\n\n        return groupSysIds;\n    },\n\n    type: 'RecursiveByManager'\n});\n\n// Added so this could be called without having to instantiate the class. \nRecursiveByManager.getGroupSysIds = function (managerSysId, depth) {\n    var util = new RecursiveByManager();\n    return util.getGroupSysIds(managerSysId, depth);\n};\n\nRecursiveByManager.getUserSysIds = function (managerSysId, depth) {\n    var util = new RecursiveByManager();\n    return util.getUserSysIds(managerSysId, depth);\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Match URL with a String/MatchURLByRegex.js",
    "content": "var MatchURLByRegex = Class.create();\nMatchURLByRegex.prototype = {\n    initialize: function() {},\n\t\n    matchReferer: function() {\n        return ((/YOUR_URL_MATCH_STRING/.test(this.getReferer())));\n    },\n    \n\tgetReferer: function() {\n        return GlideTransaction.get().getRequest().getHeader(\"referer\");\n    },\n    \n\ttype: 'MatchURLByRegex'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Match URL with a String/README.md",
    "content": "# Match URL with a String using Regex\n\nThis script gets the url using the referer method and matches the substring in the url."
  },
  {
    "path": "Server-Side Components/Script Includes/Next Business Window Calculator/README.md",
    "content": "# Next Business Window Calculator\n\n## Problem\nYou often need to compute the *next business window* for task scheduling, SLAs, or batch processing: “Start at 16:45, add 95 working minutes using schedule X and holiday set Y, and return both the end time and the ‘windows’ you traversed.” Native APIs give you the building blocks, but teams frequently re-invent this logic.\n\n## Where to use it\n- Script Include utility callable from Business Rules, Flow/Schedule jobs, or Background Scripts\n- Works in *scoped* apps\n\n## What it does\n- Accepts: start `GlideDateTime`, working minutes (integer), a schedule sys_id (or name), and an optional timezone\n- Uses `GlideSchedule` to hop across working/non-working periods and holidays\n- Returns:\n  - `endGdt` — the calculated ending `GlideDateTime`\n  - `segments` — an ordered list of working sub-segments used (start/end per segment), useful for audit/debugging\n  - `consumedMinutes` — total minutes consumed\n\n## Configuration\nAt the top of the script you can configure:\n- Default schedule sys_id (fallback)\n- Default timezone (e.g., `Europe/London`)\n- Maximum safety iterations\n\n## How it works\nThe utility constructs a `GlideSchedule` from the provided schedule id, aligns the starting point, then iteratively consumes the requested working minutes across schedule segments (respecting holidays and timezone). It avoids recursion and uses a safety counter to prevent infinite loops.\n\n## References\n- GlideSchedule (Scoped) API. ServiceNow Docs.  \n  https://www.servicenow.com/docs/ (GlideSchedule Scoped)  \n- Server API overview (Zurich docs bundle). :contentReference[oaicite:1]{index=1}\n\n## Example\n```js\nvar util = new x_snc_example.NextBusinessWindow();\nvar result = util.addWorkingMinutes('2025-10-21 16:45:00', 95, 'your_schedule_sys_id', 'Europe/London');\n// result.endGdt.getDisplayValue() -> \"2025-10-22 09:20:00\"\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Next Business Window Calculator/next_business_window.js",
    "content": "var NextBusinessWindow = Class.create();\nNextBusinessWindow.prototype = (function () {\n\t// Configuration\n\tvar DEFAULT_SCHEDULE_SYS_ID = ''; // optional fallback schedule sys_id (cmn_schedule)\n\tvar DEFAULT_TZ = 'Europe/London';\n\tvar MAX_SEGMENTS = 1000; // safety guard\n\n\t// Helpers\n\tfunction _toGdt(input) {\n\t\tif (input instanceof GlideDateTime) return input;\n\t\tvar g = new GlideDateTime();\n\t\tif (input) g.setDisplayValue(input);\n\t\treturn g;\n\t}\n\tfunction _getSchedule(scheduleSysId) {\n\t\tvar sch = new GlideSchedule();\n\t\tif (scheduleSysId) sch.load(scheduleSysId);\n\t\telse if (DEFAULT_SCHEDULE_SYS_ID) sch.load(DEFAULT_SCHEDULE_SYS_ID);\n\t\telse throw new Error('No schedule sys_id supplied and no default configured.');\n\t\treturn sch;\n\t}\n\tfunction _setTz(gdt, tz) { if (tz) gdt.setTZ(tz); return gdt; }\n\n\treturn {\n\t\tinitialize: function () {},\n\n\t\t/**\n\t\t * Add working minutes across a GlideSchedule.\n\t\t * @param {String|GlideDateTime} start\n\t\t * @param {Number} minutes\n\t\t * @param {String} scheduleSysId\n\t\t * @param {String} [timeZone] IANA name, e.g. \"Europe/London\"\n\t\t * @returns {{endGdt: GlideDateTime, consumedMinutes: number, segments: Array}}\n\t\t */\n\t\taddWorkingMinutes: function (start, minutes, scheduleSysId, timeZone) {\n\t\t\tif (!minutes || minutes < 0) throw new Error('Minutes must be a positive integer.');\n\t\t\tvar tz = timeZone || DEFAULT_TZ;\n\t\t\tvar sch = _getSchedule(scheduleSysId);\n\n\t\t\tvar cursor = _toGdt(start);\n\t\t\t_setTz(cursor, tz);\n\n\t\t\t// If not in schedule, jump to the next working start\n\t\t\tif (!sch.isInSchedule(cursor)) {\n\t\t\t\tvar nextStart = sch.getNextStartTime(cursor);\n\t\t\t\tif (!nextStart) throw new Error('No next working period found from start time.');\n\t\t\t\tcursor = nextStart;\n\t\t\t}\n\n\t\t\tvar remaining = parseInt(minutes, 10);\n\t\t\tvar segments = [];\n\t\t\tvar guard = 0;\n\n\t\t\twhile (remaining > 0) {\n\t\t\t\tif (guard++ > MAX_SEGMENTS) throw new Error('Exceeded max segments; check schedule/inputs.');\n\n\t\t\t\tvar segEnd = sch.getNextEndTime(cursor);\n\t\t\t\tif (!segEnd) throw new Error('Schedule has no next end time; check configuration.');\n\n\t\t\t\tvar available = Math.ceil((segEnd.getNumericValue() - cursor.getNumericValue()) / (60 * 1000)); // minutes\n\n\t\t\t\tif (remaining <= available) {\n\t\t\t\t\tvar end = new GlideDateTime(cursor);\n\t\t\t\t\tend.addSeconds(remaining * 60);\n\t\t\t\t\tsegments.push({ segmentStart: new GlideDateTime(cursor), segmentEnd: new GlideDateTime(end), consumed: remaining });\n\t\t\t\t\treturn { endGdt: end, consumedMinutes: minutes, segments: segments };\n\t\t\t\t}\n\n\t\t\t\t// consume full segment\n\t\t\t\tsegments.push({ segmentStart: new GlideDateTime(cursor), segmentEnd: new GlideDateTime(segEnd), consumed: available });\n\t\t\t\tremaining -= available;\n\n\t\t\t\tvar nextStart = sch.getNextStartTime(segEnd);\n\t\t\t\tif (!nextStart) throw new Error('No subsequent working segment found.');\n\t\t\t\tcursor = nextStart;\n\t\t\t}\n\n\t\t\treturn { endGdt: new GlideDateTime(cursor), consumedMinutes: minutes, segments: segments };\n\t\t}\n\t};\n})();\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Non Prod Instance Password Reset/README.md",
    "content": "This script include can reset the password of the non prod instances using Rest integration. Before implementation you should have a admin user credentials for the integration. Follow follwoing steps to implement this script.\n\nStep 1: Create Basic Auth Profile\n\nStep 2: Go To the sys_auth_profile_basic.LIST\n\nStep 3: Enter the Profile Name. For example: I have given instance name as a basic auth profile name \"dev73660\"\n\nStep 4: Enter the admin user id and password of the non prod instance.\n\nStep 5: Call this script include using below code.\n\n```var instanceName = 'dev73660';  //instance name of the non prod instance```\n\n```var userid = '62826bf03710200044e0bfc8bcbe5df1'; //Sysid of the target user for password reset```\n\n```var password = 'Welcome@12345'; // Dummy password ```\n\n```var authProfileName = 'dev73660'; //Basic authentication profile name setup at step 3```\n\n```var passwordReset = new passwordReset();```\n\n```gs.print(passwordReset.nonProdPasswordReset(instanceName, userid, password,authProfileName));```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Non Prod Instance Password Reset/passwordReset.js",
    "content": "var passwordReset = Class.create();\npasswordReset.prototype = {\n    initialize: function() {},\n    nonProdPasswordReset: function(instanceName, userid, password,authProfileName) {\n\t\tvar access='';\n\t\tvar endpoint = \"https://\"+instanceName+'.service-now.com/api/now/table/sys_user/'+userid+'?sysparm_input_display_value=true';\n\t\tvar request2='';\n        var auth = new GlideRecord('sys_auth_profile_basic');\n        auth.addEncodedQuery('name='+authProfileName);\n        auth.query();\n        if (auth.next()) {\n            access = auth.getValue('sys_id');\n        }      \n        request2 = new sn_ws.RESTMessageV2();\n        request2.setEndpoint(endpoint);\n        request2.setHttpMethod('PATCH');\n        request2.setAuthenticationProfile(\"basic\", access);\n        request2.setRequestHeader(\"Accept\", \"application/json\");\n        request2.setRequestHeader('Content-Type', 'application/json');\n        var currenttime = new GlideDateTime();\n        var pw = password;\n\n        var js = {};\n        js.locked_out = 'false';\n        js.password_needs_reset = 'true';\n        js.user_password = pw.toString();\n\n        request2.setRequestBody(JSON.stringify(js));\n        var response2 = request2.execute();\n        var httpResponseStatus = response2.getStatusCode();\n        return(response2.getBody());\n    },\n    type: 'passwordReset'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/NonProdLogUtils/LogUtilsNonProd.js",
    "content": "var LoggingUtils = Class.create();\nLoggingUtils.prototype = {\n    initialize: function() {\n    },\n\n    /**\n     * @param {string} message The message to log.\n     * @param {string} [type='info'] The type of log. Options: 'info', 'warn', 'error'.\n     * @returns {void}\n     *\n     * This utility function logs a message only if the current instance is non-production.\n     * Use it in other server-side scripts like this:\n     * var logUtil = new LoggingUtils();\n     * logUtil.log('This message will only show in dev and test environments.');\n     */\n    log: function(message, type) {\n        // Get the current instance name from a system property\n        var instanceName = gs.getProperty('instance_name');\n        \n        // You must define your production instance name.\n        // For example, if your production instance is 'mycompanyprod'.\n        var productionInstanceName = 'mycompanyprod';\n\n        // Check if the current instance name is NOT the production instance name\n        if (instanceName && instanceName != productionInstanceName) {\n            type = type || 'info';\n\n            // Determine the correct logging function based on the specified type\n            switch (type) {\n                case 'warn':\n                    gs.warn(message);\n                    break;\n                case 'error':\n                    gs.error(message);\n                    break;\n                default:\n                    gs.log(message);\n                    break;\n            }\n        }\n    },\n\n    type: 'LoggingUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/NonProdLogUtils/README.md",
    "content": "To create a logging utility in a ServiceNow Script Include that only logs in non-production environments, you can follow these steps:\n\n\nCreate a new Script Include.\nDefine a utility class with a logging function.\nInside the function, get the instance name and use conditional logic to check if it matches the production instance name.\nIf the instance is non-production, execute the logging command.\nCall this utility from other server-side scripts.\n\n\nThis method centralizes your logging logic, making it easy to manage.\nStep 1: Create the Script Include\nIn the main navigation, type Script Includes and select it under System Definition.\nClick New to create a new record.\nFill out the form with the following details:\nName: LoggingUtils\nAccessible from: All application scopes (This allows you to call the function from anywhere).\nClient callable: Unchecked (This is a server-side utility).\n\n\n\n\n// Instantiate the Script Include\nvar logUtil = new LoggingUtils();\n\n// Use the log() function to log a message\nlogUtil.log('Hackertoberfest', 'info');\n"
  },
  {
    "path": "Server-Side Components/Script Includes/NotificationUtil/NotificationUtil.js",
    "content": "var notificationUtil = Class.create();\nnotificationUtil.prototype = {\n    initialize: function () { },\n\n    //Display Multi-Row Variable Set in a table\n    formatMRVS: function (current) {\n\n        var headers = getMrvsHeaders(current);\n        var values = getMrvsValues(current, headers.length);\n        var result = \"\";\n        result += \"<head><style>table, th, td {border: 1px solid black; text-align:left;}</style></head>\";\n        result += '<body><table style=\"border-collapse: collapse; width:75%\"><tbody>';\n        result += headers.html; // Print headers\n        result += values; // Print values\n        result += \"</tbody></table></body>\";\n        return result;\n    },\n\n    type: 'notificationUtil'\n};\n\nfunction getMrvsHeaders(gr) {\n    var multiVar = new GlideRecord('sc_multi_row_question_answer');\n    multiVar.addQuery('parent_id', gr.sys_id.toString());\n    multiVar.addQuery('variable_set', '!=', '');\n    multiVar.orderBy('row_index');\n    multiVar.orderBy('sc_item_option.order');\n    multiVar.query();\n    var headers = {\n        length: 0,\n        html: \"\",\n        arr: []\n    };\n    while (multiVar.next()) {\n        if (headers.arr.indexOf(multiVar.item_option_new.getDisplayValue()) === -1) {\n            headers.arr.push(multiVar.item_option_new.getDisplayValue());\n            headers.html = headers.html + \"<th>\" + multiVar.item_option_new.getDisplayValue() + \"</th>\";\n            headers.length++;\n        }\n    }\n    headers.html = \"<tr>\" + headers.html + \"</tr>\";\n    return headers;\n}\n\nfunction getMrvsValues(gr, headerLength) {\n    var multiVar = new GlideRecord('sc_multi_row_question_answer');\n    multiVar.addQuery('parent_id', gr.sys_id.toString());\n    multiVar.addQuery('variable_set', '!=', '');\n    multiVar.orderBy('row_index');\n    multiVar.orderBy('sc_item_option.order');\n    multiVar.query();\n    var values = [];\n    while (multiVar.next()) {\n        values.push(getDisplayValue(multiVar.value.toString(), multiVar.item_option_new));\n    }\n    var result = \"\";\n    for (var i = 0; i < values.length; i++) {\n        result += i % headerLength == 0 ? \"<tr><td> \" + values[i] + \" </td>\" : \"<td> \" + values[i] + \" </td></tr>\";\n    }\n    return result;\n}\n\nfunction getDisplayValue(rawValue, question) {\n    var varType = question.type.toString(), varRefTable = question.reference.toString();\n    if (varType == 8) { // Type == Reference\n\n        var gr = new GlideRecord(varRefTable);\n        gr.get(rawValue);\n\n        return gr.getDisplayValue();\n    }\n\n    else if (varType == 3 || varType == 5) { // Type == Multiple Choice or Select Box\n\n        var variableName = question.name.toString();\n        var questionID = question.sys_id.toString();\n\n        var qc = new GlideRecord(\"question_choice\");\n        qc.addQuery(\"question\", questionID);\n        qc.addQuery(\"value\", rawValue);\n        qc.query();\n\n        if (qc.next()) {\n            return qc.text;\n        }\n    } else {\n        return rawValue;\n    }\n}"
  },
  {
    "path": "Server-Side Components/Script Includes/NotificationUtil/README.md",
    "content": "# NotificationUtil\n\nQuickly format all MRVS variables into a table of your current record into a notification mail scripts by using the NotificationUtil script include.\n\n## Usage\n\nMail script\n```javascript\n(function runMailScript(/* GlideRecord */ current, /* TemplatePrinter */ template,\n          /* Optional EmailOutbound */ email, /* Optional GlideRecord */ email_action,\n          /* Optional GlideRecord */ event) {\n              template.print(new notificationUtil().formatMRVS(current))\n})(current, template, email, email_action, event);\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Number Padding/README.md",
    "content": "Lets you pad your single digit number for better formating\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Number Padding/numberPadding.js",
    "content": "module.exports.numberPadding = function (value) {\n  return String(value).padStart(2, '0');\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/OAuth token helper/OAuthTokenHelper.js",
    "content": "\nclass OAuthTokenHelper {\n\n    /**\n     * Returns the access token and refresh token for the given OAuth profile id and credentials\n     * @param {String} oauth_profile_id - The sys_id of the OAuth profile\n     * @param {String} oAuth_Application_Registry_Name - The name of the OAuth application registry record (oauth_entity)\n     * @param {String} requestor_context - Context of the request e.g. Sales Order \n     * @param {String} requestor_id - user ID of the sys_user who requests\n     * @param {String} username - the username to use for the password grant\n     * @param {String} password - the password to use for the password grant\n     * @return {Object} an object with accessToken and refreshToken properties\n     */\n    getRefreshAndAccessTokens(oauth_profile_id, requestor_context, requestor_id, username, password) {\n        if (!oauth_profile_id) {\n            throw gs.getMessage(\"oauth_profile_id is a required parameter\");\n        }\n        if (!requestor_context) {\n            throw gs.getMessage(\"requestor_context is a required parameter\");\n        }\n        if (!requestor_id) {\n            throw gs.getMessage(\"requestor_id is a required parameter\");\n        }\n        if (!username) {\n            throw gs.getMessage(\"username is a required parameter\");\n        }\n        if (!password) {\n            throw gs.getMessage(\"password is a required parameter\");\n        }\n\n        let oAuthClient = new sn_auth.GlideOAuthClient();\n        let params = {\n            grant_type: \"password\",\n            username: username,\n            password: password,\n            oauth_requestor_context: requestor_context,\n            oauth_requestor: requestor_id,\n            oauth_provider_profile: oauth_profile_id\n        };\n\n        let json = new global.JSON();\n        let text = json.encode(params);\n        let tokenResponse = oAuthClient.requestToken(oAuth_Application_Registry_Name, text); // is the name of the OAuth application registry record (oauth_entity)\n        let token = tokenResponse.getToken();\n        let accessToken = token.getAccessToken();\n        let refreshToken = token.getRefreshToken();\n\n        return {\n            accessToken: accessToken,\n            refreshToken: refreshToken\n        };\n    }\n\n\n    /**\n     * Returns the access token based on the refresh token\n     * @param {String} oauth_profile_id - The sys_id of the OAuth profile (oauth_entity_profile)\n     * @param {String} oAuth_Application_Registry_Name - The name of the OAuth application registry record (oauth_entity)\n     * @param {String} requestor_context - Context of the request e.g. Sales Order\n     * @param {String} requestor_id - user ID of the sys_user who requests\n     * @param {String} refreshToken - the refresh token to use for the refresh token grant\n     * @return {Object} an object with accessToken and refreshToken properties\n     */\n    getAccessToken(oauth_profile_id, oAuth_Application_Registry_Name, requestor_context, requestor_id, refreshToken) {\n        if (!oauth_profile_id) {\n            throw \"oauth_profile_id is a required parameter\";\n        }\n        if (!requestor_context) {\n            throw \"requestor_context is a required parameter\";\n        }\n        if (!requestor_id) {\n            throw \"requestor_id is a required parameter\";\n        }\n        if (!refreshToken) {\n            throw \"refreshToken is a required parameter\";\n        }\n\n        let oAuthClient = new sn_auth.GlideOAuthClient();\n        let params = {\n            grant_type: \"refresh_token\",\n            refresh_token: refreshToken,\n            oauth_requestor_context: requestor_context,\n            oauth_requestor: requestor_id,\n            oauth_provider_profile: oauth_profile_id\n        };\n\n        let json = new global.JSON();\n        let text = json.encode(params);\n        let tokenResponse = oAuthClient.requestToken(oAuth_Application_Registry_Name, text); // the name of the OAuth application registry record (oauth_entity)\n        let token = tokenResponse.getToken();\n        let access_Token = token.getAccessToken();\n        let refresh_Token = token.getRefreshToken();\n\n        return {\n            accessToken: access_Token,\n            refreshToken: refresh_Token\n        };\n    }\n\n\n}\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/OAuth token helper/README.md",
    "content": "# Helps to get Refresh Token based on username and password or get the Access Token based on the Refresh Token\n# To be noted that this is using the new ES2021 feature. So if your instance is upgraded to Xanadu or you are using a Scoped App that already enabled the ES2021 then this Script Include can be used.\n\n# Example\n\n```\nvar helper = new OAuthTokenHelper();\n\nvar result = helper.getRefreshAndAccessTokens(\"oauth_profile_id\", \"context e.g. Sales\", \"user@service-now.com\", \"username\", \"password\") \n\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/OrderedRecords/README.md",
    "content": "# ArtifactRank\nScript Include that helps with getting the next spaced out ordering \n\n## Example Script\n```javascript\nvar nextRank = new ArtifactRank.getNextRank();\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/OrderedRecords/orderedRecords.js",
    "content": "var ArtifactRank = Class.create();\nArtifactRank.prototype = {\n    initialize: function() {\n    },\n\t\n\tgetNextRank: function(field) {\n\t\tvar gr = new GlideRecord('TABLENAME'); \n\t\tgr.addQuery('state', '!=', 'published'); \n\t\tgr.addNotNullQuery(field); \n\t\tgr.orderByDesc(field); \n\t\tgr.setLimit(1);\n\t\tgr.query(); \n\t\t\n\t\tif (gr.next()) {\n\t\t\t// Round up to nearest 10\n\t\t\tvar nextRank = parseInt(gr.getValue(field)) + 10; \n\t\t\treturn Math.round(nextRank / 10) * 10; \n\t\t} else {\n\t\t\treturn 10; \n\t\t}\n\t},\n\n    type: 'ArtifactRank'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/PII Redactor/README.md",
    "content": "There are many implementations where there is a need to redact PII data from servicenow tables as par of audit requirements.\ne.g Instances may have catalog item for the newly onboarded users to order hardware equirement.\nThis catalog item will contain variable to store shipping information of users which is PII data.\nAudit will not want other users to see this PII data.\n\nI have created a script include which redacts PII data from variables, audit log, audit history, emails based on different paramters.\n\nExample Usage:\n\nBelow sample code redacts PII data from requested item variables which contain PII Data.\n\n```ruby\n var sc = new GlideRecord('sc_item_option_mtom');\n    sc.addQuery('request_item', '<SYSID_OF_RITM>');\n    sc.query();\n    while (sc.next()) {\n\n        var r = new piiRedaction().redactPii('sc_req_item','<SYSID_OF_RITM>',sc.sc_item_option.value);\n\n    }\n\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/PII Redactor/piiRedaction.js",
    "content": "var piiRedaction = Class.create();\npiiRedaction.prototype = {\n    initialize: function() {},\n\n    redactPii: function(tableName, record, piiData) {\n\n        //store the parameters as variables\n\n        if (tableName != undefined) {\n            var table = tableName;\n        } else {\n            return gs.log('tableName required', 'piiRedaction');\n        }\n\n        if (record != undefined) {\n            var record = record;\n        } else {\n            return gs.log('sysId required', 'piiRedaction');\n        }\n\n        if (piiData != undefined) {\n            var piiData = piiData;\n        } else {\n            return gs.log('piiData required', 'piiRedaction');\n        }\n\n\n        //variables for use in queries locating the records that need to be deleted\n\n        var grAudit;\n        var grJournal;\n        var grEmail;\n        var grHistory;\n        var grVariables;\n        var recordNumber;\n\n        //get the record\n\n        var gr = new GlideRecord(table);\n        gr.get(record);\n\n        //if the record has a number store it for later use\n\n        if (gr.getValue('number')) {\n            recordNumber = gr.getValue('number');\n        }\n\n        //locate the Journal records where the element_id matches the sys_id of the record being redacted and the value contains the piiData being redacted\n\n        grJournal = new GlideRecord('sys_journal_field');\n        grJournal.addQuery('element_id', record);\n        grJournal.addQuery('value', 'CONTAINS', piiData);\n        grJournal.query();\n\n        grJournal.deleteMultiple();\n\n\n        //If removing piiData from a specific field, not a variable, locate the Audit records.  Query for any records where the document key is the sys_id of the record being redacted and the piiData shows in either the new or old value\n\n        grAudit = new GlideRecord('sys_audit');\n        grAudit.addEncodedQuery('documentkey=' + record + '^oldvalueLIKE' + piiData + '^ORnewvalueLIKE' + piiData);\n        grAudit.query();\n\n        grAudit.deleteMultiple();\n\n\n        //Locate the History Records.  All history records for the record need to be removed so they can be regenerated when the history set is loaded again\n\n        grHistory = new GlideRecord('sys_history_set');\n        grHistory.addQuery('id', record);\n        grHistory.query();\n\n        grHistory.deleteMultiple();\n\n        //if deleting piiData from a variable look up all records on the SC Item Option Mtom table where the request_item.number is the number of the RITM being redacted and the variable name matches the variables name passed into the function\n\n        if (recordNumber != undefined) {\n            grVariables = new GlideRecord('sc_item_option_mtom');\n            grVariables.addEncodedQuery('request_item.numberSTARTSWITH' + recordNumber + '^sc_item_option.value=' + piiData);\n            grVariables.query();\n\n            grVariables.deleteMultiple();\n        }\n\n        var grActivity = new GlideRecord('sys_activity');\n        grActivity.addQuery('document_id', record);\n        grActivity.addQuery('payload', 'CONTAINS', piiData);\n        grActivity.query();\n\n        grActivity.deleteMultiple();\n\n        //locate the email records associated to the record being redacted where the body contain the piiData\n        grEmail = new GlideRecord('sys_email');\n        grEmail.addQuery('instance', record);\n        grEmail.addQuery('body', 'CONTAINS', piiData);\n        grEmail.query();\n\n        grEmail.deleteMultiple();\n    },\n\n    piiRedactField: function(table, record, field, piiData) {\n        if (table != undefined) {\n            var table = table;\n        } else {\n            return gs.log('Table required', 'piiRedaction');\n        }\n        if (record != undefined) {\n            var record = record;\n        } else {\n            return gs.log('Record sys_id required', 'piiRedaction');\n        }\n        if (field != undefined) {\n            var field = field;\n        } else {\n            return gs.log('Field name required', 'piiRedaction');\n        }\n        if (piiData != undefined) {\n            var piiData = piiData;\n        } else {\n            return gs.log('piiData string required', 'piiRedaction');\n        }\n\n        //variables for use in queries locating the records that need to be deleted\n\n        var grAudit;\n        var grJournal;\n        var grEmail;\n        var grHistory;\n        var grVariables;\n        var recordNumber;\n\n        //get the record\n\n        var gr = new GlideRecord(table);\n        gr.get(record);\n\n        //if the record has a number store it for later use\n\n        if (gr.getValue('number')) {\n            recordNumber = gr.getValue('number');\n        }\n\n        //locate the Journal records where the element_id matches the sys_id of the record being redacted and the value contains the piiData being redacted\n\n        grJournal = new GlideRecord('sys_journal_field');\n        grJournal.addQuery('element_id', record);\n        grJournal.addQuery('value', 'CONTAINS', piiData);\n        grJournal.query();\n\n        grJournal.deleteMultiple();\n\n\n        //If removing piiData from a specific field, not a variable, locate the Audit records.  Query for any records where the document key is the sys_id of the record being redacted and the piiData shows in either the new or old value\n\n        grAudit = new GlideRecord('sys_audit');\n        grAudit.addEncodedQuery('fieldname=' + field + 'documentkey=' + record + '^oldvalueLIKE' + piiData + '^ORnewvalueLIKE' + piiData);\n        grAudit.query();\n\n        grAudit.deleteMultiple();\n\n\n        //Locate the History Records.  All history records for the record need to be removed so they can be regenerated when the history set is loaded again\n\n        grHistory = new GlideRecord('sys_history_set');\n        grHistory.addQuery('id', record);\n        grHistory.query();\n\n        grHistory.deleteMultiple();\n\n        //if deleting piiData from a variable look up all records on the SC Item Option Mtom table where the request_item.number is the number of the RITM being redacted and the variable name matches the variables name passed into the function\n\n        if (recordNumber != undefined && table == 'sc_req_item') {\n            grVariables = new GlideRecord('sc_item_option_mtom');\n            grVariables.addEncodedQuery('request_item.numberSTARTSWITH' + recordNumber + '^sc_item_option.value=' + piiData);\n            grVariables.query();\n\n            grVariables.deleteMultiple();\n        }\n\n\n        //locate the email records associated to the record being redacted where the body contain the piiData\n        grEmail = new GlideRecord('sys_email');\n        grEmail.addQuery('instance', record);\n        grEmail.addQuery('body', 'CONTAINS', piiData);\n        grEmail.query();\n\n        grEmail.deleteMultiple();\n\n        var grActivity = new GlideRecord('sys_activity');\n        grActivity.addQuery('document_id', record);\n        grActivity.addQuery('payload', 'CONTAINS', piiData);\n        grActivity.query();\n\n        grActivity.deleteMultiple();\n\n        if (gr.getValue(field) == piiData) {\n            gr.setValue(field, 'NULL');\n            gr.update();\n        }\n    },\n\n    piiRedactRp: function(table, record, piiData) {\n        if (table != undefined) {\n            var table = table;\n        } else {\n            return gs.log('Table name required', 'piiRedactRp');\n        }\n\n        if (record != undefined) {\n            var record = record;\n        } else {\n            return gs.log('Record sys_id required', 'piiRedactRp');\n        }\n\n        if (piiData != undefined) {\n            var piiData = piiData;\n        } else {\n            return gs.log('piiData string required', 'piiRedactRp');\n        }\n\n        //variables for use in queries locating the records that need to be deleted\n\n        var grAudit;\n        var grJournal;\n        var grEmail;\n        var grHistory;\n\n        //get the record\n\n        var gr = new GlideRecord(table);\n        gr.get(record);\n\n        //if the record has a number store it for later use\n\n        if (gr.getValue('number')) {\n            recordNumber = gr.getValue('number');\n        }\n\n        //locate the Journal records where the element_id matches the sys_id of the record being redacted and the value contains the piiData being redacted\n\n        grJournal = new GlideRecord('sys_journal_field');\n        grJournal.addQuery('element_id', record);\n        grJournal.addQuery('value', 'CONTAINS', piiData);\n        grJournal.query();\n\n        grJournal.deleteMultiple();\n\n        //locate the Audit records.  Query for any records where the document key is the sys_id of the record being redacted and the piiData shows in either the new or old value\n\n        grAudit = new GlideRecord('sys_audit');\n        grAudit.addEncodedQuery('documentkey=' + record + '^oldvalueLIKE' + piiData + '^ORnewvalueLIKE' + piiData);\n        grAudit.query();\n\n        grAudit.deleteMultiple();\n\n        //Locate the History Records.  All history records for the record need to be removed so they can be regenerated when the history set is loaded again\n\n        grHistory = new GlideRecord('sys_history_set');\n        grHistory.addQuery('id', record);\n        grHistory.query();\n\n        grHistory.deleteMultiple();\n\n        //locate the Question/Answer records for the record producer\n        var grQA = new GlideRecord('question_answer');\n        grQA.addQuery('table_sys_id', record);\n        grQA.addQuery('value', piiData);\n        grQA.query();\n\n        grQA.deleteMultiple();\n\n        var grActivity = new GlideRecord('sys_activity');\n        grActivity.addQuery('document_id', record);\n        grActivity.addQuery('payload', 'CONTAINS', piiData);\n        grActivity.query();\n\n        grActivity.deleteMultiple();\n\n        //locate the email records associated to the record being redacted where the body contain the piiData\n        grEmail = new GlideRecord('sys_email');\n        grEmail.addQuery('instance', record);\n        grEmail.addQuery('body', 'CONTAINS', piiData);\n        grEmail.query();\n\n        grEmail.deleteMultiple();\n\n    },\n\n    piiRedactQa: function(record, variableName) {\n\n        if (record != undefined) {\n            var record = record;\n        } else {\n            return gs.log('Record sys_id required', 'piiRedactRp');\n        }\n\n        if (piiData != undefined) {\n            var piiData = piiData;\n        } else {\n            return gs.log('piiData string required', 'piiRedactRp');\n        }\n\n        var gr = new GlideRecord('question_answer');\n        gr.addEncodedQuery('table_sys_id=' + record + '^question.name=' + variableName);\n        gr.query();\n\n\t\twhile(gr.next()) {\n\n\t\tvar piiData = gr.getValue('value');\t\n        \n\t\tgr.deleteRecord();\n\t\t}\n\t\t\n        var grActivity = new GlideRecord('sys_activity');\n        grActivity.addQuery('document_id', record);\n        grActivity.addQuery('payload', 'CONTAINS', piiData);\n        grActivity.query();\n\n        grActivity.deleteMultiple();\n    },\n    type: 'piiRedaction'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Password Generator with specific length/PasswordGenerator.js",
    "content": "var PasswordGenerator = Class.create();\nPasswordGenerator.prototype = {\n    initialize: function() {},\n\n    //\n    // Input: Minimum password length that is required\n\t// Returns a random password for the min length specified\n    //\n    generate: function(givenPasswordLength) {\n        var specials = '!@#$%&*()_+<>[].~';\n        var lowercase = 'abcdefghijklmnopqrstuvwxyz';\n        var uppercase = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';\n        var numbers = '0123456789';\n        var all = specials + lowercase + uppercase + numbers;\n\n        String.prototype.pick = function(min, max) {\n            var n, chars = '';\n            if (typeof max === 'undefined') {\n                n = min;\n            } else {\n                n = min + Math.floor(Math.random() * (max - min));\n            }\n            for (var i = 0; i < n; i++) {\n                chars += this.charAt(Math.floor(Math.random() * this.length));\n            }\n            return chars;\n        };\n\n\n        String.prototype.shuffle = function() {\n            var array = this.split('');\n            var tmp, current, top = array.length;\n\n            if (top)\n                while (--top) {\n                    current = Math.floor(Math.random() * (top + 1));\n                    tmp = array[current];\n                    array[current] = array[top];\n                    array[top] = tmp;\n                }\n            return array.join('');\n        };\n\n        //adjust the pick numbers here to increase or decrease password strength\n        var ent = givenPasswordLength - 4;\n        if (ent < 0) {\n            ent = 0;\n        }\n\n        var password = (specials.pick(1) + lowercase.pick(1) + uppercase.pick(1) + numbers.pick(1) + all.pick(ent)).shuffle();\n        return (password + '');\n    },\n\n\n    type: 'PasswordGenerator'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Password Generator with specific length/README.md",
    "content": "# Generates a random password with a specified length\n# NOTE: There is a OOTB script that generates password but length is between 8 to 10 characters. However, if you need a simple password generator with specified length you can use this.\n\n# Example\n\n```\nvar helper = new PasswordGenerator();\n\nvar result = helper.generate(20); // length of password\n\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/PerformanceAnalyticsUtils/PerformanceAnalyticsUtils.js",
    "content": "var PerformanceAnalyticsUtils = Class.create();\nPerformanceAnalyticsUtils.prototype = {\n\t\n    initialize: function() {\n    },\n\n\t/**SNDOC\n\t\t@name getCmdbClassTableNames\n\t\t@description Retrieve an array of all the child classes from a start class\n\t\t\n\t\t@param {string} [startClassTableName]\n\t\t\n\t\t@returns {array} An array of all child classes from the start class downwards\n\t*/\n\t\n\tgetCmdbClassTableNames: function(startClassTableName) {\n\t\t\n\t\tvar dbObjectIds = [];\n\t\t\n\t\t// From the start table sys_class_path, get the child tables\n\t\t\n\t\tvar grDbObjectStart = new GlideRecord('sys_db_object');\n\t\tif (grDbObjectStart.get('name', startClassTableName)) {\n\t\t\t\n\t\t\tvar grDbObjectChild = new GlideRecord('sys_db_object');\n\t\t\tgrDbObjectChild.addQuery('sys_class_path', 'CONTAINS', grDbObjectStart.getValue('sys_class_path'));\n\t\t\tgrDbObjectChild.query();\n\t\t\n\t\t\t// Put all the child table sys_id's into the returned array\n\t\t\twhile (grDbObjectChild.next()) {\n\t\t\t\tdbObjectIds.push(grDbObjectChild.getUniqueValue());\n\t\t\t}\n\t\t\t\n\t\t}\n\t\t\n\t\treturn dbObjectIds;\n\t\t\n\t},\t\n    type: 'PerformanceAnalyticsUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/PerformanceAnalyticsUtils/README.md",
    "content": "# Performance Analytics Utils\n\nA Script Include to gather methods for handling Performance Analytics subjects. Typical use case is to be invoked by PA Scripts (for custom aggregation or breakdown mappings).\n\n## getCmdbClassTableNames\n\nReturns sys_id's of all the tables (sys_db_object) starting -and including- a start class. This is very useful to build a PA Breakdown on CMDB Classes. This method is fast as it uses the sys_class_path field to query sys_db_object.\n\nThis is a replacement for SNC.CMDBUtil.getAllChildrenOfAsCommaList() that I did not manage to invoke from Breakdown Source record Condition Builder (was getting an illegal access error because of the SNC scope).\n\nI understand this also could be in an CMDBUtil Script Include, but I found very useful for building a PA Breakdown Source.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Populate MRVS from Excel/ParsingScript.js",
    "content": "parseExcel: function() {\n    var attachSysId = this.getParameter('sysparm_id');\n    var attachment = new GlideSysAttachment();\n    var parser = new sn_impex.GlideExcelParser();\n    var mrvsArray = [];\n    // get content of the attachment\n    var content = attachment.getContentStream(attachSysId);\n    parser.parse(content);\n    // Iterate through each row after header. Return false if row doesn't exist\n    while (parser.next()) {\n        // get content of the row\n        var row = parser.getRow();\n        //push the object with key same as variable name in the MRVS.\n        var obj = {};\n        obj.employee_id = row['Id'];\n        obj.employee_name = row['Name'];\n        mrvsArray.push(obj);\n    }\n    return JSON.stringify(mrvsArray);\n},\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Populate MRVS from Excel/README.md",
    "content": "This script allows to parse the excel file attached to the attachment variable and populate the MRVS present in the\ncatalog item / record producer.\nUse this script in a client-callable script include along with an onChange client script on the attachment variable.\n\nWhen a file is uploaded as an attachment, it's metdata is stored in the sys_attachment table and sys_attachment_doc contains\nthe actual binary content.\n\n**getContentStream()** converts the binary content in a way so that it can be parsed by GlideExcelParser API.\n\n**Example used-**\n\nThe excel has two columns \"Id\" and \"Name\" to store employee details. MRVS also has the variable name as \"employee_id\" and \"employee_name\".\n\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Prevent circular dependencies in task relationships/README.md",
    "content": "# Detect Circular Reference in Task Dependencies\n\n\n\n## Overview\n\nThis Script Include helps identify **circular dependencies** in task relationships within ServiceNow. Circular references can cause workflow issues, reporting errors, and logic failures in project management or task tracking modules.\n\n## What It Does\n\n- Traverses task dependencies recursively.\n- Detects if a task is indirectly dependent on itself.\n- Returns `true` if a circular reference is found, `false` otherwise.\n\n## Use Case\n\nImagine Task A depends on Task B, and Task B depends on Task A. This creates a circular loop that can break automation or cause infinite recursion. This script helps prevent such configurations.\n\nTables and modules it would be usefull\nIt is used for Table: planned_task_rel_planned_task\nThis table is used to define task dependencies between project tasks in ServiceNow's Strategic Portfolio Management (SPM) or Project Portfolio Management (PPM) modules. It stores relationships such as:\n\nPredecessor Task\nSuccessor Task\nDependency Type (e.g., Finish-to-Start)\n\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Prevent circular dependencies in task relationships/code.js",
    "content": "var DependencyChecker = Class.create();\nDependencyChecker.prototype = {\n    initialize: function() {},\n\n    hasCircularReference: function(taskId) {\n        var visited = {};\n        return this._check(taskId, visited);\n    },\n\n    _check: function(taskId, visited) {\n        if (visited[taskId]) return true;\n        visited[taskId] = true;\n\n        var gr = new GlideRecord('task_dependency');\n        gr.addQuery('dependent_task', taskId);\n        gr.query();\n\n        while (gr.next()) {\n            if (this._check(gr.task.toString(), visited)) return true;\n        }\n\n        return false;\n    },\n\n    type: 'DependencyChecker'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Project Base Line/README.md",
    "content": "// Sample code to pull the latest planned baseline tasks list of each project. It will fetch the list and push it an array.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Project Base Line/latestPlannedBaseline.js",
    "content": "var LatestBaselineList = Class.create();\nLatestBaselineList.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    runReport: function() {\n\nvar answer=[];\n        var prj = new GlideRecord('pm_project');\n        prj.addEncodedQuery('stateIN-5,2');\n        prj.query();\n\n        while (prj.next()) {\n            var base = prj.getValue('sys_id');\n            var plannedBLines = new GlideRecord('planned_task_baseline');\n            plannedBLines.addQuery('top_task', base);\n            //plannedBLines.addEncodedQuery('task.key_LatestBaselineList=true^task.top_task.state=2');\n            plannedBLines.orderByDesc('sys_created_on');\n            //plannedBLines.setLimit(1);\n\t\t\t\n            plannedBLines.query();\n            if (plannedBLines.next()) {\n                var baseLineId = plannedBLines.getValue('sys_id');\n                var baseLineName = plannedBLines.baseline.name;\n\t\t\t\tanswer.push(baseLineId);\n\t\t\t\t\n            }\n           \n\t}\n\t\treturn answer;\n        \n    },\n\n    type: 'LatestBaselineList'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Public Script Include search/README.md",
    "content": "# Find public Script Includes\n\nThe snippet helps to find public Script Includes (can be executed by a non-logged-in user) and check their security later manually.\n\nSuch Script Include can be called from a public page (e.g. login.do page) in the browser console."
  },
  {
    "path": "Server-Side Components/Script Includes/Public Script Include search/script.js",
    "content": "var publicSIArr = [];\nvar nonPublicSIArr = [];\nvar currentScope = gs.getCurrentScopeName() == 'rhino.global' ? 'global' : gs.getCurrentScopeName();\n\nvar scriptIncludeGR = new GlideRecord('sys_script_include');\nscriptIncludeGR.addActiveQuery();\nscriptIncludeGR.addQuery('client_callable', true);\nscriptIncludeGR.addQuery('sys_scope.scope', currentScope).addOrCondition('access', '!=', 'package_private');\nscriptIncludeGR.query();\nwhile (scriptIncludeGR.next()) {\n\tvar apiNameArr = scriptIncludeGR.getValue('api_name').split('.');\n\tvar scope = apiNameArr[0];\n\tvar name = apiNameArr[1];\n\n\ttry {\n\t\tvar klass = this[scope][name].prototype;\n\n\t\tif (typeof klass.isPublic === 'function') {\n\t\t\tvar isPublic = klass.isPublic();\n\n\t\t\tif (isPublic === true) {\n\t\t\t\tpublicSIArr.push(name);\n\t\t\t} else {\n\t\t\t\tnonPublicSIArr.push(name);\n\t\t\t}\n\t\t}\n\t} catch (e) {\n\t\tgs.log(name + ' ' + e);\n\t}\n}\n\ngs.log('Count of public Script Includes: ' + publicSIArr.length);\ngs.log('Public Script Includes: ' + publicSIArr.toString());\ngs.log('Count of explicitly non-public Script Includes: ' + nonPublicSIArr.length);\ngs.log('Explicitly non-public Script Includes: ' + nonPublicSIArr.toString());"
  },
  {
    "path": "Server-Side Components/Script Includes/PullEmptySerialNumberAssetRecords/README.md",
    "content": "Overview\nThe getAssetRecord Script Include is designed to identify records in the alm_asset table that are missing a serial_number. This functionality is crucial for maintaining the integrity of asset management data, as it ensures that all assets have complete and accurate information. The script executes a query to detect assets with a missing serial_number and returns a summary that includes the count of such records and a direct link to view them in the ServiceNow interface.\n\nHow It Works\nInitialization:\n\nThe script defines the assetRecord() method, which is responsible for executing the logic to find asset records with missing serial_number.\nQuery Execution:\n\nA GlideRecordSecure object is created to query the alm_asset table.\nAn encoded query is added using addEncodedQuery('serial_numberEMPTY'), which checks for records where the serial_number field is empty.\nResult Compilation:\n\nThe script counts the number of records that match the query using getRowCount().\nThe instance's base URL is retrieved using gs.getProperty('glide.servlet.uri'), which is used to construct a complete URL to view the filtered list of records.\nAn object containing the rule description, count of missing serial numbers, table name, and URL is created and pushed to the result array.\nOutput:\n\nThe method returns the result array as a JSON string, which includes:\nruleDescription: Describes the purpose of the query.\ncount: Number of assets with missing serial numbers.\ntableName: Identifies the alm_asset table.\nurl: A link to the ServiceNow list view filtered for these records.\nTesting\nUnit Testing:\n\nYou can test the script by executing it in the Scripts - Background module in ServiceNow.\nTo run a test, enter the following code in the background script editor:\njavascript\nCopy code\nvar assetScriptInclude = new getAssetRecord();\nvar result = assetScriptInclude.assetRecord();\ngs.info('Result from assetRecord: ' + result);\nAfter executing the script, check the logs under System Logs > All to review the output.\nIntegration Testing:\n\nIntegrate this Script Include into other ServiceNow processes, such as scheduled jobs or business rules, to automate the detection of assets with missing serial_number.\nMonitor the output to ensure it meets expectations when invoked under various conditions.\nBenefits\nAutomated Data Validation:\n\nThis Script Include automates the detection of assets with incomplete information, significantly reducing manual checks and improving efficiency.\nActionable Insights:\n\nThe output includes a direct URL to the affected records, allowing asset managers and IT staff to quickly navigate to the relevant data for review and remediation.\nImproved Data Integrity:\n\nRegular use of this script helps maintain high data quality in the asset management system, reducing the risk of compliance issues or operational inefficiencies.\nEnhanced Reporting:\n\nBy identifying assets without a serial_number, organizations can generate accurate reports and make informed decisions regarding asset management and inventory control.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/PullEmptySerialNumberAssetRecords/pull_empty_serial_number_record.js",
    "content": "var getAssetRecord = Class.create();\ngetAssetRecord.prototype = {\n    initialize: function() {},\n\t\n   assetRecord: function() {\n        try { \n\t\t\tvar tableName= \"alm_asset\";\n            var result = [];\n            var ruleDescription = \"Records in Asset with missing Serial Number\";\n\n            // GlideRecord query to get all assets with missing Serial Number\n            var grAsset = new GlideRecordSecure(tableName);\n            grAsset.addEncodedQuery('serial_numberEMPTY'); // Checking for missing serial numbers\n            grAsset.query();\n\n            var count = grAsset.getRowCount(); // Count of records with missing serial numbers\n\n            // Get the instance URL\n            var instanceUrl = gs.getProperty('glide.servlet.uri'); // Get the instance's base URL\n            var url = instanceUrl + 'alm_asset_list.do?sysparm_query=serial_numberEMPTY'; // Complete URL for assets with missing serial number\n\n            // Create the object with required fields and push to result array\n            result.push({\n                ruleDescription: ruleDescription,\n                count: count,\n                tableName: tableName,\n                url: url\n            });\n\n            // Return the result as a JSON string\n            return JSON.stringify(result);\n        } catch (e) {\n            // Log the error in case something goes wrong\n            var message = 'In INHUB_0198 method, failure could be due to ' + e.message;\n            return [\"Error\", message];\n        }\n    },\n    type: 'getAssetRecord'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Query ldap server/LDAPquery.js",
    "content": "/**\n * LDAPquery class provides a fluent interface for querying LDAP entries.\n * It wraps GlideLDAP and allows configuration of RDN, filters, pagination, and server config.\n * @class\n *\n * @example\n * Example 1: Retrieve a single LDAP record\n * var result = new LDAPquery()\n *   .setRDN('')\n *   .setFilter('uid=einstein')\n *   .setConfig('2fd003c083e07e10557ff0d6feaad3d7')\n *   .getOne();\n * gs.info(result.telephoneNumber);\n *\n * @example\n * Example 2: Retrieve multiple LDAP records with pagination\n * var result = new LDAPquery()\n *   .setRDN('ou=scientists')\n *   .setPagination(1000)\n *   .setConfig('2fd003c083e07e10557ff0d6feaad3d7')\n *   .getIterable();\n *\n * var item;\n * while (item = result.next()) {\n *   gs.info(JSON.stringify(j2js(item), null, 2));\n * }\n */\nvar LDAPquery = Class.create();\n\n/**\n * @typedef {Object} LDAPquery\n * @property {string} ldapConfig - The sys_id of the LDAP server config.\n * @property {string} rdn - The relative distinguished name to start the query from.\n * @property {string} filter - LDAP filter string.\n * @property {boolean} boolean - Boolean flag used in query logic.\n * @property {number} rowsPerPage - Number of rows to return per page.\n * @property {GlideLDAP} glideLdap - GlideLDAP instance used to perform queries.\n */\nLDAPquery.prototype = {\n\t/**\n\t * Initializes the LDAPquery instance with default values.\n\t */\n\tinitialize: function () {\n\t\tthis.ldapConfig = '';\n\t\tthis.rdn = '';\n\t\tthis.filter = '';\n\t\tthis.boolean = true;\n\t\tthis.rowsPerPage = 1;\n\t\tthis.glideLdap = new GlideLDAP();\n\t},\n\n\t/**\n\t * Sets the relative distinguished name (RDN) for the query.\n\t * @param {string} rdn - The RDN string.\n\t * @returns {LDAPquery} Returns the current instance for chaining.\n\t */\n\tsetRDN: function (rdn) {\n\t\tthis.rdn = rdn || '';\n\t\treturn this;\n\t},\n\n\t/**\n\t * Sets the LDAP filter string.\n\t * @param {string} filter - The LDAP filter.\n\t * @returns {LDAPquery} Returns the current instance for chaining.\n\t */\n\tsetFilter: function (filter) {\n\t\tthis.filter = filter || '';\n\t\treturn this;\n\t},\n\n\t/**\n\t * Sets the number of rows to return per page.\n\t * @param {number} rows - Number of rows.\n\t * @returns {LDAPquery} Returns the current instance for chaining.\n\t */\n\tsetPagination: function (rows) {\n\t\tthis.rowsPerPage = rows || 1;\n\t\treturn this;\n\t},\n\n\t/**\n\t * Sets the LDAP server configuration using its sys_id.\n\t * Also sets the config on the GlideLDAP instance.\n\t * @param {string} config - The sys_id of the LDAP server config.\n\t * @returns {LDAPquery} Returns the current instance for chaining.\n\t */\n\tsetConfig: function (config) {\n\t\tthis.ldapConfig = config || '';\n\t\tthis.glideLdap.setConfigID(config);\n\t\treturn this;\n\t},\n\n\t/**\n\t * Executes the query and returns the first matching LDAP entry.\n\t * @returns {Object|null} Returns the first matching entry as a JavaScript object, or null if none found.\n\t * @throws Will raise an error if ldapConfig is not set.\n\t */\n\tgetOne: function () {\n\t\tif (!this.ldapConfig) {\n\t\t\tNiceError.raise('no ldap config defined');\n\t\t}\n\t\tvar entry;\n\t\tif (entry = this._query().next()) {\n\t\t\treturn j2js(entry);\n\t\t}\n\t\treturn null;\n\t},\n\n\t/**\n\t * Executes the query and returns an iterable result set.\n\t * @returns Returns a iterator over matching LDAP entries.\n\t * @throws Will raise an error if ldapConfig is not set.\n\t */\n\tgetIterable: function () {\n\t\tif (!this.ldapConfig) {\n\t\t\tNiceError.raise('no ldap config defined');\n\t\t}\n\t\treturn this._query();\n\t},\n\n\t/**\n\t * Internal method to perform the LDAP query using GlideLDAP.\n\t * @private\n\t * @returns Returns the result iterator.\n\t */\n\t_query: function () {\n\t\treturn this.glideLdap.getMatching(this.rdn, this.filter, this.boolean, this.rowsPerPage);\n\t},\n\n\ttype: 'LDAPquery'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Query ldap server/README.md",
    "content": "# Query ldap server by config sys_id\nInteract with GlideLDAP using a fluent wrapper. Handy for querying ldap server for non-persistent data for example via remote tables.\n\n# How to use it?\nConfigure LDAP server in *ldap_server_config* table\nQuery server from serverside script using script include LDAPQuery\n\n# Example 1: get one entry\n\n```javascript\nvar result = new LDAPquery()\n\t.setRDN('')                                      //set relative start \n\t.setFilter('uid=einstein')                       //set filter\n\t.setConfig('2fd003c083e07e10557ff0d6feaad3d7')   //set the sys_id for ldap_server_config record\n\t.getOne();                                       //returns first entry as js object\ngs.info(result.telephoneNumber);\n```\n```\noutput:\n*** Script: 314-159-2653\n```\n\n# Example 2: get the iterable for query\n\n```javascript\nvar result = new LDAPquery()\n\t.setRDN('ou=scientists') \n\t.setPagination(1000)                              //set how many records per page\n\t.setConfig('2fd003c083e07e10557ff0d6feaad3d7')\n\t.getIterable();                                   //returns iterable result object\n\nvar item;\nwhile (item = result.next()) {\n\tgs.info(JSON.stringify(j2js(item), null, 2)); //note that next returns a java object, hence the j2js to convert to js\n}\n```\n```\noutput:\n*** Script: {\n  \"dn\": \"ou=scientists,dc=example,dc=com\",\n  \"objectClass\": \"groupOfUniqueNames^top\",\n  \"ou\": \"scientists\",\n  \"source\": \"ldap:ou=scientists,dc=example,dc=com\",\n  \"uniqueMember\": \"uid=einstein,dc=example,dc=com^uid=tesla,dc=example,dc=com^uid=newton,dc=example,dc=com^uid=galileo,dc=example,dc=com\",\n  \"cn\": \"Scientists\"\n}\n*** Script: {\n  \"dn\": \"ou=italians,ou=scientists,dc=example,dc=com\",\n  \"objectClass\": \"groupOfUniqueNames^top\",\n  \"ou\": \"italians\",\n  \"source\": \"ldap:ou=italians,ou=scientists,dc=example,dc=com\",\n  \"uniqueMember\": \"uid=tesla,dc=example,dc=com\",\n  \"cn\": \"Italians\"\n}\nLDAP SEARCH: >>Next Page \n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Read CSV file from Mid Server/CSVReaderUtil.js",
    "content": "var CSVReaderUtil = Class.create();\nCSVReaderUtil.prototype = {\n    initialize: function() {\n        this.midServer = 'YOUR_MID_SERVER_NAME'; // Replace with your MID Server name\n    },\n\n    // Method to read CSV file from MID Server\n    readCSVFile: function(filePath) {\n        try {\n            // Create ECC Queue input record (Probe)\n            var probe = new GlideRecord('ecc_queue');\n            probe.agent = 'mid.server.' + this.midServer;\n            probe.topic = 'Command';\n            probe.name = 'read_csv_file';\n            probe.source = 'ServiceNow';\n            probe.queue = 'output';\n\n            var scriptPath = 'cat ' + filePath;\n            probe.payload = '<?xml version=\"1.0\" encoding=\"UTF-8\"?><parameters><parameter name=\"name\" value=\"' + scriptPath + '\"/></parameters>';\n\n            var probeId = probe.insert();\n\n            gs.info('CSV Read Probe sent with sys_id: ' + probeId);\n            return probeId;\n\n        } catch (e) {\n            gs.error('Error sending probe: ' + e.message);\n            return null;\n        }\n    },\n\n    // Method to check and retrieve the response\n    getCSVResponse: function(probeId) {\n        var gr = new GlideRecord('ecc_queue');\n        gr.addQuery('response_to', probeId);\n        gr.addQuery('state', 'processed');\n        gr.orderByDesc('sys_created_on');\n        gr.query();\n\n        if (gr.next()) {\n            var payload = gr.payload + '';\n            return this.parseCSVPayload(payload);\n        }\n        return null;\n    },\n\n    // Parse the CSV data from payload\n    parseCSVPayload: function(payload) {\n        var csvData = [];\n        try {\n            // Extract CSV content from XML payload\n            var xmlDoc = new XMLDocument2();\n            xmlDoc.parseXML(payload);\n            var content = xmlDoc.getNode('//stdout');\n\n            if (content) {\n                var lines = content.split('\\n');\n                for (var i = 0; i < lines.length; i++) {\n                    if (lines[i].trim()) {\n                        csvData.push(lines[i].split(','));\n                    }\n                }\n            }\n        } catch (e) {\n            gs.error('Error parsing CSV payload: ' + e.message);\n        }\n        return csvData;\n    },\n\n    type: 'CSVReaderUtil'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Read CSV file from Mid Server/README.md",
    "content": "ServiceNow MID Server CSV Reader\n\nThis utility enables reading CSV files from MID Server machines through the ECC Queue mechanism.\n\nOverview\n\nThe solution uses a Script Include named CSVReaderUtil.js to send and receive data between the ServiceNow instance and the MID Server.\nIt supports executing read operations on local CSV files stored on the MID Server filesystem.\n\nSteps to Use\n\nCreate the Script Include\nCreate a new Script Include named CSVReaderUtil.js in ServiceNow.\nThis Script Include handles communication with the MID Server and parsing of CSV data.\n\nTrigger the Script from a Background Script\n\nUse the following example to read data from a CSV file located on the MID Server:\n\nvar csvUtil = new CSVReaderUtil();\n\n// Send probe to the MID Server to read the CSV file\nvar probeId = csvUtil.readCSVFile('/path/to/your/file.csv');\n\n// Wait a few seconds to allow the MID Server to process the request\ngs.sleep(5000);\n\n// Retrieve the response from the ECC Queue\nvar csvData = csvUtil.getCSVResponse(probeId);\n\ngs.info('CSV Data: ' + csvData);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RecordProducerVariableUtils/README.md",
    "content": "# RecordProducerVariableUtils\n\nThe RecordProducerVariableUtils Script Include provides a utility function to dynamically create and associate variables with a specific record in ServiceNow. This function is designed to work with ServiceNow's Variable Editor to make created variables accessible and visible on record forms.\n\n## Methods\n\n* createVariable(currentGr, order, questionId):\n  * Accepts a GlideRecord object, variable display order, and the question's sys_id.\n  * Retrieves the table_name and sys_id of the current record.\n  * Initializes and inserts a new entry in the question_answer table to associate the variable with the specified record.\n  * Returns the sys_id of the newly created variable.\n\n## Usage\n\nBackground Script to add an additional variable to a change record:\n\n```javascript\nvar current = new GlideRecord(\"change_request\");\nif (current.get(\"<SYS_ID>\")){\n    RecordProducerVariableUtils.createVariable(current, -1000, \"<VARIABLE_SYS_ID>\");\n}\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RecordProducerVariableUtils/RecordProducerVariableUtils_v1.0.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<unload unload_date=\"2024-10-13 11:45:15\">\n<sys_script_include action=\"INSERT_OR_UPDATE\">\n<access>public</access>\n<active>true</active>\n<api_name>global.RecordProducerVariableUtils</api_name>\n<caller_access/>\n<client_callable>false</client_callable>\n<description>This script include is used to support functionality around custom usage of Record Producer variables</description>\n<mobile_callable>false</mobile_callable>\n<name>RecordProducerVariableUtils</name>\n<script><![CDATA[var RecordProducerVariableUtils = Class.create();\n\n// Function to create a new variable\nRecordProducerVariableUtils.createVariable = function(currentGr, order, questionId) {\n\n    // Get table name and the sys_id of the record\n    var recordId = currentGr.getUniqueValue();\n    var tableName = currentGr.getTableName();\n\n\t// Create variable and associate it with the record so that it can be visible via Variable Editor\n    var variableGr = new GlideRecord('question_answer');\n    variableGr.initialize();\n    variableGr.setValue('order', order);\n    variableGr.setValue('question', questionId);\n    variableGr.setValue('table_name', tableName);\n    variableGr.setValue('table_sys_id', recordId);\n    \n\treturn variableGr.insert();\n};]]></script>\n<sys_class_name>sys_script_include</sys_class_name>\n<sys_created_by>ivan.betev</sys_created_by>\n<sys_created_on>2024-09-26 13:00:35</sys_created_on>\n<sys_id>9e42a3b2837c525013c9bac6feaad333</sys_id>\n<sys_mod_count>3</sys_mod_count>\n<sys_name>RecordProducerVariableUtils</sys_name>\n<sys_package display_value=\"Global\" source=\"global\">global</sys_package>\n<sys_policy/>\n<sys_scope display_value=\"Global\">global</sys_scope>\n<sys_update_name>sys_script_include_9e42a3b2837c525013c9bac6feaad333</sys_update_name>\n<sys_updated_by>ivan.betev</sys_updated_by>\n<sys_updated_on>2024-10-13 11:33:20</sys_updated_on>\n</sys_script_include>\n</unload>\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RecordProducerVariableUtils/script.js",
    "content": "var RecordProducerVariableUtils = Class.create();\n\n// Method to create a new variable\n// by providing target GlideRecord, variable order, and variable (question) sys_id\nRecordProducerVariableUtils.createVariable = function(currentGr, order, questionId) {\n\n    // Get table name and the sys_id of the record\n    var recordId = currentGr.getUniqueValue();\n    var tableName = currentGr.getTableName();\n\n\t// Create variable and associate it with the record so that it can be visible via Variable Editor\n    var variableGr = new GlideRecord('question_answer');\n    variableGr.initialize();\n    variableGr.setValue('order', order);\n    variableGr.setValue('question', questionId);\n    variableGr.setValue('table_name', tableName);\n    variableGr.setValue('table_sys_id', recordId);\n    \n\treturn variableGr.insert();\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Records Calculator/README.md",
    "content": "\n#  Records Calculator\nProvides functions to easily calculate values across multiple records\nThis class provides shortcut functions that works in both Client and Server sides.\n\n## Note: \nIt is part of a case management application built with App Engine. Contact me for more information: \n \n # Usage\n\nWhen you copy this script into your scoped application, make sure to set the value of \"Accessible from\" in the Script Include form to \"All application scopes\". Otherwise you will get this error: \"Illegal access to private script include Calculator in scope <script scope> being called from scope <your scope>\", \n\n```javascript\n\nvar last_login = new x_your_cope.Calculator().getMax('sys_user', 'last_login', 'user_name!=admin');\ngs.info ( \"Last time a user logged in: \" + last_login) ;\n\n\n/* \n * With a table of business owners with a column 'ownership_percentage' we \n * want to calculate the sum of all ownership percentage and use that in a\n * business rule to insure it does not exceed 100%\n */\nvar tableName = \"x_snc_psd_pas_owner\";\nvar fieldName = 'ownership_percentage';\nvar query = 'business_entity=bb5cb5811b8f30107d4c2171604bcb78';\n\nvar sum = new x_snc_ecms.Calculator().getSum(tableName, 'ownership_percentage', query);\ngs.info ( \"Sum of ownerships = \" + sum) ;\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Records Calculator/RecordsCalculator.js",
    "content": "/* \n * RecordsCalculator \n * \n * Provides functions to easily calculate values across multiple records\n * The functions work in both Client and Server scripts.\n */\nvar RecordsCalculator = Class.create();\nRecordsCalculator.prototype = Object.extendsObject(global.AbstractAjaxProcessor, {\n\n\n    _getAggregateByType: function (in_tableName, pColumn, pEncodedQuery, pAggregateType) {\n        var ga = new GlideAggregate(in_tableName);\n        if (pEncodedQuery) {\n            ga.addQuery(pEncodedQuery);\n        }\n        ga.setGroup(false);\n        //ga.setOrder(false);\n        ga.addAggregate(pAggregateType, pColumn);\n        ga.query();\n        if (ga.next()) {\n            return ga.getAggregate(pAggregateType, pColumn);\n        } else {\n            return null;\n        }\n    },\n\n    /**\n     * Retrieve the parameters value independently of where they come from: passed as parameters or in the Ajax call\n     */\n    _getParameters: function (in_tableName, in_fieldName, in_encodedQuery) {\n        var tableName = global.JSUtil.nil(in_tableName) ? this.getParameter('sysparm_tableName') : in_tableName;\n        var fieldName = global.JSUtil.nil(in_fieldName) ? this.getParameter('sysparm_fieldName') : in_fieldName;\n        var encodedQuery = global.JSUtil.nil(in_encodedQuery) ? this.getParameter('sysparm_encodedQuery') : in_encodedQuery;\n        return {\n            tableName: tableName,\n            fieldName: fieldName,\n            encodedQuery: encodedQuery\n        };\n    },\n\n    getMin: function (in_tableName, in_fieldName, in_encodedQuery) {\n        var param = this._getParameters(in_tableName, in_fieldName, in_encodedQuery);\n        return this._getAggregateByType(param.tableName, param.fieldName, param.encodedQuery, 'MIN');\n    },\n\n    getMax: function (in_tableName, in_fieldName, in_encodedQuery) {\n        var param = this._getParameters(in_tableName, in_fieldName, in_encodedQuery);\n        return this._getAggregateByType(param.tableName, param.fieldName, param.encodedQuery, 'MAX');\n    },\n\n    getAvg: function (in_tableName, in_fieldName, in_encodedQuery) {\n        var param = this._getParameters(in_tableName, in_fieldName, in_encodedQuery);\n        return this._getAggregateByType(param.tableName, param.fieldName, param.encodedQuery, 'AVG');\n    },\n\n    getSum: function (in_tableName, in_fieldName, in_encodedQuery) {\n        var param = this._getParameters(in_tableName, in_fieldName, in_encodedQuery);\n        return this._getAggregateByType(param.tableName, param.fieldName, param.encodedQuery, 'SUM');\n    },\n\n    type: 'RecordsCalculator'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/Recursive GlideRecord Fetcher/README.md",
    "content": "#  Recursive GlideRecord Fetcher\n##  Overview\nThis snippet provides a reusable logic to recursively fetch child records from a parent record in ServiceNow. It is useful for traversing hierarchical relationships such as tasks, categories, CMDB CI relationships, or any table with a parent-child structure.\n\nThe logic prevents infinite loops by tracking visited records and supports nesting of children for structured output.\n\n---\n\n##  Use Case\n- Fetch all subtasks under a parent task.\n- Traverse CMDB CI relationships recursively.\n- Build hierarchical views of organizational units or categories.\n\n---\n\n##  Parameters\n| Parameter        | Description                                                                 |\n|------------------|-----------------------------------------------------------------------------|\n| `tableName`      | Name of the table to query (e.g., `task`, `cmdb_ci`, `custom_table`)        |\n| `parentField`    | Field that links to the parent record (e.g., `parent`, `parent_id`)         |\n| `parentSysId`    | Sys ID of the root parent record to start traversal                         |\n\n---\n\n##  Example Usage\n```javascript\nvar fetcher = new RecursiveFetcher('task', 'parent');\nvar hierarchy = fetcher.fetchChildren('abc123sysid'); // Replace with actual parent sys_id\ngs.info(JSON.stringify(hierarchy));\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Recursive GlideRecord Fetcher/code.js",
    "content": "var RecursiveFetcher = Class.create();\nRecursiveFetcher.prototype = {\n    initialize: function(tableName, parentField) {\n        this.tableName = tableName;\n        this.parentField = parentField;\n        this.visited = [];\n    },\n\n    fetchChildren: function(parentSysId) {\n        if (this.visited.indexOf(parentSysId) !== -1) {\n            // Avoid infinite loops\n            return [];\n        }\n\n        this.visited.push(parentSysId);\n        var children = [];\n\n        var gr = new GlideRecord(this.tableName);\n        gr.addQuery(this.parentField, parentSysId);\n        gr.query();\n\n        while (gr.next()) {\n            var child = {\n                sys_id: gr.getValue('sys_id'),\n                name: gr.getDisplayValue('name') || gr.getDisplayValue('short_description'),\n                children: this.fetchChildren(gr.getValue('sys_id')) // Recursive call\n            };\n            children.push(child);\n        }\n\n        return children;\n    },\n\n    type: 'RecursiveFetcher'\n};\n\n// Example usage:\n//var fetcher = new RecursiveFetcher('task', 'parent');\n//var hierarchy = fetcher.fetchChildren('abc123sysid'); // Replace with actual parent sys_id\n//gs.info(JSON.stringify(hierarchy));\n``\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Regex utils/README.md",
    "content": "# RegexUtils\n\nA script include with some of the common Regex related functions\n\n## Usage\n\nMail script\n```javascript\nvar concatenatedString = `\nName: rahman mahmoodi\nPosition: Tech\nCompany: ValueFlow\n`; // If using ES6\nvar helper = new RegexUtils();\nvar name = helper.findFieldValue(\"Name\", concatenatedString, \":\");\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Regex utils/RegexUtils.js",
    "content": "var RegexUtils = Class.create();\nRegexUtils.prototype = {\n    initialize: function() {},\n\n    /**\n     * \n     * @param {String} text that tokens to be replaced in \n     * @param {String} Token to be replaced\n     * @param {String} replaceTo \n     * @returns Returns new text with updated token\n     * \n     * NB: This is case in-sensitive\n     */\n    replaceAllIgnoreCase: function(text, replaceFrom, replaceTo) {\n        var regEx = new RegExp(replaceFrom, \"ig\");\n        return text.replace(regEx, replaceTo);\n    },\n\n    /**\n     * \n     * @param {String} text that tokens to be replaced in \n     * @param {String} Token to be replaced\n     * @param {String} replaceTo \n     * @returns Returns new text with updated token\n     * \n     * NB: This is case sensitive\n     */\n    replaceAllMatchCase: function(text, replaceFrom, replaceTo) {\n        var regEx = new RegExp(replaceFrom, \"g\");\n        return text.replace(regEx, replaceTo);\n    },\n\n    /**\n     * \n     * @param {String} field to find the value for \n     * @param {String} text that contains a list of fields and values \n     * @returns returns field values\n     * \n     * Example text:\n     * \n     * Name: rahman mahmoodi\n     * Position: Tech\n     * Company: ValueFlow\n     * \n     * findFieldValue(\"Position\", text)\n     */\n    findFieldValue: function(field, text) {\n        return this._findFeildValue(field, text, \":\");\n    },\n\n    /**\n     * \n     * @param {String} field to find the value for \n     * @param {String} text that contains a list of fields and values \n     * @param {String} delimiter to be used to split the string based\n     * @returns returns field values\n     * \n     * Example text:\n     * \n     * Name: rahman mahmoodi\n     * Position: Tech\n     * Company: ValueFlow\n     * \n     * findFieldValue(\"Position\", text, \":\")\n     */\n    findFieldValue: function(field, text, delimiter) {\n        return this._findFeildValue(field, text, delimiter);\n    },\n\n    _findFeildValue: function(field, text, delimiter) {\n        if (!field || !text || !delimiter) return \"\";\n\n        var result = '';\n        var regExp = new RegExp(field + delimiter + '(.*)', 'g');\n        var match = text.match(regExp);\n        if (match && match.length > 0) {\n            var fieldList = match[0].split(delimiter);\n            if (fieldList.length > 1) {\n                result = fieldList[1].replace(/[\\[\\]]/g, '');\n                result = result.trim();\n            }\n        }\n\n        return result;\n    },\n\n    /**\n     * \n     * @param {String} email to validate \n     * @returns returns true if valid email\n     */\n    isValidEmail: function(email) {\n        if (!email) return false;\n        var pattern = /^(([^<>()[\\]\\\\.,;:\\s@\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$/;\n        return pattern.test(email.toLowerCase());\n    },\n\n    /**\n     * \n     * @param {number} number to validate \n     * @returns returns true if the number is a positive integer\n     */\n    isInteger: function(number) {\n        if (!number) return false;\n        var regex = /^\\d+$/;\n        return regex.test(number);\n    },\n\n    /**\n     * \n     * @param {number} number to validate \n     * @returns returns true if the number is a decimal digit\n     * \n     * NB: This will match all the numbers in the form of \n     * \n     * \t3.14529, -255.34, 128, 1.9e10, 123,340.00\n     */\n    isDecimal: function(number) {\n        if (!number) return false;\n        var regex = /^-?\\d+(,\\d+)*(\\.\\d+(e\\d+)?)?$/;\n        return regex.test(number);\n    },\n\n    /**\n     * \n     * @param {String} password to validate \n     * @returns returns true if the password contains One or More Upper, one or more Lower, and one or more Special character,\n     * one or more numbers, and minimum of 8 characters\n     * \n     * Example: Pa$$word1!\n     */\n    isStrongPassword: function(password) {\n        if (!password) return false;\n        // positive look ahead to check for each condition\n        var regex = /^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[@$!%*?&])[A-Za-z\\d@$!%*?&]{8,}$/;\n        return regex.test(password);\n    },\n\n    /**\n     * \n     * @param {Regex} regex : regex to execute. Regex should include groups\n     * @param {string} text : text to execute the regex on \n     * @returns returns an array of all groups identified via regex\n     * Example: var reg = new RegexUtils();\n     * var groups = reg.executeGroups(/(\\d{4})(\\d{3})(\\d{3})/gm, \"0423394881\");\n\t *\n\t * Or find all numbers in a string\n\t * var reg = new RegexUtils();\n\t * var result = reg.executeGroups(/\\b\\d+\\b/g, 'A string with 3 numbers in it... 42 and 88.');\n     */\n    executeGroups: function(regex, text) {\n        if (!regex || !text) return null;\n\n        var groups = [];\n\n        while ((m = regex.exec(text)) !== null) {\n            // This is necessary to avoid infinite loops with zero-width matches\n            if (m.index === regex.lastIndex) {\n                regex.lastIndex++;\n            }\n\n            // The result can be accessed through the `m`-variable.\n            m.forEach(function(match, groupIndex) {\n                if (match) {\n                    groups.push(match);\n                }\n            });\n        }\n\n        return groups;\n    },\n\n    type: 'RegexUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Reparent Table/README.md",
    "content": "# Reparent Table\n\nThis function `reparentTable` is to reparent the existing table to the another parent.\n\nThis `glide.rollback.blacklist.TableParentChange.change` property can restrict user to reparent the table so in script we first set it to **false** before reparenting and later on changed it to its initially value once reparenting is done.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Reparent Table/script.js",
    "content": "function reparentTable(tableName, parentTable) {\n  var tableParentChangePropVal = gs.getProperty(\"glide.rollback.blacklist.TableParentChange.change\");\n  try {\n    gs.info(tableName + \" reparent to \" + parentTable);\n    var tableUtil = new TableUtils(tableName);\n    var parentTables = tableUtil.getTables();\n    if (parentTables.indexOf(parentTable) != -1) {\n      gs.info(tableName + \" is reparented to \" + parentTable);\n      reparentingDone = true;\n    } else {\n      gs.setProperty(\"glide.rollback.blacklist.TableParentChange.change\", false);\n      var tpc = new GlideTableParentChange(tableName);\n      reparentingDone = tpc.change(tableName, parentTable);\n      gs.info(\n        \"Completed \" + tableName + \" reparent to \" + parentTable + \"with status reparentingDone = \" + reparentingDone\n      );\n    }\n  } catch (exception) {\n    gs.info(\"Exception thrown during reparenting: \" + exception);\n    reparentingDone = false;\n  } finally {\n    gs.setProperty(\"glide.rollback.blacklist.TableParentChange.change\", tableParentChangePropVal);\n    gs.info(\"Completed setting up glide.rollback.blacklist.TableParentChange.change property\");\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Request Approval Helper/README.md",
    "content": "# Checks all RTIMs approval status for a request returns True if all RTIMs are approved or rejected\n\n# Example:\n\n```javascript\nvar result = new RequestApprovalHelper().areAllRTIMsApprovedOrRejected(\"sys_id_of_the_request\");\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Request Approval Helper/RequestApprovalHelper.js",
    "content": "var RequestApprovalHelper = Class.create();\n\nRequestApprovalHelper.prototype = {\n\tinitialize: function() {\n\t},\n\t\n\t///\n\t/// Checks all RTIMs approval status for a request\n\t/// Returns True if all RTIMs are approved or rejected\n\t///\n\tareAllRTIMsApprovedOrRejected : function(requestSysId) {\n\t\tvar result = false;\n\t\t\n\t\tvar rtimGR = new GlideRecord('sc_req_item');\n\t\t\n\t\trtimGR.addQuery('request', requestSysId);\n\t\trtimGR.query();\n\t\t\n\t\t// If ALL RTIMs are approved or rejected\n\t\tvar allRTIMsHaveDecisionAndAtleastOneApproved = this._CheckForAllRTIMsApprovedOrRejected(rtimGR);\n\t\t\n\t\tif (allRTIMsHaveDecisionAndAtleastOneApproved) {\n\t\t\tresult = true;\n\t\t}\n\t\t\n\t\treturn result;\n\t},\n\n\t\n\t///\n\t/// Update the request and mark the flag that all RTIMs are approved or rejected\n\t///\n\tupdateRequest : function(requestSysId){\n\t\t\n\t\tvar rec = new GlideRecord('sc_request');\n\t\trec.get(requestSysId);\n\n\t\tif(rec){\n\t\t\trec.u_all_rtims_are_approved_or_rejected = true;\n\t\t\trec.update();\n\t\t}\n\t\t\n\t},\n\t\n\t///\n\t/// Helper that checks all RTIMs have a decision i.e. Either approved or rejected e.g. not requested etc\n\t///\n\t_CheckForAllRTIMsApprovedOrRejected : function(rtimGR) {\n\t\t\n\t\tvar result = false;\n\t\tvar totalRecords = rtimGR.getRowCount();\n\t\tvar approvedCounter = 0;\n\t\tvar rejectedCounter = 0;\n\t\t\n\t\twhile (rtimGR.next()) {\n\t\t\t\n\t\t\tvar status = rtimGR.approval;\n\t\t\t\n\t\t\tif (status == 'approved') {\n\t\t\t\tapprovedCounter += 1;\n\t\t\t}\n\t\t\t\n\t\t\tif (status == 'rejected') {\n\t\t\t\trejectedCounter += 1;\n\t\t\t}\n\t\t}\n\t\t\n\t\t// At least one approved exist\n\t\tif (approvedCounter > 0) { \n\t\t\t\n\t\t\t// All records either approved or rejected\n\t\t\tif(approvedCounter + rejectedCounter == totalRecords){\n\t\t\t\tresult = true;\n\t\t\t}\n\t\t}\n\t\t\n\t\treturn result;\n\t},\n\t\n\ttype: 'RequestApprovalHelper'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RequestNotificationUtil/README.md",
    "content": "If a Request is rejected through Employee Center, the rejection notes get added to the RITM record rather than the Approval record. Therefore, the OOB reject notification does not contain the rejection comments. \nRequestNotificationUtil is used in reject_reason_new notification email script to pull RITM reject reason (if available)\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RequestNotificationUtil/RequestNotificationUtil.js",
    "content": "var RequestNotificationUtil = Class.create();\nRequestNotificationUtil.prototype = Object.extendsObject(RequestNotificationUtilSNC, {\n    initialize: function() {\n        RequestNotificationUtilSNC.prototype.initialize.apply(this, arguments);\n    },\n\n\t/**\n     * Get Reject comment from RITM - found that rejection notes added in EC will post\n\t * to RITM record rather than Approval record\n     * @param requestId - requestId\n     * @returns comment based on the state for last rejected\n     */\n    getRejectCommentRITM: function(requestId) {\n        var ritmRecord = new GlideRecord(\"sc_req_item\");\n        ritmRecord.addQuery('request', requestId);\n        ritmRecord.orderBy('sys_updated_on');\n        ritmRecord.setLimit(1);\n        ritmRecord.query();\n        while (ritmRecord.next()) {\n            var commentDesc = ritmRecord.comments.getJournalEntry(1).toString();\n            if (commentDesc.length > 0 && commentDesc.indexOf(\"Reason for rejection:\") > -1) {\n                var split = commentDesc.split(/\\(Additional comments\\)/gi);\n                if (split.length > 1) {\n                    // returns the first comment.\n                    var comment = split[split.length - 1];\n                    comment = comment.trim();\n                    var colonIndex = comment.indexOf(':');\n                    if (colonIndex != -1) {\n                        comment = comment.substr(colonIndex + 2, comment.length - 1);\n                    }\n                    comment = comment.replace(/\\n/g, '<br/>');\n                    return comment;\n                }\n            }\n        }\n    },\n\n    type: 'RequestNotificationUtil'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RestMessageUtils/README.md",
    "content": "RestMessageUtils\nA utility Script Include for simplifying REST API calls in ServiceNow using sn_ws.RESTMessageV2.\nFeatures\n\nSupports dynamic or named REST messages.\nEasily set headers, query parameters, and request body.\nSupports Basic Auth, Auth Profiles, and API Key authentication.\nOptional MID Server configuration.\nHandles variable substitution.\nCentralized error handling with gs.error() logging.\nDesigned for use in Script Includes, Business Rules, or Scripted REST APIs.\nLightweight and reusable for multiple integrations.\n\nObject Structure \n/*\nObjectStructure = {\n    endPoint: 'enpoint',\n    httpMethod: 'Add Method',\n    queryParams: {\n        key1: 'value',\n        key2: 'value'\n    },\n    requestHeaders: {\n    },\n    authType: 'CHOICES among [BasicCreds,BasicAuthProfile,APIKEY]',\n    authProfile: 'sysid of auth profile if authtype is selected as BasicAuthProfile',\n    userName: 'userName',\n    pasword: 'password',\n    midServer: 'midServer',\n\n}\n*/\n\nExample Usage\n/*\nvar obj = {\n    endPoint: 'https://instancename.service-now.com/api/now/table/problem',\n    queryParams: {\n        sysparm_query: 'active=true',\n        sysparm_limit: 2,\n        sysparm_fields: 'number,short_description'\n    },\n    httpMethod: 'POST',\n    authType: 'BasicCreds',\n    userName: 'admin',\n    password: gs.getProperty('dev204127.admin.password'),\n    requestHeaders: {\n        Accept: 'Application/JSON'\n    },\n    \n\n};\nvar resp = new RestMessageUtils(obj, 'Test RestMessage Utils', 'Default GET').execute();\n\ngs.print(resp.getBody())\n*/\n\n\n\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/RestMessageUtils/RestMessageUtils.js",
    "content": "var RestMessageUtils = Class.create();\nRestMessageUtils.prototype = {\n    initialize: function(rmObj, restMessage, restFunc) {\n        this.RM = (restFunc && restMessage) ? new sn_ws.RESTMessageV2(restMessage, restFunc) : new sn_ws.RESTMessageV2();\n        this.rmObj = rmObj;\n    },\n\n    checkAndGetKeyValue: function(obj, key) {\n        return obj.hasOwnProperty(key);\n\n    },\n\n    setQueryParams: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'queryParams'))\n            for (var i in this.rmObj.queryParams)\n                this.RM.setQueryParameter(i, this.rmObj.queryParams[i]);\n\n    },\n\n    variableSubs: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'variableInfo'))\n            for (var i in this.rmObj.variableInfo)\n                this.RM.setStringParameterNoEscape(i, this.rmObj.variableInfo[i]);\n    },\n\n    setAuth: function() {\n        var authType = this.rmObj.authType;\n\n        if (authType) {\n            if (authType == 'APIKEY')\n                return;\n            else if (authType == 'BasicCreds')\n                this.RM.setBasicAuth(this.rmObj.userName, this.rmObj.password);\n            else if (authType == 'BasicAuthProfile')\n                this.RM.setAuthenticationProfile('basic', this.rmObj.authProfile);\n\n        }\n\n    },\n\n    setRequestBody: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'requestBody'))\n            this.RM.setRequestBody(typeof this.rmObj.requestBody == 'object' ? JSON.stringify(this.rmObj.requestBody) : this.rmObj.requestBody);\n    },\n\n    setRequestHeaders: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'requestHeaders'))\n            for (var i in this.rmObj.requestHeaders)\n                this.RM.setRequestHeader(i, this.rmObj.requestHeaders[i]);\n    },\n\n    setMidServer: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'midServer'))\n            this.RM.setMidServer(this.rmObj.midServer);\n    },\n\n    setEndpoint: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'endPoint'))\n            this.RM.setEndpoint(this.rmObj.endPoint);\n    },\n    setHttpMethod: function() {\n        if (this.checkAndGetKeyValue(this.rmObj, 'httpMethod'))\n            this.RM.setHttpMethod(this.rmObj.httpMethod);\n    },\n\n    execute: function() {\n        try {\n            this.setHttpMethod();\n            this.setEndpoint();\n            this.setRequestHeaders();\n            this.setAuth();\n            this.setRequestBody();\n            this.setQueryParams();\n            this.variableSubs();\n            this.setMidServer();\n            return this.RM.execute();\n        } catch (err) {\n            gs.error('REST Message execution failed: ' + err);\n        }\n\n    },\n\n\n    type: 'RestMessageUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Retrieve Last Comment by Ticket/README.md",
    "content": "# Retrieve Last Comment By Ticket\n\nThis script returns the last comment that has been added on a ticket by the record id.\nThe function 'retrieveLastCommentByTicket' requires two arguments\n\n    Argument #1: sys_id - this is the sys_id of the record from which you need to pick the last comment\n    Argument #2: tableName - this is the table name of the record"
  },
  {
    "path": "Server-Side Components/Script Includes/Retrieve Last Comment by Ticket/RetrieveLastCommentByTicket.js",
    "content": "var RetrieveLastCommentByTicket = Class.create();\nRetrieveLastCommentByTicket.prototype = {\n    initialize: function() {},\n\t\n    retrieveLastCommentByTicket: function(sysId, tableName) {\n        var lastComment = new GlideRecord('sys_journal_field');\n\n        lastComment.addQuery('name', tableName);\n        lastComment.addQuery('element_id', sysId);\n        lastComment.addQuery('element', 'comments');\n        lastComment.orderByDesc('sys_created_on');\n        lastComment.setLimit(1);\n        lastComment.query();\n\n        if (lastComment.next())\n            return lastComment;\n\n        return null;\n    },\n\n    type: 'RetrieveLastCommentByTicket'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Return Object/README.md",
    "content": "## ReturnRecord Script Include\n- The script include is client callable\n- The returnRecordObject function allows for users to return a record values within the objects properties.\n- In call back function, in client script, parse the object that is return, then dot walk to access its propery values."
  },
  {
    "path": "Server-Side Components/Script Includes/Return Object/ReturnObject.js",
    "content": "var ReturnRecord = Class.create();\nReturnRecord.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    returnRecordObject: function() {\n        var tableName = this.getParameter('sysparm_tableName');\n\t\tvar recordID = this.getParameter('sysparm_recordID');\n\n        var getRecord = new GlideRecord(tableName);\n        getRecord.addQuery('sys_id', recordID);\n        getRecord.query();\n        var obj = {};\n\n        if (getRecord.next()) {\n            obj.short_description = getRecord.getValue('short_description');\n            obj.sys_id = getRecord.getValue('sys_id');\n        }\n\n        return JSON.stringify(obj);\n    }\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/Role Checker Util/README.md",
    "content": "# Script incude with 4 functions to check if users have a certain role.\nShould work server side as well as client side with the right prameters (with some caviats for the 4th function).\nPay attention to the defined sysparm names in the script if used on client side.\n\n## hasRoleID\nChecks if a user has a given role based on user and role sys_id passed in\nReturns a boolean value of true or false depending on whether or not the user has the role\n* @param1 - roleID: must be the sys_id of the role to check\n* @param2 -userID: must be the sys_id of the user to check\n* @returns: true/false\n\n## hasRoleEmail\nChecks if a user has a given role based on sys_id of role and email address of user passed in\nOnly makes sense if user has an account in ServiceNow, otherwise it won't have a role anyway :)\nReturns a boolean value of true or false depending on whether or not the user has the role\n* @param1 - roleID: must be the sys_id of the role to check\n* @param2 - email: must be an email address in string format\n* @returns: true/false\n\n## checkArray\nChecks if an array of users have a given role based on the role's name (note: not sys_id!)\n* @param1 - roleName: must be the name of the role to check as string\n* @param2 - array: must be an array that contains sys_ids, email addresses or a combination of both\n* @returns: - a comma separated list (stringified array) of names (can be changed) of those users who have the provided role. You can remove stringification to return an array (for server side scripts).\n\n## checkArrayGetObjects\nChecks if an array of users have a given role based on the role's name\n* @roleName: must be name of the role to check as string\n* @array: must be an array that contains sys_ids, email addresses or a combination of both.\n* @returns: an array of objects with some details of the users who have the provided role. You can extend the object per your requirements, by default it returns sys_id, name and email.\n\nNOTE: if you want to use this function in GlideAjax, you should stringify the array here, and convert it back (if you want) on the client side!\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Role Checker Util/checkUserRole.js",
    "content": "var checkUserRole = Class.create();\ncheckUserRole.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    hasRoleID: function(roleID, userID) {\n\t\t/** Checks if a user has a given role based on sys_id received\n\t\t* Returns a boolean value of true or false depending on whether or not the user has the role\n\t\t* @roleID: must be the sys_id of the role to check\n\t\t* @userID: must be the sys_id of the user to check\n\t\t*/\n    var role = this.getParameter('sysparm_roleID') || roleID;\n\tvar user = this.getParameter('sysparm_userID') || userID;\n\tvar rolesCheck = new GlideAggregate('sys_user_has_role');\n        rolesCheck.addEncodedQuery('role=' + role + '^user.sys_id=' + user);\n        rolesCheck.addAggregate('count');\n        rolesCheck.query();\n\tvar counter = 0;\n        if (rolesCheck.next())\n            counter = rolesCheck.getAggregate('count');\n        if (counter > 0)\n            return true;\n        else\n            return false;\n    },\n\n    hasRoleEmail: function(roleID, email) {\n\t\t/** Checks if a user has a given role based on sys_id of role and email address of user received\n\t\t* Only makes sense if user has an account in ServiceNow, otherwise it won't have a role anyway :)\n\t\t* Returns a boolean value of true or false depending on whether or not the user has the role\n\t\t* @roleID: must be the sys_id of the role to check\n\t\t* @email: must be an email address in string format\n\t\t*/\n    var role = this.getParameter('sysparm_roleID') || roleID;\n\tvar emailAddress = this.getParameter('sysparm_email') || email;\n\tvar rolesCheck = new GlideAggregate('sys_user_has_role');\n        rolesCheck.addEncodedQuery('role=' + role + '^user.email=' + emailAddress);\n        rolesCheck.addAggregate('count');\n        rolesCheck.query();\n\t var counter = 0;\n        if (rolesCheck.next())\n            counter = rolesCheck.getAggregate('count');\n        if (counter > 0)\n            return true;\n        else\n            return false;\n    },\n\n    checkArray: function(roleName, array) {\n\t\t/** Checks if an array of users have a given role based on the role's name\n\t\t* Returns a comma separated list of names (can be changed) of those users who have the provided role\n\t\t* @roleName: must be name of the role to check as string\n\t\t* @array: must be an array that contains sys_ids, email addresses or a combination of both.\n\t\t*/\n        var role = this.getParameter('sysparm_roleName') || roleName;\n        var arr = [];\n        arr = this.getParameter('sysparm_array') || array;\n        //          gs.info('checkArray method in checkUserRole script include started.  Received parameters rolename: ' + role + ' and array: ' + arr);\n        var users = arr.toString();\n        //         gs.info('These are the users: ' + users);\n\n        var userGR = new GlideRecord('sys_user');\n        userGR.addEncodedQuery('sys_idIN' + users + '^ORemailIN' + users + '^roles=' + role);\n        // \t\tgs.info('Encoded query: ' + userGR.getEncodedQuery());\n        userGR.query();\n        //         gs.info('userGR results: ' + userGR.getRowCount());\n        var roleUsers = [];\n        while (userGR.next()) {\n            roleUsers.push(userGR.getDisplayValue('name')); // change this line if you want to return a different value than name (e.g. sys_id, email, etc.)\n            //             gs.info('List of users with role updated: ' + roleUsers);\n        }\n        //         gs.info('Final list of users with role: ' + roleUsers);\n        return roleUsers.toString();\n    },\n\n    checkArrayGetObjects: function(roleName, array) {\n\t\t/** Checks if an array of users have a given role based on the role's name and array of users received\n\t\t* Returns an array of objects with some details of the users who have the provided role. You can extend the object per your requirements, by default it returns sys_id, name and email.\n\t\t* @roleName: must be name of the role to check as string\n\t\t* @array: must be an array that contains sys_ids, email addresses or a combination of both.\n\t\t*/\n        var role = this.getParameter('sysparm_roleName') || roleName;\n        var arr = [];\n        arr = this.getParameter('sysparm_array') || array;\n//         gs.info('checkArrayGetObjects method in checkUserRole script include started.  Received parameters rolename: ' + role + ' and array: ' + arr);\n        var users = arr.toString();\n//         gs.info('These are the users: ' + users);\n\n        var userGR = new GlideRecord('sys_user');\n        userGR.addEncodedQuery('sys_idIN' + users + '^ORemailIN' + users + '^roles=' + role);\n//         gs.info('Encoded query: ' + userGR.getEncodedQuery());\n        userGR.query();\n//         gs.info('userGR results: ' + userGR.getRowCount());\n        var roleUsers = []; // this is the array that will be returned, and will contain objects as defined below in userObj variable\n        var i = 0;\n        while (userGR.next()) {\n            var userObj = {};\n\t\t\t// feel free to add or remove elements to the object per your requirements, simply taking it from the GlideRecord object (userGR)\n            userObj.sys_id = userGR.getValue('sys_id');\n            userObj.name = userGR.getValue('name');\n            userObj.email = userGR.getValue('email');\n            roleUsers[i] = userObj;\n            i += 1;\n        }\n        return roleUsers; // array of objects - Note: if you want to use it client side, you need to stringify the array first here, and convert it back in your client script!\n    },\n\n    type: 'checkUserRole'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Root-Cause Predictor/README.md",
    "content": "## Incident Root-Cause Predictor\n\n### Overview\nThe **Incident Root-Cause Predictor** automatically classifies incoming Incidents into categories like *Network, Hardware, Application,* or *Security* based on keywords in the description.  \nThis helps in faster triaging and routing tickets to the right support teams.\n\n---\n\n## How It Works\n1. A user submits an Incident.\n2. A **Business Rule** runs on insert.\n3. It calls the **Script Include – `RootCausePredictor`**.\n4. The predictor scans the description and returns a probable root-cause category.\n\n---\n## Business Rule Script (How to call Script Include on BR)\n    var util = new global.RootCausePredictor();\n    \n(function executeRule(current) {\n    var util = new global.RootCausePredictor();\n    var cat = util.predict(current.description);\n    current.u_root_cause = cat;\n    current.work_notes = \"Auto-classified as: \" + cat.toUpperCase();\n})(current);\n\n--------------\n## Sample Input and Output\nInput : A user logs a ticket:\n“Wi-Fi keeps disconnecting every few minutes.”\n\nThe Script Include scans for the word “Wi-Fi”, which matches the Network keyword list.\nOutPut: \n\nSystem automatically sets field u_root_cause = \"Network\"\nWork note added: “Auto-classified as: NETWORK”\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Root-Cause Predictor/incident.js",
    "content": "//Scenario : Automatically detect the root cause category of an Incident based on keywords in the short description or description.\n\n// Script Include\nvar RootCausePredictor = Class.create();\nRootCausePredictor.prototype = {\n    predict: function(text) {\n        var data = {\n            network: ['router', 'switch', 'wifi', 'dns'],\n            hardware: ['laptop', 'keyboard', 'printer', 'battery'],\n            application: ['login', 'error', 'bug', 'page'],\n            security: ['virus', 'attack', 'unauthorized']\n        };\n        text = text.toLowerCase();\n        for (var cat in data) {\n            for (var i = 0; i < data[cat].length; i++) {\n                if (text.includes(data[cat][i])) return cat;\n            }\n        }\n        return 'general';\n    },\n    type: 'RootCausePredictor'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SCIM Custom Mapping Handler/README.md",
    "content": "This is a script include to handle custom mapping, covering a specific case where the SCIM client is using the entitlement attribute to store the user-group-memberships. \n\nUsage: the script must be invoked from the \"SCIM User\" ETL definition (installed with the SCIM v2 plugin). \n\nThe main function accepts an array with group names and a user sys_id:\n\nvar handler = new SCIMCustomMappingHandler(true);\n\nvar ctx = sn_auth.SCIM2Util.getScimProviderCustomizationContext();\n\nvar entitlements = ctx.scimResource.entitlements;\n\nvar entitlementsList = [];\nfor (entitlement in entitlements){\nentitlementsList.push(entitlements[entitlement].value);\n}\n\nhandler.handleGroupMemberships(entitlementsList, source.id);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SCIM Custom Mapping Handler/SCIMCustomMappingHandler.js",
    "content": "var SCIMCustomMappingHandler = Class.create();\nSCIMCustomMappingHandler.prototype = {\n    initialize: function(debug) {\n        if (debug) {\n            this.debug = true;\n        }\n    },\n\n    handleGroupMemberships: function(entitlementsList, userSysID){\n\n        this.removeMembershipsNotInEntitlements(entitlementsList, userSysID);\n        this.addAllMembershipsInEntitlements(entitlementsList, userSysID);\n\n    },\n\n    addAllMembershipsInEntitlements: function(entitlementsList, userSysID){\n\n        for (entitlement in entitlementsList){\n            var groupSysID = '';\n            var groupGR = new GlideRecord('sys_user_group');\n            if(groupGR.get('name', entitlementsList[entitlement])){\n                \n                if (!this._isMemberAlready(groupGR.getUniqueValue(), userSysID)){\n                    groupSysID = groupGR.getUniqueValue();\n                }\n\n            }else{\n                groupSysID = this._createNewGroup(entitlementsList[entitlement]); \n            }\n\n            if(groupSysID && groupSysID.length == 32){\n                this.createGroupMembership(groupSysID, userSysID);\n            }\n        }\n    },\n\n    removeMembershipsNotInEntitlements: function(entitlementsList, userSysID){\n\n        var membershipsToBeDeletedGR = new GlideRecord('sys_user_grmember');\n        membershipsToBeDeletedGR.addQuery('user', userSysID);\n        membershipsToBeDeletedGR.addQuery('group.name', \"NOT IN\", entitlementsList); \n        membershipsToBeDeletedGR.query();\n\n        membershipsToBeDeletedGR.deleteMultiple();\n    },  \n    \n    createGroupMembership: function(groupSysID, userSysID) {\n        var groupMembershipGR = new GlideRecord('sys_user_grmember');\n        groupMembershipGR.initialize();\n        groupMembershipGR.setValue('user', userSysID);\n        groupMembershipGR.setValue('group', groupSysID);\n        var membershipSysID = groupMembershipGR.insert();\n\n        if(this.debug){\n            if (membershipSysID){\n                gs.info ('Group membership created for User: ' + this._getUserDisplayValueAndEmail(userSysID) + ' and Group: ' + this._getGroupDisplayValue(groupSysID), this.type);\n            }\n        }\n    },\n\n    deleteGroupMembership: function(groupSysID, userSysID) {\n\n        var groupMembershipGR = new GlideRecord('sys_user_grmember');\n        groupMembershipGR.addQuery('group', groupSysID);\n        groupMembershipGR.addQuery('user', userSysID);\n        groupMembershipGR.query();\n\n        if (groupMembershipGR.next()) {\n            groupMembershipGR.deleteRecord();\n                \n            if (this.debug) {\n                gs.info(\"User: \" + this._getUserDisplayValueAndEmail(userSysID) + ' has been removed from ' + groupMembershipGR.group.getDisplayValue(), this.type);\n            }\n        }\n    },\n\n    _getUserDisplayValueAndEmail: function(userSysID) {\n        var userGR = new GlideRecord('sys_user');\n        if (userGR.get(userSysID)) {\n            var userInfo = userGR.getDisplayValue() + ' (' + userGR.getValue('email') + ')';\n            return userInfo;\n        }else{\n            gs.info(\"User with sys_id: \" + userSysID + ' does not exist.', this.type);\n        }\n    },\n\n    _getGroupDisplayValue: function(groupSysID) {\n        var groupGR = new GlideRecord('sys_user_group');\n        if (groupGR.get(groupSysID)) {\n            return groupGR.getDisplayValue();\n        }else{\n            gs.info(\"Group with sys_id: \" + groupSysID + ' does not exist.', this.type);\n        }\n    },\n\n    \n    _createNewGroup: function(groupName){\n        if (this.debug){\n            gs.info('_createNewGroup invoked with groupName = ' + groupName, this.type);\n        }\n        \n        var newGroupGR = new GlideRecord('sys_user_group');\n        newGroupGR.initialize();\n        newGroupGR.setValue('name', groupName);\n        \n        return newGroupGR.insert();\n    \n    },\n\n    _isMemberAlready: function(groupSysID, userSysID){\n        var membershipGR = new GlideRecord('sys_user_grmember');\n        membershipGR.addQuery('group', groupSysID);\n        membershipGR.addQuery('user', userSysID);\n        membershipGR.query();\n\n        if (membershipGR.hasNext()){\n            return true;\n        }\n        return false;\n    },\n\n    type: 'SCIMCustomMappingHandler'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SCIM Payload Generator/GenerateSCIMPayload.js",
    "content": "var GenerateSCIMPayload = Class.create();\nGenerateSCIMPayload.prototype = {\n    initialize: function() {\n    },\n\n    // Function to generate a random string for new group names\n    generateRandomString: function(length) {\n        var characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';\n        var result = '';\n        for (var i = 0; i < length; i++) {\n            result += characters.charAt(Math.floor(Math.random() * characters.length));\n        }\n        return \"test group \" + result;\n    },\n\n    // Function to retrieve the groups where the user is a member\n    getUserGroups: function(userSysId) {\n        var groupNames = [];\n        var grGroupMember = new GlideRecord('sys_user_grmember'); // Group Member table\n        grGroupMember.addQuery('user', userSysId); // Get groups of the user\n        grGroupMember.query();\n        \n        while (grGroupMember.next()) {\n            var groupName = grGroupMember.group.getDisplayValue(); // Get group name\n            groupNames.push({\n                name: groupName\n            });\n        }\n        return groupNames;\n    },\n\n    // Function to retrieve groups where the user is NOT a member\n    getGroupsNotMemberOf: function(userSysId) {\n        var groupNames = [];\n        var grGroup = new GlideRecord('sys_user_group'); // Group table\n\n        // Subquery to find groups the user is a member of\n        var subquery = new GlideRecord('sys_user_grmember');\n        subquery.addQuery('user', userSysId);\n        subquery.query();\n\n        var groupIds = [];\n        while (subquery.next()) {\n            groupIds.push(subquery.group.sys_id.toString());\n        }\n\n        // Query for groups where the user is NOT a member\n        grGroup.addQuery('sys_id', 'NOT IN', groupIds);\n        grGroup.query();\n\n        while (grGroup.next()) {\n            groupNames.push({\n                name: grGroup.name.toString() // Only store the group name\n            });\n        }\n\n        return groupNames;\n    },\n\n    // Function to generate entitlements for current user groups, excluding a set number\n    generateEntitlementsFromCurrentGroups: function(userSysId, groupsToRemove) {\n        var currentGroups = this.getUserGroups(userSysId);\n        var countToKeep = Math.max(0, currentGroups.length - groupsToRemove); // Ensure non-negative value\n        var keptGroups = currentGroups.slice(0, countToKeep); // Keep the first groups, up to the count\n\n        var entitlements = [];\n        for (var i = 0; i < keptGroups.length; i++) {\n            entitlements.push({\n                \"value\": keptGroups[i].name // Add kept group names to entitlements\n            });\n        }\n        return entitlements;\n    },\n\n    // Function to generate entitlements for groups the user will \"join\"\n    generateEntitlementsForNewGroups: function(userSysId, groupsToAdd) {\n        var availableGroups = this.getGroupsNotMemberOf(userSysId);\n        var entitlements = [];\n\n        for (var i = 0; i < groupsToAdd; i++) {\n            if (availableGroups.length > 0) {\n                var randomIndex = Math.floor(Math.random() * availableGroups.length);\n                var selectedGroup = availableGroups.splice(randomIndex, 1)[0];\n                entitlements.push({\n                    \"value\": selectedGroup.name // Add selected group names to entitlements\n                });\n            }\n        }\n\n        return entitlements;\n    },\n\n    // Main function to generate entitlements\n    generateEntitlements: function(userSysId, groupsToRemove, groupsToAdd, newGroupsToCreate) {\n        // Initialize the object\n        var userObj = {\n            \"schemas\": [\n                \"urn:ietf:params:scim:schemas:extension:servicenow:2.0:User\",\n                \"urn:ietf:params:scim:schemas:core:2.0:User\",\n                \"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\"\n            ],\n            \"id\": userSysId,\n            \"meta\": {\n                \"resourceType\": \"User\",\n                \"created\": \"2006-07-11T21:16:15Z\",\n                \"lastModified\": new Date().toISOString(), // Current date for lastModified\n                \"location\": \"https://<instance-name>.service-now.com/api/now/scim/Users/\" + userSysId //to be updated with your instance name\n            },\n            \"userName\": \"jvittolo\", // Replace with actual user name if needed\n            \"name\": {\n                \"familyName\": \"Vittolo\",\n                \"givenName\": \"Jamessss\"\n            },\n            \"displayName\": \"Jamessss Vittolo\",\n            \"title\": \"VP, Client Services\",\n            \"active\": true,\n            \"emails\": [\n                {\n                    \"value\": \"jvittolo@example.com\",\n                    \"type\": \"work\"\n                }\n            ],\n            \"entitlements\": []\n        };\n\n        // Step 1: Generate entitlements for existing groups (excluding some)\n        var currentGroupEntitlements = this.generateEntitlementsFromCurrentGroups(userSysId, groupsToRemove);\n        userObj.entitlements = userObj.entitlements.concat(currentGroupEntitlements);\n\n        // Step 2: Generate entitlements for new groups the user will \"join\"\n        var newGroupEntitlements = this.generateEntitlementsForNewGroups(userSysId, groupsToAdd);\n        userObj.entitlements = userObj.entitlements.concat(newGroupEntitlements);\n\n        // Step 3: Create new groups (non-existing) and add to entitlements\n        for (var j = 0; j < newGroupsToCreate; j++) {\n            var newGroupName = this.generateRandomString(12);\n            userObj.entitlements.push({\n                \"value\": newGroupName // Add new group names directly to entitlements\n            });\n        }\n\n        // Return the modified object\n        return userObj;\n    },\n\n    // Function to simulate removal of all group memberships\n    removeAllGroupMemberships: function(userSysId) {\n        // Initialize the object with all original properties\n        var userObj = {\n            \"schemas\": [\n                \"urn:ietf:params:scim:schemas:extension:servicenow:2.0:User\",\n                \"urn:ietf:params:scim:schemas:core:2.0:User\",\n                \"urn:ietf:params:scim:schemas:extension:enterprise:2.0:User\"\n            ],\n            \"id\": userSysId,\n            \"meta\": {\n                \"resourceType\": \"User\",\n                \"created\": \"2006-07-11T21:16:15Z\",\n                \"lastModified\": new Date().toISOString(), // Current date for lastModified\n                \"location\": \"https://<instance-name>.service-now.com/api/now/scim/Users/\" + userSysId\n            },\n            \"userName\": \"jvittolo\", // Replace with actual user name if needed\n            \"name\": {\n                \"familyName\": \"Vittolo\",\n                \"givenName\": \"Jamessss\"\n            },\n            \"displayName\": \"Jamessss Vittolo\",\n            \"title\": \"VP, Client Services\",\n            \"active\": true,\n            \"emails\": [\n                {\n                    \"value\": \"jvittolo@example.com\",\n                    \"type\": \"work\"\n                }\n            ],\n            \"entitlements\": [] // Empty entitlements array\n        };\n\n        return userObj; // Return the full object with empty entitlements\n    },\n\n    type: 'GenerateSCIMPayload'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SCIM Payload Generator/README.md",
    "content": "A script include to generate payload for testing SCIM-based integration.\n\nThe script covers as specific case where user is added/removed to user groups based on the values in the 'entitlements' object of the SCIM payload.\n\nThe main function accepts 3 parameters:\n\ngroupsToRemove - will check all groups of which the user is currently member and will randomly remove the membership of groups equal to the number passed to the parameter\ngroupsToAdd - will check all groups to which the is not currently a member and will randomly create membershis for groups equal to the number passed to the parameter\nnewGroupsToCreate - will create new groups and add the user to them. Group names are concatenation of a prefix and randomly generated string.\nThe end result of the function is a JSON object that can be directly passed as a payload while testing via REST API explorer or Postman.\n\nUsage: var groupsToRemove = 2; var groupsToAdd = 1; var newGroupsToCreate = 3;\n\nvar generator = new GenerateSCIMPayload(); var scimPayload = generator.generateEntitlements(jamesVittoloSysID, groupsToRemove, groupsToAdd, newGroupsToCreate); gs.info(JSON.stringify(scimPayload));\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SQLInjectionChecker/README.md",
    "content": " SQLInjectionValidator\n \n Script Include for detecting potential SQL injection attempts in user-provided strings.\n \n Purpose:\n Validates user input against a comprehensive list of SQL injection patterns including\n keywords, operators, comment syntax, and common attack vectors.\n \n Usage:\n var validator = new SQLInjectionValidator();\n var isSafe = validator.isSafeFromSQLInjection(userInput);\n \n Performance Considerations:\n - Uses efficient string methods (toLowerCase, includes) for keyword detection\n - Regex patterns are pre-compiled for performance\n - Early exit on first match to minimize processing\n - Suitable for high-volume input validation\n \n Security Note:\n This function provides pattern-based detection and should be used as ONE LAYER\n of defense. Always use parameterized queries and prepared statements in your\n database interactions as the PRIMARY defense against SQL injection."
  },
  {
    "path": "Server-Side Components/Script Includes/SQLInjectionChecker/SQLInjectionChecker.js",
    "content": "/**\n * SQLInjectionValidator\n * \n * Script Include for detecting potential SQL injection attempts in user-provided strings.\n * \n * Purpose:\n * Validates user input against a comprehensive list of SQL injection patterns including\n * keywords, operators, comment syntax, and common attack vectors.\n * \n * Usage:\n * var validator = new SQLInjectionValidator();\n * var isSafe = validator.isSafeFromSQLInjection(userInput);\n * \n * Performance Considerations:\n * - Uses efficient string methods (toLowerCase, includes) for keyword detection\n * - Regex patterns are pre-compiled for performance\n * - Early exit on first match to minimize processing\n * - Suitable for high-volume input validation\n * \n * Security Note:\n * This function provides pattern-based detection and should be used as ONE LAYER\n * of defense. Always use parameterized queries and prepared statements in your\n * database interactions as the PRIMARY defense against SQL injection.\n * \n * @class SQLInjectionValidator\n */\nvar SQLInjectionValidator = Class.create();\n\nSQLInjectionValidator.prototype = {\n    \n    /**\n     * Checks if a string appears safe from SQL injection attempts\n     * \n     * @param {string} inputString - The user-provided string to validate\n     * @returns {boolean} true if string appears safe, false if suspicious patterns detected\n     */\n    isSafeFromSQLInjection: function(inputString) {\n        // Input validation: ensure we have a string\n        if (typeof inputString !== 'string') {\n            gs.debug('SQLInjectionValidator: Input is not a string, converting to string');\n            inputString = String(inputString);\n        }\n        \n        // Empty strings are considered safe\n        if (inputString.length === 0) {\n            return true;\n        }\n        \n        // Convert to lowercase for case-insensitive comparison\n        var lowerInput = inputString.toLowerCase();\n        \n        // ========== CHECK 1: SQL Keywords ==========\n        // Detects common SQL commands that could indicate injection attempts\n        var sqlKeywords = [\n            'select', 'insert', 'update', 'delete', 'drop', 'create', 'alter',\n            'truncate', 'exec', 'execute', 'union', 'declare', 'cast', 'convert'\n        ];\n        \n        for (var i = 0; i < sqlKeywords.length; i++) {\n            // Use word boundary regex to avoid false positives (e.g., \"selected\" vs \"select\")\n            var keywordRegex = new RegExp('\\\\b' + sqlKeywords[i] + '\\\\b', 'i');\n            if (keywordRegex.test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected SQL keyword: ' + sqlKeywords[i]);\n                return false;\n            }\n        }\n        \n        // ========== CHECK 2: SQL Clauses ==========\n        // Detects FROM, WHERE, ORDER BY, GROUP BY, HAVING clauses\n        var sqlClauses = [\n            'from', 'where', 'order by', 'group by', 'having', 'join', 'inner join',\n            'left join', 'right join', 'cross join', 'on'\n        ];\n        \n        for (var j = 0; j < sqlClauses.length; j++) {\n            var clauseRegex = new RegExp('\\\\b' + sqlClauses[j] + '\\\\b', 'i');\n            if (clauseRegex.test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected SQL clause: ' + sqlClauses[j]);\n                return false;\n            }\n        }\n        \n        // ========== CHECK 3: Comment Patterns ==========\n        // Detects SQL comment syntax: --, /* */, ;\n        var commentPatterns = [\n            /--\\s/,           // SQL line comment: -- followed by space\n            /\\/\\*/,            // SQL block comment start: /*\n            /\\*\\//,            // SQL block comment end: */\n            /;\\s*$/,           // Semicolon at end (statement terminator)\n            /;\\s*select/i,     // Semicolon followed by select\n            /;\\s*insert/i,     // Semicolon followed by insert\n            /;\\s*update/i,     // Semicolon followed by update\n            /;\\s*delete/i      // Semicolon followed by delete\n        ];\n        \n        for (var k = 0; k < commentPatterns.length; k++) {\n            if (commentPatterns[k].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected comment pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 4: Boolean-based Injection Patterns ==========\n        // Detects common boolean-based SQL injection: OR 1=1, AND 1=1, etc.\n        var booleanPatterns = [\n            /or\\s+1\\s*=\\s*1/i,           // OR 1=1\n            /and\\s+1\\s*=\\s*1/i,          // AND 1=1\n            /or\\s+'1'\\s*=\\s*'1'/i,       // OR '1'='1'\n            /and\\s+'1'\\s*=\\s*'1'/i,      // AND '1'='1'\n            /or\\s+true/i,                // OR TRUE\n            /and\\s+true/i,               // AND TRUE\n            /or\\s+1/i,                   // OR 1 (generic)\n            /and\\s+1/i,                  // AND 1 (generic)\n            /or\\s+''/i,                  // OR ''\n            /and\\s+''/i                  // AND ''\n        ];\n        \n        for (var l = 0; l < booleanPatterns.length; l++) {\n            if (booleanPatterns[l].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected boolean-based injection pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 5: Comparison Operators with Suspicious Values ==========\n        // Detects patterns like: = 1, != 0, <> 0, > 0, < 1\n        var comparisonPatterns = [\n            /=\\s*1\\s*$/i,                // Ends with = 1\n            /!=\\s*0/i,                   // != 0\n            /<>\\s*0/i,                   // <> 0\n            />\\s*0/i,                    // > 0\n            /<\\s*1/i                     // < 1\n        ];\n        \n        for (var m = 0; m < comparisonPatterns.length; m++) {\n            if (comparisonPatterns[m].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected suspicious comparison pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 6: SQL Functions ==========\n        // Detects SQL functions commonly used in injection: CHAR, ASCII, SUBSTRING, WAITFOR, SLEEP, BENCHMARK\n        var sqlFunctions = [\n            'char(', 'ascii(', 'substring(', 'waitfor(', 'sleep(', 'benchmark(',\n            'concat(', 'length(', 'mid(', 'instr(', 'load_file(', 'into outfile'\n        ];\n        \n        for (var n = 0; n < sqlFunctions.length; n++) {\n            if (lowerInput.includes(sqlFunctions[n])) {\n                gs.debug('SQLInjectionValidator: Detected SQL function: ' + sqlFunctions[n]);\n                return false;\n            }\n        }\n        \n        // ========== CHECK 7: System Variables and Commands ==========\n        // Detects: @@VERSION, xp_cmdshell, sp_executesql, etc.\n        var systemPatterns = [\n            /@@version/i,                // SQL Server version\n            /@@servername/i,             // SQL Server name\n            /xp_cmdshell/i,              // SQL Server command shell\n            /sp_executesql/i,            // SQL Server execute SQL\n            /information_schema/i,       // Database schema enumeration\n            /mysql\\.user/i,              // MySQL user table\n            /pg_catalog/i                // PostgreSQL catalog\n        ];\n        \n        for (var o = 0; o < systemPatterns.length; o++) {\n            if (systemPatterns[o].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected system variable or command');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 8: Quote Escaping and Concatenation ==========\n        // Detects: '', \"\", \\', \\\", ||, +, CONCAT\n        var escapePatterns = [\n            /''/,                        // Double single quote (escape)\n            /\"\"/,                        // Double double quote (escape)\n            /\\\\'/ ,                       // Backslash single quote\n            /\\\\\"/,                       // Backslash double quote\n            /\\|\\|/,                      // Oracle/PostgreSQL concatenation\n            /\\+\\\\s*'/i                   // SQL Server concatenation\n        ];\n        \n        for (var p = 0; p < escapePatterns.length; p++) {\n            if (escapePatterns[p].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected quote escaping or concatenation pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 9: UNION-based Injection ==========\n        // Detects UNION SELECT patterns\n        var unionPatterns = [\n            /union\\s+select/i,           // UNION SELECT\n            /union\\s+all\\s+select/i,     // UNION ALL SELECT\n            /union\\s+distinct\\s+select/i // UNION DISTINCT SELECT\n        ];\n        \n        for (var q = 0; q < unionPatterns.length; q++) {\n            if (unionPatterns[q].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected UNION-based injection pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 10: Time-based Blind Injection ==========\n        // Detects: SLEEP, BENCHMARK, WAITFOR DELAY, pg_sleep\n        var timeBasedPatterns = [\n            /sleep\\s*\\(/i,               // SLEEP function\n            /benchmark\\s*\\(/i,           // BENCHMARK function\n            /waitfor\\s+delay/i,          // WAITFOR DELAY\n            /pg_sleep\\s*\\(/i             // PostgreSQL sleep\n        ];\n        \n        for (var r = 0; r < timeBasedPatterns.length; r++) {\n            if (timeBasedPatterns[r].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected time-based blind injection pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 11: Stacked Queries ==========\n        // Detects multiple statements separated by semicolons\n        var stackedQueryPattern = /;\\s*[a-z]/i;\n        if (stackedQueryPattern.test(inputString)) {\n            gs.debug('SQLInjectionValidator: Detected stacked query pattern');\n            return false;\n        }\n        \n        // ========== CHECK 12: Hex Encoding ==========\n        // Detects: 0x (hex encoding used to bypass filters)\n        var hexPattern = /0x[0-9a-f]+/i;\n        if (hexPattern.test(inputString)) {\n            gs.debug('SQLInjectionValidator: Detected hex encoding pattern');\n            return false;\n        }\n        \n        // ========== CHECK 13: Comment-based Injection ==========\n        // Detects: #, --, /*, */ patterns\n        var commentInjectionPatterns = [\n            /#/,                         // MySQL comment\n            /--/,                        // SQL comment\n            /\\/\\*/,                      // Block comment start\n            /\\*\\//                       // Block comment end\n        ];\n        \n        for (var s = 0; s < commentInjectionPatterns.length; s++) {\n            if (commentInjectionPatterns[s].test(inputString)) {\n                gs.debug('SQLInjectionValidator: Detected comment-based injection pattern');\n                return false;\n            }\n        }\n        \n        // ========== CHECK 14: Wildcard Patterns ==========\n        // Detects: %, _ (SQL wildcards that could be used in LIKE injection)\n        // Note: This is a loose check - legitimate data may contain these\n        // Only flag if combined with suspicious patterns\n        var wildcardPattern = /[%_]\\s*(or|and|union|select)/i;\n        if (wildcardPattern.test(inputString)) {\n            gs.debug('SQLInjectionValidator: Detected wildcard with SQL keyword pattern');\n            return false;\n        }\n        \n        // ========== CHECK 15: Parentheses Nesting ==========\n        // Detects excessive parentheses which could indicate subquery injection\n        var openParens = (inputString.match(/\\(/g) || []).length;\n        var closeParens = (inputString.match(/\\)/g) || []).length;\n        \n        // Flag if more than 3 levels of nesting or mismatched parentheses\n        if (openParens > 5 || closeParens > 5 || openParens !== closeParens) {\n            gs.debug('SQLInjectionValidator: Detected excessive or mismatched parentheses');\n            return false;\n        }\n        \n        // ========== CHECK 16: Case Sensitivity Bypass ==========\n        // Detects: sElEcT, UnIoN, etc. (mixed case SQL keywords)\n        var mixedCasePattern = /[a-z]{2,}\\s+[a-z]{2,}/i;\n        if (mixedCasePattern.test(inputString)) {\n            // Check if it matches known SQL keywords in mixed case\n            var mixedCaseKeywords = [\n                /s[eE][lL][eE][cC][tT]/,\n                /u[nN][iI][oO][nN]/,\n                /[iI][nN][sS][eE][rR][tT]/,\n                /[uU][pP][dD][aA][tT][eE]/,\n                /[dD][eE][lL][eE][tT][eE]/\n            ];\n            \n            for (var t = 0; t < mixedCaseKeywords.length; t++) {\n                if (mixedCaseKeywords[t].test(inputString)) {\n                    gs.debug('SQLInjectionValidator: Detected mixed-case SQL keyword');\n                    return false;\n                }\n            }\n        }\n        \n        // ========== All checks passed ==========\n        gs.debug('SQLInjectionValidator: Input passed all SQL injection checks');\n        return true;\n    },\n    \n    type: 'SQLInjectionValidator'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/SRAPIUtil/README.md",
    "content": "#SRAPIUtil\n\nUtility class for common methods when implementing SRAPIs. Also includes examples of SNDocs and Revealing Module script include pattern with true private methods\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SRAPIUtil/SRAPIUtil.js",
    "content": "var SRAPIUtil = (function() {\n\n    /**SNDOC\n    @name updateRecord\n    @description Update a record on provided table with provided field/value pairs\n    @param {String} [table] - Table the record is on\n\t@param {String} [id] - sys_id or number of record\n\t@param {Object} [fields] - Fields to be updated and their value eg. {\"state\": \"1\"}\n\t@throws error if no record is found or an invalid field or choice value is used\n\t@return {Object} {updatedFields, unchangedFields, record}\n\t@function REIChangeUtil~updateRecord\n\t*/\n    function updateRecord(table, id, fields) {\n        var response = {};\n        var unchangedFields = [];\n        var updatedFields = [];\n\n        var recordGR = new GlideRecordSecure(table);\n        var fieldToQuery;\n        _isSysID(id) ? fieldToQuery = 'sys_id' : fieldToQuery = 'number';\n        if (!recordGR.get(fieldToQuery, id)) {\n            throw 'Could not find record, please provide a valid number or sys_id';\n        }\n        for (var field in fields) {\n            if (!recordGR.isValidField(field)) {\n                throw field + ' is not a valid field on the ' + table + ' table';\n            }\n            if (!recordGR.getElement(field).canWrite()) {\n                unchangedFields.push(field);\n                continue;\n            }\n            var choiceList = _getChoiceList(field, table);\n            if (_getChoiceList(field) && _isChoiceList(field)) {\n                if (choiceList.indexOf(fields[field]) < 0) {\n                    throw fields[field] + ' is not a valid value for ' + field + '. Valid values: ' + choiceList;\n                }\n            }\n            recordGR.setValue(field, fields[field]);\n            var obj = {};\n            obj[field] = fields[field];\n            updatedFields.push(obj);\n        }\n        recordGR.update();\n        response.updatedFields = updatedFields;\n        if (unchangedFields.length > 0) {\n            response.unchangedFields = unchangedFields;\n        }\n        response[table] = _convertToJSON(recordGR);\n        return response;\n    }\n\n    /**SNDOC\n\t @name getRecord\n     @description Get a GlideRecord\n     @param {String}  [table] - Table the record is on\n\t @param {String} [id] - sys_id or number of record to be retrieved\n\t @return {Object} GlideRecord represented in JSON\n\t*/\n    function getRecord(table, id) {\n        var field;\n        _isSysID(id) ? field = 'sys_id' : field = 'number';\n        var recordGRS = new GlideRecordSecure(table);\n        if (recordGRS.get(field, id)) {\n            var record = _convertToJSON(recordGRS);\n            return record;\n        }\n        throw \"Could not find record. Please provide a valid table and sys_id\";\n    }\n\n    /**SNDOC\n     @name queryRecords\n     @description Query a table\n     @param {String} [table] - table the records are on\n\t @param {Object} [queryParams] - Query parameters of a REST message containing fields/values to query for\n\t @return {Array} Collection of GlideRecords represented in JSON\n\t*/\n    function queryRecords(table, queryParams) {\n        var records = [];\n        var recordGRS = new GlideRecordSecure(table);\n        recordGRS.setLimit(100);\n        for (var param in queryParams) {\n            if (param == 'api') {\n                continue;\n            }\n            if (param == 'limit') {\n                recordGRS.setLimit(queryParams[param]);\n                continue;\n            }\n            if (!recordGRS.isValidField(param)) {\n                throw param + ' is not a valid field on ' + table;\n            }\n            var fieldType = recordGRS.getElement(param).getED().getInternalType();\n            if (fieldType == 'reference') {\n                var refTable = recordGRS.getElement(param).getReferenceTable();\n                var displayField = _getTableDisplayValue(refTable);\n            }\n            recordGRS.addQuery(param, queryParams[param]).addOrCondition(param + '.' + displayField, queryParams[param]);\n        }\n        recordGRS.query();\n        while (recordGRS.next()) {\n            var obj = _convertToJSON(recordGRS);\n            records.push(obj);\n        }\n        return records;\n    }\n\n    /**SNDOC\n     @name _getChoiceList\n     @description Get choices available for a field\n     @private\n     @param {String} [field] - Name of field to check\n     @param {String} [table] - Name of the field the table is on\n     @return {Array} Collection of choices available on the field\n    */\n    function _getChoiceList(field, table) {\n        var choices = [];\n        var choiceGR = new GlideRecord('sys_choice');\n        choiceGR.addQuery('element', field);\n        choiceGR.addQuery('name', table);\n        choiceGR.addQuery('inactive', false);\n        choiceGR.query();\n        if (!choiceGR.hasNext()) {\n            return null;\n        }\n        while (choiceGR.next()) {\n            choices.push(choiceGR.getValue('value'));\n        }\n        return choices;\n    }\n\n    /**SNDOC\n     @name _isChoiceList\n     @description Check if a field is choice list by checking if the field's choice value is dropdown, dropdown with none, or suggestion\n     @private\n\t @param {String} [field] - Name of field to check\n\t @return {Boolean} True if the provided field is a choice list, false if not\n\t*/\n    function _isChoiceList(field) {\n        var dictionaryGR = new GlideRecord('sys_dictionary');\n        dictionaryGR.addQuery('name', 'task');\n        dictionaryGR.addQuery('element', field);\n        dictionaryGR.addQuery('active', true);\n        dictionaryGR.query();\n        if (dictionaryGR.next()) {\n            if (dictionaryGR.getValue('choice') == 2 || dictionaryGR.getValue('choice') == 3 || dictionaryGR.getValue('choice') == 1) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    /**SNDOC\n     @name _convertToJSON\n     @description Convert the provided GlideRecord to a JSON object\n     @private \n\t @param {GlideRecord} [GlideRecord] - GlideRecord to convert\n\t @return {Object} GlideRecord represented as JSON\n\t*/\n    function _convertToJSON(glideRecord) {\n        var obj = {};\n        for (var field in glideRecord) {\n            if (glideRecord.getValue(field)) {\n                obj[field] = glideRecord.getDisplayValue(field);\n            }\n        }\n        return obj;\n    }\n\n    /**SNDOC\n     @name _isSysID\n     @description Check if provided text is a sys_id (32 char alphanumeric)\n     @private \n\t @param {String} [text] - Text to check\n\t @return {Boolean} True if the provided string is a sys_id, false if not\n\t*/\n    function _isSysID(text) {\n        var regexp = /[0-9a-f]{32}/;\n        if (text.match(regexp)) {\n            return true;\n        } else {\n            return false;\n        }\n    }\n\n    /**SNDOC\n     @name _getTableDisplayValue\n     @description Get the display field for the provided table\n     @private\n\t @param {String} [table] - Table to get the display field of\n\t @return {String} Name of the display field\n\t*/\n    function _getTableDisplayValue(table) {\n        var dictionaryGR = new GlideRecord('sys_dictionary');\n        dictionaryGR.addQuery('name', table);\n        dictionaryGR.addQuery('display', true);\n        dictionaryGR.query();\n        if (dictionaryGR.next()) {\n            return dictionaryGR.getValue('element');\n        } else {\n            return 'name';\n        }\n    }\n\n    return {\n        'updateRecord': updateRecord,\n        'getRecord': getRecord,\n        'queryRecords': queryRecords\n    };\n})();\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Safe Bulk Update Runner/README.md",
    "content": "# Safe Bulk Update Runner (auto-throttled)\n\n## Use case\nRun large backfills/hygiene tasks without timeouts or instance impact. Instead of one risky long transaction, process records in chunks and automatically schedule the next slice.\n\n## Where to use it\n- Script Include invoked from Background Script, on-demand Scheduled Job, or Flow Action wrapper.\n\n## How it works\n- Queries a time-boxed chunk (e.g., 40 seconds, 500 rows).\n- Executes a caller-supplied per-record function.\n- Saves a checkpoint (`sys_id`) in a system property.\n- Uses `ScheduleOnce` to queue the next slice (no `gs.sleep`).\n\n## Configuration\n- Target table, encoded query, orderBy field (default `sys_id`)\n- Chunk size, max execution seconds\n- Property name for checkpoint\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Safe Bulk Update Runner/safe_bulk_update_runner.js",
    "content": "\nvar SafeBulkUpdateRunner = Class.create();\nSafeBulkUpdateRunner.prototype = (function () {\n\tvar DEFAULTS = {\n\t\tproperty_name: 'x_util.safe_bulk.last_id',\n\t\ttable: 'incident',\n\t\tquery: 'active=true',\n\t\torder_by: 'sys_id',\n\t\tchunk_size: 500,\n\t\tmax_seconds: 40 // keep under typical script timeout\n\t};\n\n\tfunction _propGet(name, fallback) { var v = gs.getProperty(name, ''); return v || fallback || ''; }\n\tfunction _propSet(name, value) { gs.setProperty(name, value || ''); }\n\tfunction _gt(field, sysId) { return sysId ? field + '>' + sysId : ''; }\n\tfunction _now() { return new Date().getTime(); }\n\tfunction _scheduleNext(scriptIncludeName, methodName, args) {\n\t\tvar so = new ScheduleOnce();\n\t\tso.schedule(scriptIncludeName, methodName, JSON.stringify(args || {}));\n\t\tgs.info('[SafeBulk] Scheduled next slice for ' + scriptIncludeName + '.' + methodName);\n\t}\n\n\treturn {\n\t\tinitialize: function () {},\n\n\t\t/**\n\t\t * Run one slice then schedule the next.\n\t\t * @param {Object} cfg\n\t\t * @param {Function} perRecordFn function(gr) -> void\n\t\t */\n\t\trunSlice: function (cfg, perRecordFn) {\n\t\t\tvar c = Object.assign({}, DEFAULTS, cfg || {});\n\t\t\tif (typeof perRecordFn !== 'function') throw new Error('perRecordFn is required.');\n\n\t\t\tvar start = _now();\n\t\t\tvar lastId = _propGet(c.property_name, '');\n\n\t\t\tvar gr = new GlideRecord(c.table);\n\t\t\tgr.addEncodedQuery(c.query);\n\t\t\tif (lastId) gr.addEncodedQuery(_gt(c.order_by, lastId));\n\t\t\tgr.orderBy(c.order_by);\n\t\t\tgr.setLimit(c.chunk_size);\n\t\t\tgr.query();\n\n\t\t\tvar processed = 0;\n\t\t\twhile (gr.next()) {\n\t\t\t\tperRecordFn(gr);\n\t\t\t\tlastId = String(gr.getUniqueValue());\n\t\t\t\tprocessed++;\n\n\t\t\t\tif ((_now() - start) / 1000 >= c.max_seconds) {\n\t\t\t\t\tgs.info('[SafeBulk] Timebox reached after ' + processed + ' records.');\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (lastId) _propSet(c.property_name, lastId);\n\n\t\t\tif (processed === c.chunk_size || gr.hasNext()) {\n\t\t\t\t_scheduleNext('SafeBulkUpdateRunner', 'runSliceScheduled', { cfg: c });\n\t\t\t} else {\n\t\t\t\tgs.info('[SafeBulk] All done. Clearing checkpoint.');\n\t\t\t\t_propSet(c.property_name, '');\n\t\t\t}\n\t\t},\n\n\t\t/**\n\t\t * Entry point for ScheduleOnce (stringified args).\n\t\t */\n\t\trunSliceScheduled: function (jsonArgs) {\n\t\t\tvar args = (typeof jsonArgs === 'string') ? JSON.parse(jsonArgs) : (jsonArgs || {});\n\t\t\tvar cfg = args.cfg || {};\n\t\t\t// Example logic; replace with your own:\n\t\t\tthis.runSlice(cfg, function (gr) {\n\t\t\t\tgr.setValue('u_backfilled', true);\n\t\t\t\tgr.update();\n\t\t\t});\n\t\t}\n\t};\n})();\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Scheduled Recursion/README.md",
    "content": "# Scheduled Recursion\n\nTo take action on a large number of records in a table, a single script may time out before the actions are completed. This is especially true for deletes. To avoid timing out the script, this solution will enable the action to be scheduled in batches with delays to avoid hanging the system for an extended operation.\n\n## Any (i.e. Background) Script\n```javascript\nvar objInputs = {\n\t\t\"strTableName\": \"x_803922_demo_cmdb_widget\",  // Which table?\n\t\t\"strEncodedQuery\": \"nameLIKE34\",\t\t\t  // Which records?\n\t\t\"intBatchSize\": 100,\t \t\t\t\t\t  // How many records at a time?\n\t\t\"intBatchDelay\": 3,\t\t\t\t\t\t      // How many seconds delay to schedule the next batch\n\t\t\"strLogPrefix\": 'JD: ',\t\t\t\t\t\t  // Used to prefix log entries\n\t\t\"strFunction\": \"function( tableName, query ){ // Function to be executed on each record. \n\t\t\tnew global.GlideQuery.parse( tableName, query )\n\t\t\t.deleteMultiple();\t\t  \n\t\t}\"\n    };\n\nnew ScheduleRecursion().runMe( JSON.stringify( objInputs ) );\n```\n\n## Script Include\n```javascript\nvar ScheduledRecursion = Class.create();\nScheduledRecursion.prototype = {\n    initialize: function( strInputs ) {},\n    type: 'ScheduledRecursion',\n\t\n\trunMe: function( strInputs ){\n\t\tvar objInputs = JSON.parse( strInputs );\n\t\t\n\t\treturn createTrigger( strInputs );\n\t\t\n\t},\n\t\n\tdoIt: function( strInputs ){\n\n\t\ttry{\n\n\t\t\tobjInputs = JSON.parse( strInputs );\n\n\t\t\tvar totalRecordCount = new global.GlideQuery.parse( objInputs.strTableName, objInputs.strEncodedQuery ).count(); // Total records matching the query\n\t\t\tif( totalRecordCount == 0 ){ return null; }\n\n\t\t\tif( objInputs.intBatchCount == undefined ){\n\t\t\t\tobjInputs.intBatchCount = 0;\n\t\t\t\tgs.info( objInputs.strLogPrefix + 'Starting to process ' + totalRecordCount + ' records from ' + \n\t\t\t\t\t\tobjInputs.strTableName + ' in batches of ' + objInputs.intBatchSize + ' records.\\nFunction: ' + JSON.stringify( objInputs.strFunction ) );\t\t\t\t\n\t\t\t}\n\t\t\tgs.info( objInputs.strLogPrefix + 'Batch #' + ++objInputs.intBatchCount + ', Processing ' + objInputs.intBatchSize < totalRecordCount ? objInputs.intBatchSize : totalRecordCount + \n\t\t\t\t' of ' + totalRecordCount + ' remaining records.' );\n\n\t\t\tvar batchIDs = [];\n\t\t\tvar grBID = new GlideRecord( objInputs.strTableName );\n\t\t\tgrBID.addEncodedQuery( objInputs.strEncodedQuery );\n\t\t\tgrBID.setLimit( objInputs.intBatchSize );\n\t\t\tgrBID.query();\n\t\t\twhile( grBID.next() ){\n\t\t\t\tbatchIDs.push( grBID.getUniqueValue() );\n\t\t\t}\n\n\t\t\tobjInputs.strFunction( objInputs.strTableName, 'sys_idIN' + batchIDs )\n\n\t\t\tgs.info( objInputs.strLogPrefix + 'Batch #' + objInputs.intBatchCount + ' is complete.' );\n\t\t\t\n\t\t\tstrInputs = JSON.stringify( objInputs );\n\t\t\tcreateTrigger( strInputs );\n\t\t\t\n\t\t}\n\t\tcatch( e ){\n\t\t\tgs.error( \n\t\t\t\tobjInputs.strLogPrefix + 'An error occurred trying to process records from ' + objInputs.strTableName + \n\t\t\t\t'\\nERROR: ' + e.name + ': ' + e.message \n\t\t\t);\n\t\t}\n\n\t},\n\t\n\tcreateTrigger: function( strInputs ){\n\t\tvar objInputs = JSON.parse( strInputs ),\n\t\t\tintBatchNumber = ( objInputs.intBatchCount || 0 ) + 1,\n\t\t\tgdtNextAction = new GlideDateTime(),\n\t\t\tgrTrigger = new GlideRecord( 'sys_trigger' );\n\t\t\n\t\tgdtNextAction.addSeconds( objInputs.intBatchDelay );\n\t\t\t\n\t\tgrTrigger.initialize();\n\t\tgrTrigger.setValue( 'name', 'MassRecordDeletes Batch #' + intBatchNumber);\n\t\tgrTrigger.setValue( 'next_action', gdtNextAction );\n\t\tgrTrigger.setValue( 'trigger_type', 0 ); // Run Once\n\t\tgrTrigger.setValue( 'script', \"new ScheduledRecursion().doIt( '\" + strInputs + \"' )\" );\n\t\t\n\t\treturn 'Trigger: ' + grTrigger.insert() + ': ' + gs.getProperty('glide.servlet.uri') + 'sys_trigger_list.do?nameSTARTSWITHMassRecordDeletes';\n\t\t\n\t},\n\n};\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Scheduled Recursion/background_script.js",
    "content": "var objInputs = {\n    \"strTableName\": \"u_demo_cmdb_widget\",         // Which table?\n    \"strEncodedQuery\": \"nameLIKE34\",\t\t\t  // Which records?\n    \"intBatchSize\": 100,\t \t\t\t\t\t  // How many records at a time?\n    \"intBatchDelay\": 3,\t\t\t\t\t\t      // How many seconds delay to schedule the next batch\n    \"strLogPrefix\": 'JD: ',\t\t\t\t\t\t  // Used to prefix log entries for grouping/identification\n    \"strFunction\": function( tableName, query ){  // Function to be executed on each record. \n        new global.GlideQuery.parse( tableName, query )\n        .deleteMultiple();\t\t  \n    }\n};\n\nnew ScheduleRecursion().runMe( JSON.stringify( objInputs ) );\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Scheduled Recursion/scheduled_recursion.js",
    "content": "var ScheduledRecursion = Class.create();\nScheduledRecursion.prototype = {\n    initialize: function( strInputs ) {},\n    type: 'ScheduledRecursion',\n\t\n\trunMe: function( strInputs ){\n\t\tvar objInputs = JSON.parse( strInputs );\n\t\t\n\t\treturn createTrigger( strInputs );\n\t\t\n\t},\n\t\n\tdoIt: function( strInputs ){\n\n\t\ttry{\n\n\t\t\tobjInputs = JSON.parse( strInputs );\n\n\t\t\tvar totalRecordCount = new global.GlideQuery.parse( objInputs.strTableName, objInputs.strEncodedQuery ).count(); // Total records matching the query\n\t\t\tif( totalRecordCount == 0 ){ return null; }\n\n\t\t\tif( objInputs.intBatchCount == undefined ){\n\t\t\t\tobjInputs.intBatchCount = 0;\n\t\t\t\tgs.info( objInputs.strLogPrefix + 'Starting to process ' + totalRecordCount + ' records from ' + \n\t\t\t\t\t\tobjInputs.strTableName + ' in batches of ' + objInputs.intBatchSize + ' records.\\nFunction: ' + JSON.stringify( objInputs.strFunction ) );\t\t\t\t\n\t\t\t}\n\t\t\tgs.info( objInputs.strLogPrefix + 'Batch #' + ++objInputs.intBatchCount + ', Processing ' + objInputs.intBatchSize < totalRecordCount ? objInputs.intBatchSize : totalRecordCount + \n\t\t\t\t' of ' + totalRecordCount + ' remaining records.' );\n\n\t\t\tvar batchIDs = [];\n\t\t\tvar grBID = new GlideRecord( objInputs.strTableName );\n\t\t\tgrBID.addEncodedQuery( objInputs.strEncodedQuery );\n\t\t\tgrBID.setLimit( objInputs.intBatchSize );\n\t\t\tgrBID.query();\n\t\t\twhile( grBID.next() ){\n\t\t\t\tbatchIDs.push( grBID.getUniqueValue() );\n\t\t\t}\n\n\t\t\tobjInputs.strFunction( objInputs.strTableName, 'sys_idIN' + batchIDs )\n\n\t\t\tgs.info( objInputs.strLogPrefix + 'Batch #' + objInputs.intBatchCount + ' is complete.' );\n\t\t\t\n\t\t\tstrInputs = JSON.stringify( objInputs );\n\t\t\tcreateTrigger( strInputs );\n\t\t\t\n\t\t}\n\t\tcatch( e ){\n\t\t\tgs.error( \n\t\t\t\tobjInputs.strLogPrefix + 'An error occurred trying to process records from ' + objInputs.strTableName + \n\t\t\t\t'\\nERROR: ' + e.name + ': ' + e.message \n\t\t\t);\n\t\t}\n\n\t},\n\t\n\tcreateTrigger: function( strInputs ){\n\t\tvar objInputs = JSON.parse( strInputs ),\n\t\t\tintBatchNumber = ( objInputs.intBatchCount || 0 ) + 1,\n\t\t\tgdtNextAction = new GlideDateTime(),\n\t\t\tgrTrigger = new GlideRecord( 'sys_trigger' );\n\t\t\n\t\tgdtNextAction.addSeconds( objInputs.intBatchDelay );\n\t\t\t\n\t\tgrTrigger.initialize();\n\t\tgrTrigger.setValue( 'name', 'MassRecordDeletes Batch #' + intBatchNumber);\n\t\tgrTrigger.setValue( 'next_action', gdtNextAction );\n\t\tgrTrigger.setValue( 'trigger_type', 0 ); // Run Once\n\t\tgrTrigger.setValue( 'script', \"new ScheduledRecursion().doIt( '\" + strInputs + \"' )\" );\n\t\t\n\t\treturn 'Trigger: ' + grTrigger.insert() + ': ' + gs.getProperty('glide.servlet.uri') + 'sys_trigger_list.do?nameSTARTSWITHMassRecordDeletes';\n\t\t\n\t},\n\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Script Include Usage Tracker/README.md",
    "content": "#  Script Include Usage Tracker\n\nA utility Script Include to help ServiceNow developers identify where a specific Script Include is being referenced across the instance. This is especially useful during refactoring, cleanup, or impact analysis.\n\n##  Features\n\n- Scans multiple tables for references to a given Script Include.\n- Outputs a list of locations including table name, record name, and sys_id.\n- Easily extendable to include more tables or fields.\n\n##  Installation\n\n1. Navigate to **System Definition > Script Includes** in your ServiceNow instance.\n2. Click **New** and paste the code from `ScriptIncludeUsageTracker.js`.\n3. Save and make sure the Script Include is **Client Callable = false**.\n\n##  Usage\n\nYou can run the Script Include from a background script or another Script Include like this:\nvar tracker = new ScriptIncludeUsageTracker();\ntracker.findUsage('MyScriptInclude');\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Script Include Usage Tracker/code.js",
    "content": "\n\n    var scriptIncludeName = 'MyScriptInclude'; // Change this to your target Script Include\n    var usage = [];\n\n    function searchUsage(table, field) {\n        var gr = new GlideRecord(table);\n        gr.addEncodedQuery(field + 'LIKE' + scriptIncludeName);\n        gr.query();\n        while (gr.next()) {\n            usage.push({\n                table: table,\n                name: gr.name || gr.getDisplayValue(),\n                sys_id: gr.getUniqueValue()\n            });\n        }\n    }\n\n    var tablesToSearch = [\n        { table: 'sys_script', field: 'script' },\n        { table: 'sys_ui_action', field: 'script' },\n        { table: 'sys_script_include', field: 'script' },\n        { table: 'sys_flow_context', field: 'definition' },\n        { table: 'sys_trigger', field: 'script' }\n    ];// can add more entries here\n\n    tablesToSearch.forEach(function(t) {\n        searchUsage(t.table, t.field);\n    });\n\n    gs.info('Usage of Script Include: ' + scriptIncludeName);\n    usage.forEach(function(u) {\n        gs.info('Found in table: ' + u.table + ', name: ' + u.name + ', sys_id: ' + u.sys_id);\n    });\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Sends Slack/Teams notifications when specific fields change on configured tables/README.md",
    "content": "#  AuditFieldChangeNotifier\n### Description\nA ServiceNow Script Include that audits changes to specific fields and sends real-time notifications to Slack or Microsoft Teams using a webhook.  \nIt helps teams monitor important updates like priority or assignment changes without needing to check the platform.\n---\n### 🔧 Features\n- Monitors field-level changes on any table\n- Sends rich notifications to Slack/Teams\n- Easy to configure via system properties\n- Can be reused across multiple tables via Business Rules\n---\n### 🧩 How to Use\n1. **Create a System Property**\n  - Name: `x_custom.audit_notifier.webhook_url`\n  - Value: Your Slack or Teams webhook URL\n2. **Create a Script Include**\n  - Name: `AuditFieldChangeNotifier`\n  - Paste the provided Script Include code\n3. **Create a Business Rule**\n  - Table: e.g. `incident`\n  - When: `after update`\n  - Add this script:\n    ```js\n    (function executeRule(current, previous) {\n        var notifier = new AuditFieldChangeNotifier();\n        notifier.notifyOnFieldChange(current, previous, ['priority', 'state', 'assigned_to']);\n    })(current, previous);\n    ```\n4. **Test It**\n  - Update one of the watched fields.\n  - A message should appear in your Slack/Teams channel like:\n    ```\n    🛠️ ServiceNow — Field Update Notification\n    Record: Incident INC0010001\n    Description: Unable to access VPN\n    • priority changed from 4 - Low → 2 - High\n    • assigned_to changed from John → Alex\n    ```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Sends Slack/Teams notifications when specific fields change on configured tables/code.js",
    "content": "/**\n* Script Include: AuditFieldChangeNotifier\n* Description: Sends Slack/Teams notifications when specific fields change on configured tables.\n* Usable in: Business Rule (after update)\n*/\nvar AuditFieldChangeNotifier = Class.create();\nAuditFieldChangeNotifier.prototype = {\n   initialize: function () {\n       // Slack or Teams webhook URL (store in sys_properties for security)\n       this.webhookUrl = gs.getProperty('x_custom.audit_notifier.webhook_url', '');\n       // Default app name\n       this.appName = gs.getProperty('x_custom.audit_notifier.app_name', 'ServiceNow');\n   },\n   /**\n    * Send notification if the specified fields have changed\n    * @param {GlideRecord} current - Current record\n    * @param {GlideRecord} previous - Previous record\n    * @param {Array} fieldsToWatch - Array of field names to monitor\n    */\n   notifyOnFieldChange: function (current, previous, fieldsToWatch) {\n       try {\n           if (!this.webhookUrl) {\n               gs.warn('[AuditFieldChangeNotifier] Webhook URL not configured.');\n               return;\n           }\n           var changes = [];\n           fieldsToWatch.forEach(function (field) {\n               if (current[field] + '' !== previous[field] + '') {\n                   changes.push({\n                       field: field,\n                       oldValue: previous[field] + '',\n                       newValue: current[field] + ''\n                   });\n               }\n           });\n           if (changes.length === 0)\n               return; // No relevant field changed\n           var payload = this._buildPayload(current, changes);\n           this._sendWebhook(payload);\n       } catch (e) {\n           gs.error('[AuditFieldChangeNotifier] Error: ' + e.message);\n       }\n   },\n   /**\n    * Build payload for Slack/Teams message\n    */\n   _buildPayload: function (current, changes) {\n       var shortDesc = current.short_description ? current.short_description + '' : '(No short description)';\n       var tableName = current.getTableName();\n       var recordUrl = gs.getProperty('glide.servlet.uri') + tableName + '.do?sys_id=' + current.sys_id;\n       var changeLines = changes.map(function (c) {\n           return `• *${c.field}* changed from _${c.oldValue}_ → *${c.newValue}*`;\n       }).join('\\n');\n       return JSON.stringify({\n           text: `🛠️ *${this.appName}* — Field Update Notification\\n\\n*Record:* <${recordUrl}|${tableName}> \\n*Description:* ${shortDesc}\\n\\n${changeLines}\\n\\n_Updated by ${gs.getUserDisplayName()}_`\n       });\n   },\n   /**\n    * Send payload to webhook\n    */\n   _sendWebhook: function (payload) {\n       var r = new sn_ws.RESTMessageV2();\n       r.setEndpoint(this.webhookUrl);\n       r.setHttpMethod('POST');\n       r.setRequestHeader('Content-Type', 'application/json');\n       r.setRequestBody(payload);\n       var response = r.execute();\n       if (response.getStatusCode() >= 400)\n           gs.error('[AuditFieldChangeNotifier] Webhook error: ' + response.getBody());\n   },\n   type: 'AuditFieldChangeNotifier'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Single Sign-On (SSO) Direct Login URL Generator/README.md",
    "content": "# Script Include for Single Sign-On (SSO) Direct Login URL Generator using UserHelper\n\n1. [Introduction](#introduction)\n2. [Installation](#installation)\n3. [Example & Usage](#usage)\n4. [Security](#security)\n\n## Introduction<a name=\"introduction\"></a>\n\nThe UserHelper script include provides functions for generating direct login URLs for users in ServiceNow. This functionality is similar to the impersonate user feature in ServiceNow, but it allows you to directly log in the user without requiring them to enter any credentials.\n\n## Installation<a name=\"installation\"></a>\n\nThis script include required Digest Token Authentication Integration below:\n\n#### Prerequisites\n\n* Multi-Provider SSO plugin is activated [documentation](https://docs.servicenow.com/bundle/vancouver-platform-security/page/integrate/single-sign-on/task/t_ActivateMultipleProviderSSO.html)\n* Multi-Provider SSO properties are configured [documentation](https://docs.servicenow.com/bundle/vancouver-platform-security/page/integrate/single-sign-on/task/t_ConfigureMultiProviderSSOProps.html)\n\n#### Steps\n\n1. Go to **Multi-Provider SSO** > **Identity Providers**.\n2. Select the **Digested Token** record.\n3. Add a **Secret Passphrase**. This will be the `<YOUR_SECRET_KEY>` in the script include.\n4. Note the **sys_id** of the **Digested Token** record. This will be the `<SSO_PROVIDER_SYS_ID>` for the script include.\n\n## Usage<a name=\"usage\"></a>\n\nTo generate a direct login URL for a user, you can use the `login()` function provided by the UserHelper script include. This function takes the user's name or GlideRecord as input and returns a URL that the user can use to log in directly.\n\nThe following example shows how to use the UserHelper script include to generate a direct login URL for a user:\n```javascript\n// Generate a direct login URL for the user \"admin\".\nvar userHelper = new UserHelper();\n\n// Generate a direct login URL for the user \"admin\".\nvar loginUrl = userHelper.login('admin');\n```\n```javascript\n// Generate a direct login URL for the user with the sys_id \"1234567890\", email.\nvar userHelper = new UserHelper();\nuserHelper.getUserById('1234567890'); //userHelper.getUserByEmail('<EMAIL>');\n\n// Generate a direct login URL for the user \"admin\".\nvar loginUrl = userHelper.login();\n\n//loginUrl: https://<instance>.service-now.com/?glide_sso_id=<SSO_PROVIDER_SYS_ID>&SM_USER=admin&DE_USER=htrULTFZTOLl9PHEvNBejz65ghxp6dJgDazXXv9v/wY=\n```\n\n## Security<a name=\"security\"></a>\nIt is crucial to emphasize that the UserHelper script include provides direct access to users without requiring any credentials. Therefore, it is important to set proper security policies to secure this script include. Make sure to follow best practices for securing access to this functionality.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Single Sign-On (SSO) Direct Login URL Generator/UserHelper.js",
    "content": "var UserHelper = Class.create();\nUserHelper.prototype = {\n    SECRET_KEY: '<YOUR_SECRET_KEY>', //secret key defined by you for encoding\n    MAC_ALG: 'HmacSHA256',\n    GLIDE_SSO_ID: '<SSO_PROVIDER_SYS_ID>', //system id of digest token sso provider\n\n    initialize: function(userGR) {\n        this.userGR = userGR;\n    },\n\n    getUserById: function(sys_id) {\n        return this.getUser('sys_id', sys_id);\n    },\n\n    getUserByEmail: function(email) {\n        return this.getUser('email', email);\n    },\n\n    getUserByName: function(user_name) {\n        return this.getUser('user_name', user_name);\n    },\n\n    getUser: function(key, value) {\n        if (key && value) {\n            this.userGR = new GlideRecord('sys_user');\n            this.userGR.get(key, value);\n        }\n        return this.userGR;\n    },\n\n    //generate the direct login url using user_name or user glide record\n    login: function(user_name) {\n\n        if (user_name) {\n            this.getUserByName(user_name);\n        }\n\n        if (!this.userGR) {\n            return null;\n        }\n\n        //generating token\n        var token = SncAuthentication.encode(this.userGR.getValue('user_name'), this.SECRET_KEY, this.MAC_ALG);\n\n        //formating url\n        var url = gs.getProperty('glide.servlet.uri') + '?glide_sso_id=' + this.GLIDE_SSO_ID + '&SM_USER=' + this.userGR.getValue('user_name') + '&DE_USER=' + token;\n\n        return url;\n    },\n\n    type: 'UserHelper'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Slack JSON Block Factory/README.md",
    "content": "This Script Includes provides simple factories (Slacktory) to create Slack JSON blocks, that can be sent in payloads. \nDocumentation for syntax is inline in the code.\n\nThe factories will always return the created or modified block [Object]."
  },
  {
    "path": "Server-Side Components/Script Includes/Slack JSON Block Factory/Slacktory.js",
    "content": "//Class def\nvar Slacktory = Class.create();\nSlacktory.prototype = {\n\t\n    initialize: function() {},\n\t\n\t/*\n\tinnerText [String]: The text for the text.text property\n\t\t- if not supplied, defaults to an empty string\n\t*/\n\tsectionBlock: function(innerText){\n\t\tvar facBlock = {};\n        facBlock.type = 'section';\n        facBlock.text = {};\n        facBlock.text.type = 'plain_text';\n        facBlock.text.text = innerText ? innerText : '';\n\t\treturn facBlock;\n\t},\n\t\n\t/*\n    innerText [String]: The text to be inserted into the block's text.text property.\n        - if not supplied, defaults to an empty string.\n\t*/\n\tsectionMarkdownBlock: function(innerText){\n\t\tvar facBlock = {};\n\t\tfacBlock.type = 'section';\n\t\tfacBlock.text = {};\n\t\tfacBlock.text.type = 'mrkdwn';\n\t\tfacBlock.text.text = innerText ? innerText : '';\n\t\treturn facBlock;\n\t},\n\n\t/*\n\t\tinnerTexts [Array]: An array of strings to be inserted into the block's text.text property.\n\t*/\n\tmultiFieldSectionMarkdownBlock: function(innerTexts){\n\t\tvar facBlock = {};\n\t\tfacBlock.type = 'section';\n\t\tfacBlock.fields = [];\n\n        innerTexts.forEach(function(innerText){\n            var field = {};\n            field.type = 'mrkdwn';\n            field.text = innerText;\n            facBlock.fields.push(field);\n        });\n\t\treturn facBlock;\n\t},\n\n\t/*\n\t\tinnerText [String]: The text to be inserted into the block's element's text.text property.\n\t\t\t- This is 100% required ( even if it's just an emoji... )\n\n\t\tex: \n\n\t\tblock = contextMarkdownBlock(':blank:\\n:sn-info: *Information*');\n\t*/\n\tcontextMarkdownBlock: function(innerText){\n\t\tvar facBlock = {};\n\t\tfacBlock.type = 'context';\n\t\tfacBlock.elements = [];\n\t\tif(Array.isArray(innerText)){\n\t\t\tinnerText.forEach(function(t){\n\t\t\t\telement = {};\n\t\t\t\telement.type = 'mrkdwn';\n\t\t\t\telement.text = t;\n\t\t\t\tfacBlock.elements.push(element);\n\t\t\t});\n\t\t}\n\t\telse{\n\t\t\telement = {};\n\t\t\telement.type = 'mrkdwn';\n\t\t\telement.text = innerText;\n\t\t\tfacBlock.elements.push(element);\n\t\t}\n\t\treturn facBlock;\n\t},\n\n\t/*\n\t\theaderText [String]: The text to be inserted into the block's text.text property.\n\t\ttable [String]: The table that is being referenced (e.g. 'sys_user' or 'task')\n\t\trecord [String]: The sys_id of the record that is being referenced\n\n\t\trecord can be directly passed by name, as it is defined earlier.\n\n\t\ttable can (sometimes) be passed by name as it is defined earlier.\n\t\tThe only exception here is when the table name is not the way to directly reference the list.\n\t\tFor example, INC, SCTASK, and RITM all live on the `task` table, but the table would be the\n\t\tcorresponding `inc`, `sc_task`, etc.\n\n\t\tex:\n\n\t\tblock = headerBlockWithOpenInServiceNow('Record', table, record);\n\n\t\tIf the table name does not mach the list name, you can pass the table name as a string:\n\n\t\tblock = headerBlockWithOpenInServiceNow('Record', 'task', record);\n\t*/\n\theaderBlockWithOpenInServiceNow: function(headerText, table, record){\n\t\tvar facBlock = this.sectionMarkdownBlock(headerText);\n\t\tthis.buttonAdder(facBlock, 'Open in ServiceNow', (gs.getProperty('glide.servlet.uri') + 'nav_to.do?uri=' + table + '.do?sys_id=' + record));\n\t\treturn facBlock;\n\t},\n\n\t/*\n\t\tAdd a button that will open a specified URL on press. This should NOT be used to add Modals,\n\t\tas modal URLs will not play nice with block.accessory.url\n\t\n\t\tblock [Object]: The block to add the button to.\n\t\tbuttonText [String]: The text label for the button.\n\t\tbuttonUrl [String]: The url that the button will link to.\n\t*/\n\tbuttonAdder: function(block, buttonText, buttonUrl){\n\t\tblock.accessory = {};\n\t\tblock.accessory.type = 'button';\n\t\tblock.accessory.style = 'primary';\n\t\tblock.accessory.text = {};\n\t\tblock.accessory.text.type = 'plain_text';\n\t\tblock.accessory.text.text = buttonText;\n\t\tblock.accessory.url = buttonUrl;\n\t\treturn block;\n\t},\n\t/*\n\t\tModal adder should be used for adding any non-url-explicit-redirect functionality (More Info, View Workflow, etc.)\n\t\tModals must be added as block.accessory.value instead of block.acessory.url, hence the separate function.\n\t\n\t\tblock [Object]: The block to add the button to.\n\t\tbuttonText [String]: The text label for the button.\n\t\tbuttonValue [String]: The value the modal will spit back to the modal info SRAPI. [eg: 'variables|' + gr.sys_id, 'workflow|' + gr.sys_id]\n\t*/\n\tmodalAdder: function(block, buttonText, buttonValue){\n\t\tblock.accessory = {};\n\t\tblock.accessory.type = 'button';\n\t\tblock.accessory.text = {};\n\t\tblock.accessory.text.type = 'plain_text';\n\t\tblock.accessory.text.text = buttonText;\n\t\tblock.accessory.value = buttonValue;\n\t\treturn block;\n\t},\n\t\n\t/*\n\t\tSwitch adder should be used for adding any form-controlling switcher functionality (e.g., changing active from true to false, etc.)\n\t\t\n\t\tblock [Object]: The block to add the switch to.\n\t\tplaceholderText [String]: The text that will display intermittently in Slack when the provided choices do not.\n\t\tinitialOptionIndex [Integer]: The integer index of the object that should be shown as selected originally, as is it located in objArray.\n\t\tobjArray [Object Array]: Array of options that should be provided to define what the switch options will be. Form of the object:\n\t\t\t[\n\t\t\t\t{\n\t\t\t\t\t\"text\": \"...\", [String]: The label of the option\n\t\t\t\t\t\"value\": \"...\", [String]: The action that the modal will send back when the option is selected. Usually should be in the format: \"descriptive_action_name|sys_id_of_record\"\n\t\t\t\t},\n\t\t\t\tetc.\n\t\t\t]\n\t*/\n\tswitchAdder: function(block, placeholderText, initialOptionIndex, objArray){\n\t\tblock.accessory = {};\n\t\tblock.accessory.type = 'static_select';\n\t\tblock.accessory.placeholder = {};\n\t\tblock.accessory.placeholder.type = 'plain_text';\n\t\tblock.accessory.placeholder.text = placeholderText;\n\t\tblock.accessory.options = [];\n\n\t\tobjArray.forEach(function(e, index){\n\t\t\tvar obj = {};\n\t\t\tobj.text = {};\n\t\t\tobj.text.type = 'plain_text';\n\t\t\tobj.text.text = e.text;\n\t\t\tobj.value = e.value;\n\n\t\t\tblock.accessory.options.push(obj);\n\n\t\t\tif(index == initialOptionIndex) block.accessory.initial_option = obj;\n\t\t});\n\t\t\n\t\treturn block;\n\t},\n\n    type: 'Slacktory'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Slack JSON Block Factory/sys_script_include_config.md",
    "content": "Name: Slacktory\nClient callable: false"
  },
  {
    "path": "Server-Side Components/Script Includes/SmartData/SmartData.scriptinclude.js",
    "content": "/**\n * Name: SmartData\n * Type: Script Include (server-side, global)\n * Accessible from: Server scripts (NOT client-callable)\n * Author: Abhishek\n * Summary: A tiny data helper that auto-picks GlideAggregate for counts/stats/distinct\n *          and GlideRecord for lists/one. Also includes describe() and preview().\n */\nvar SmartData = Class.create();\nSmartData.prototype = {\n  initialize: function () {},\n\n  /**\n   * Unified entry\n   * opts = {\n   *   table: 'incident',\n   *   query: 'active=true^priority=1',\n   *   want: 'list' | 'one' | 'count' | 'distinct' | 'stats' | 'describe' | 'preview',\n   *   fields: ['number','short_description'],\n   *   limit: 50,\n   *   orderBy: 'sys_created_on' | '-sys_created_on',\n   *   field: 'assignment_group',             // for distinct\n   *   groupBy: ['assignment_group','priority'], // for stats\n   *   aggregate: { fn:'AVG'|'SUM'|'MIN'|'MAX'|'COUNT', field:'time_worked' }\n   * }\n   */\n  query: function (opts) {\n    opts = opts || {};\n    var want = (opts.want || \"list\").toLowerCase();\n\n    if (want === \"count\") return this.count(opts.table, opts.query);\n    if (want === \"distinct\")\n      return this.distinct(opts.table, opts.field, opts.query);\n    if (want === \"stats\")\n      return this.stats(opts.table, opts.aggregate, opts.groupBy, opts.query);\n    if (want === \"one\")\n      return this.one(opts.table, opts.query, opts.fields, opts.orderBy);\n    if (want === \"describe\") return this.describe(opts.table);\n    if (want === \"preview\") return this.preview(opts.table, opts.query);\n\n    return this.list(\n      opts.table,\n      opts.query,\n      opts.fields,\n      opts.limit,\n      opts.orderBy\n    );\n  },\n\n  /** Fast COUNT via GlideAggregate */\n  count: function (table, encQuery) {\n    var ga = new GlideAggregate(table);\n    if (encQuery) ga.addEncodedQuery(encQuery);\n    ga.addAggregate(\"COUNT\");\n    ga.query();\n    return ga.next() ? parseInt(ga.getAggregate(\"COUNT\"), 10) || 0 : 0;\n  },\n\n  /** DISTINCT values of a single field (GlideAggregate groupBy) */\n  distinct: function (table, field, encQuery) {\n    if (!field) return [];\n    var ga = new GlideAggregate(table);\n    if (encQuery) ga.addEncodedQuery(encQuery);\n    ga.groupBy(field);\n    ga.addAggregate(\"COUNT\"); // driver\n    ga.query();\n    var out = [];\n    while (ga.next()) out.push(String(ga.getValue(field)));\n    return out;\n  },\n\n  /**\n   * Stats via GA.\n   * aggregate = { fn:'AVG'|'SUM'|'MIN'|'MAX'|'COUNT', field:'duration' }\n   * groupBy = ['assignment_group','priority']\n   */\n  stats: function (table, aggregate, groupBy, encQuery) {\n    var fn =\n      aggregate && aggregate.fn ? String(aggregate.fn).toUpperCase() : \"COUNT\";\n    var fld = (aggregate && aggregate.field) || \"sys_id\";\n    var ga = new GlideAggregate(table);\n    if (encQuery) ga.addEncodedQuery(encQuery);\n    (groupBy || []).forEach(function (g) {\n      if (g) ga.groupBy(g);\n    });\n    ga.addAggregate(fn, fld);\n    ga.query();\n    var out = [];\n    while (ga.next()) {\n      var row = {};\n      (groupBy || []).forEach(function (g) {\n        row[g] = String(ga.getValue(g));\n      });\n      row.fn = fn;\n      row.field = fld;\n      row.value = ga.getAggregate(fn, fld);\n      out.push(row);\n    }\n    return out;\n  },\n\n  /** One record via GlideRecord */\n  one: function (table, encQuery, fields, orderBy) {\n    var gr = new GlideRecord(table);\n    gr.addEncodedQuery(encQuery || \"\");\n    this._applyOrder(gr, orderBy);\n    gr.setLimit(1);\n    gr.query();\n    if (!gr.next()) return null;\n    return this._pick(gr, fields);\n  },\n\n  /** List via GlideRecord */\n  list: function (table, encQuery, fields, limit, orderBy) {\n    var gr = new GlideRecord(table);\n    gr.addEncodedQuery(encQuery || \"\");\n    this._applyOrder(gr, orderBy);\n    if (limit) gr.setLimit(limit);\n    gr.query();\n    var out = [];\n    while (gr.next()) out.push(this._pick(gr, fields));\n    return out;\n  },\n\n  /** Quick schema: field name, label, type, ref, mandatory */\n  describe: function (table) {\n    var gr = new GlideRecord(table);\n    gr.initialize();\n    var fields = gr.getFields(),\n      out = [];\n    for (var i = 0; i < fields.size(); i++) {\n      var f = fields.get(i),\n        ed = f.getED();\n      out.push({\n        name: f.getName(),\n        label: ed.getLabel(),\n        type: ed.getInternalType(),\n        ref: ed.getReference() || \"\",\n        mandatory: ed.getMandatory(),\n      });\n    }\n    return out;\n  },\n\n  /** Tiny peek — returns display value (number/ID) for the first match */\n  preview: function (table, encQuery) {\n    var gr = new GlideRecord(table);\n    gr.addEncodedQuery(encQuery || \"\");\n    gr.setLimit(1);\n    gr.query();\n    if (gr.next()) return gr.getDisplayValue(\"number\") || gr.getUniqueValue();\n    return null;\n  },\n\n  // --- helpers ---\n  _applyOrder: function (gr, orderBy) {\n    if (!orderBy) return;\n    if (orderBy.indexOf(\"-\") === 0) gr.orderByDesc(orderBy.substring(1));\n    else gr.orderBy(orderBy);\n  },\n\n  _pick: function (gr, fields) {\n    var obj = {};\n    if (Array.isArray(fields) && fields.length) {\n      fields.forEach(function (f) {\n        obj[f] = gr.getDisplayValue(f);\n      });\n    } else {\n      obj.sys_id = gr.getUniqueValue();\n      if (gr.isValidField(\"number\")) obj.number = gr.getValue(\"number\");\n      if (gr.isValidField(\"short_description\"))\n        obj.short_description = gr.getValue(\"short_description\");\n    }\n    return obj;\n  },\n\n  type: \"SmartData\",\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SmartData/SmartDataAjax.scriptinclude.js",
    "content": "/**\n * Name: SmartDataAjax\n * Type: Script Include (server-side, client-callable)\n * Extends: AbstractAjaxProcessor\n * Author: Abhishek\n * Security: Escapes encoded queries via GlideStringUtil.escapeQueryTermSeparator\n */\nvar SmartDataAjax = Class.create();\nSmartDataAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  /**\n   * Client-callable entry.\n   * Expected params:\n   *  - sysparm_table\n   *  - sysparm_query\n   *  - sysparm_want\n   *  - sysparm_fields (comma-separated)\n   *  - sysparm_limit\n   *  - sysparm_orderBy\n   *  - sysparm_field\n   *  - sysparm_groupBy (comma-separated)\n   *  - sysparm_fn\n   *  - sysparm_fn_field\n   */\n  query: function () {\n    var rawQuery = this.getParameter(\"sysparm_query\") || \"\";\n    var safeQuery = GlideStringUtil.escapeQueryTermSeparator(rawQuery); // 🔒 protect separators\n\n    var params = {\n      table: this.getParameter(\"sysparm_table\"),\n      query: safeQuery,\n      want: this.getParameter(\"sysparm_want\"),\n      fields: (this.getParameter(\"sysparm_fields\") || \"\")\n        .split(\",\")\n        .filter(Boolean),\n      limit: parseInt(this.getParameter(\"sysparm_limit\"), 10) || null,\n      orderBy: this.getParameter(\"sysparm_orderBy\"),\n      field: this.getParameter(\"sysparm_field\"),\n      groupBy: (this.getParameter(\"sysparm_groupBy\") || \"\")\n        .split(\",\")\n        .filter(Boolean),\n      aggregate: {\n        fn: this.getParameter(\"sysparm_fn\"),\n        field: this.getParameter(\"sysparm_fn_field\"),\n      },\n    };\n\n    var sd = new SmartData();\n    var result = sd.query(params);\n    return new global.JSON().encode(result);\n  },\n\n  /** health check */\n  ping: function () {\n    return \"ok\";\n  },\n\n  type: \"SmartDataAjax\",\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SmartData/readme.md",
    "content": "What it is\nA tiny, reusable utility that auto-selects GlideAggregate vs GlideRecord based on intent. Includes count, distinct, stats, one, list, describe, preview and a client-callable GlideAjax wrapper that escapes encoded queries using GlideStringUtil.escapeQueryTermSeparator.\n\nWhy it’s useful\n\nDevs can call one API and let it pick the best engine.\n\nHandy helpers: describe(table) for schema, preview(table, query) for quick peeks.\n\nAJAX-ready for client scripts/UI actions.\n\nShows secure-by-default thinking reviewers love.\n\nHow to test quickly\n\nCreate both Script Includes as provided.\n\nTesting\n\nOr run in Background Scripts:\nvar sd = new SmartData(); gs.info('Count: ' + sd.count('incident', 'active=true')); gs.info(JSON.stringify(sd.stats('incident', {fn:'AVG', field:'time_worked'}, ['assignment_group'], 'active=true'))); gs.info(JSON.stringify(sd.one('incident', 'active=true', ['number','priority'], '-sys_created_on'))); gs.info(JSON.stringify(sd.describe('problem')));\n\nSecurity note\nSmartDataAjax sanitizes sysparm_query via GlideStringUtil.escapeQueryTermSeparator to protect against malformed/injected encoded queries.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Standard Change Creator/README.md",
    "content": "This Script Includes provides a way to create a Standard Change, that will be auto-populated with certain fields.\n\nUsage and list of required variables, as well as documentation and commets are inline in the code."
  },
  {
    "path": "Server-Side Components/Script Includes/Standard Change Creator/sys_script_include.js",
    "content": "var RepeatingStandardChange = Class.create();\n\nRepeatingStandardChange.prototype = {\n    initialize: function() {\n    },\n\t\n\t/*\n\t\tGiven the following params are set before execution:\n\t\t\ttemplate [string]: the name of the CHG template\n\t\t\tassignUser [string]: the username of the user to assign the CHG to\n\t\t\tassignGroup [string]: the group name to assign the CHG to\n\t\t\tshortDesc [string]: the short description for the CHG\n\t\t\tdesc [string]: the description for the CHG\n\t\t\t\n\t\t\tstartDateTime [string]: (YYYY-MM-DD hh:mm:ss format) the start date for the CHG\n\t\t\tendDateTime [string]: (YYYY-MM-DD hh:mm:ss format) the end date for the CHG\n\t\t\t\n\t\tCreate a new CHG, and return the number\n\t*/\n\tcreateWithTemplate: function(){\n\t\t\n\t\t//Lookup the provided template\n\t\tvar templateGr = new GlideRecord('std_change_producer_version');\n\t\ttemplateGr.addQuery('std_change_producer.name', '=', this.template);\n\t\ttemplateGr.orderByDesc('version');\n\t\ttemplateGr.query();\n\t\t\n\t\t//If the template was found\n\t\tif(templateGr.next()){\n\t\t\t//Initialize a new change\n\t\t\tvar chgGr = new GlideRecord('change_request');\n\t\t\tchgGr.initialize();\n\t\t\t\n\t\t\t//Standard Type\n\t\t\tchgGr.type = 'standard';\n\t\t\tchgGr.std_change_producer_version = templateGr.sys_ID;\n\t\t\t\n\t\t\t//Apply query from template\n\t\t\tchgGr.applyEncodedQuery(templateGr.std_change_producer.template.template);\n\t\t\t\n\t\t\t//Compile dates into GDT objects\n\t\t\tvar start = new GlideDateTime;\n            if(this.startDateTime != '' && this.startDateTime != undefined) start.setDisplayValue(this.startDateTime);\n\n            var end = new GlideDateTime;\n            if(this.endDateTime != '' && this.endDateTime != undefined) end.setDisplayValue(this.endDateTime);\n\n            //Set dates\n\t\t\tchgGr.work_start = start.getDisplayValue();\n\t\t\tchgGr.work_end = end.getDisplayValue();\n\t\t\tchgGr.start_date = start.getDisplayValue();\n\t\t\tchgGr.end_date = end.getDisplayValue();\n\t\t\t\n\t\t\t//Set assigned user\n\t\t\tvar userGr = new GlideRecord('sys_user');\n\t\t\tuserGr.addQuery('user_name', this.assignUser);\n\t\t\tuserGr.query();\n\t\t\tif(userGr.next()) chgGr.assigned_to = userGr.sys_id.toString();\n\t\t\telse return \"User provided for assignment was not found.\";\n\t\t\t\n\t\t\t//Set assignment group\n\t\t\tvar groupGr = new GlideRecord('sys_user_group');\n\t\t\tgroupGr.addQuery('name', this.assignGroup);\n\t\t\tgroupGr.query();\n\t\t\tif(groupGr.next()) chgGr.assignment_group = groupGr.sys_id.toString();\n\t\t\telse return \"Group provided for assignment was not found.\";\n\t\t\t\n\t\t\t//Set description\n\t\t\tchgGr.short_description = this.shortDesc;\n\t\t\t//Replace newlines with html breaks\n\t\t\tchgGr.description = this.desc.replace(/\\n/g, \"<br />\");\n\t\t\t\n\t\t\t//Create the change\n\t\t\tvar newChange = chgGr.insert();\n\t\t\t\n\t\t\t//Manually move through the steps to complete the change\n\t\t\tvar chg = new GlideRecord('change_request');\n\t\t\tchg.get(newChange);\n\t\t\tchg.state = -2;\n\t\t\tchg.update();\n\t\t\tchg.state = -1;\n\t\t\tchg.update();\n\t\t\tchg.state = 0;\n\t\t\tchg.update();\n\t\t\t\n\t\t\tif((this.close && this.close == true) || this.close == undefined){\n\t\t\t\tgs.log(\"this.close: '\" + this.close + \"', this.close == false ?: \" + (this.close == false));\n\t\t\t\tchg.close_code = 'successful';\n\t\t\t\tchg.state = 3;\n\t\t\t\tchg.update();\n\t\t\t}\n\t\t\t\n\t\t\t//Return the CHG number\n\t\t\treturn chg.number.toString();\t\n\t\t}\n\t\telse{\n\t\t\treturn \"!!! Template provided was not found.\";\n\t\t}\n\t\t\n\t},\n\n    type: 'RepeatingStandardChange'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Standard Change Creator/sys_script_include_config.md",
    "content": "Name: RepeatingStandardChange\nClient callable: false"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/AjaxClientScript.js",
    "content": "    function onChange(control, oldValue, newValue, isLoading) {\n        if (isLoading || newValue == '') {\n            return;\n        }\n\n        var ga = new GlideAjax('StarterPackAjax'); //Script include name\n        ga.addParam('sysparm_name', 'getUserInfo'); //Function within the Script Include Name\n        ga.addParam('sysparm_id', g_form.getValue('caller_id')); //Parameter to pass to the Script Include, in this case a field called 'caller_id'\n        ga.getXML(getParse);\n    }\n\nfunction getParse(response) {\n    var answer = response.responseXML.documentElement.getAttribute(\"answer\");\n    if (answer) {\n        var data = JSON.parse(answer);\n        g_form.setValue('location', data.location); // Set values that were returned from the Script Include\n        g_form.setValue('manager', data.manager);\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/AjaxSI.js",
    "content": "var StarterPackAjax = Class.create();\nStarterPackAjax.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getUserInfo: function() {\n        var userSysid = this.getParameter(\"sysparm_id\"); //Parameter that was passed from the Client Script\n        var grUser = new GlideRecord(\"sys_user\");\n        grUser.get(userSysid);\n        var result = { //build an object with the values we want to return. The \"Name\" of the entry should be in quotes, followed by a : and then the value, followed by a comma\n            \"location\": grUser.getDisplayValue('location'),\n            \"manager\": grUser.getDisplayValue('manager'),\n        };\n        var payload = JSON.stringify(result);\n        return payload;\n    },\n\n    type: 'StarterPackAjax'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/README.md",
    "content": "Also available on Share here: https://developer.servicenow.com/connect.do#!/share/contents/6592535_script_include_starter_pack?t=PRODUCT_DETAILS\n\nand related to [CCB1193-K21 Script Includes: What are they and why should I care?](https://events.servicenow.com/widget/servicenow/knowledge2021/library/session/1612301555107001YVuE#1617832086855001eNVb)\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/classless.js",
    "content": "/*\nUsage:  gs.addInfoMessage(StarterPackClassless(current.sys_id);\n\nClassless Script Includes only have one function and that function name MUST match the name of the Script Include.\n*/\n\nfunction StarterPackClassless(userId) {\n    var answer;\n    if (userId == '6816f79cc0a8016401c5a33be04be441') { //Default admin account sys_id\n        answer = 'You are the default system admin';\n    } else {\n        answer = 'You are NOT the default system admin';\n    }\n    return answer;\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/constants.js",
    "content": "/*\nUsage: In any script block you can use \"StarterPackConstants.<SOMETHING>\"\n*/\n\nvar StarterPackConstants = Class.create();\n\nStarterPackConstants.INSTANCE = gs.getProperty('instance_name'); //usage: var instanceString = 'The current Instance is ' + StarterPackConstants.INSTANCE;\nStarterPackConstants.ACTIVEQUERY = 'active=true'; //usage: gr.addEncodedQuery(StarterPackConstants.ACTIVEQUERY);\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/reference.js",
    "content": "/*\nUsage: in a Reference Field  to the User table set the Ref Qual to Advanced and paste this in\n               javascript: 'sys_idIN'+new.StarterPackRef().getAdmins();\n*/\n\n\nvar StarterPackRef = Class.create();\nStarterPackRef.prototype = {\n    initialize: function() {},\n\n    getAdmins: function() {\n        var users = {};\n        var grHasRole = new GlideRecord('sys_user_has_role');\n        grHasRole.addEncodedQuery('role=2831a114c611228501d4ea6c309d626d'); //admin\n        grHasRole.query();\n        while (grHasRole.next()) {\n            users[grHasRole.user.toString()] = true;\n        }\n        var ids = [];\n        for (var id in users)\n            ids.push(id);\n        return ids.join(',');\n    },\n\n    type: 'StarterPackRef'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StarterPack/utilsExample.js",
    "content": "/*\nUsage: \n  new StarterPackUtils().doubleNumber(<some number>);\n  new StarterPackUtils().sayMyName(<user sys_id>);\n\nOR \n  var utils = new StarterPackUtils();\n  utils.doubleNumber(<some number>);\n  utils.sayMyName(<user sys_id>);\n\n*/\n\n\nvar StarterPackUtils = Class.create();\nStarterPackUtils.prototype = {\n    initialize: function() {},\n\n    doubleNumber: function(number) {\n        var doubleNumber = number * 2;\n        return doubleNumber;\n    },\n    sayMyName: function(userId) {\n        var currentUser = gs.getUser().getUserByID(userID);\n        return currentUser.getFirstName();\n    },\n    type: 'StarterPackUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Stopwatch/README.md",
    "content": "# Stopwatch\nA script include that can be used as a stop watch when measuring the performance of a script or when want to show the elapsed time of an operation.\n\n\n## Example Script\n```javascript\n\tvar watch = new Stopwatch();\n\twatch.start();\n\tgs.sleep(1000); // Do something\n\twatch.stop();\n\tgs.log(watch.getElapsedTimeMilliseconds());\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Stopwatch/Stopwatch.js",
    "content": "/*\n\tActs as a stop watch to log the seconds or milliseconds of an operation.\n\n\tUsage:\n\n\tvar watch = new Stopwatch();\n\twatch.start();\n\tgs.sleep(1000); // Do something\n\twatch.stop();\n\twatch.getElapsedTimeMilliseconds();\n*/\nvar Stopwatch = Class.create();\nStopwatch.prototype = {\n\n\tstartDateTime: null,\n\n\tendDateTime: null,\n\n\tinitialize: function () {\n\t},\n\n\t/**\n\t * Logs the start of the operation\n\t */\n\tstart: function () {\n\t\tthis.startDateTime = new GlideDateTime();\n\t\tgs.info(\"Started: \" + this.startDateTime.getDisplayValue(), \"StopWatch\");\n\t},\n\n\t/**\n\t * \n\t * @param {boolean} logInfo, indicates to log the end of operation in system log. Default is false \n\t */\n\tstop: function (logInfo) {\n\t\tthis.endDateTime = new GlideDateTime();\n\n\t\tif (logInfo) {\n\t\t\tgs.info(\"Total Elapsed Time Seconds: \" + this.getElapsedTimeSeconds(), \"StopWatch\");\n\t\t\tgs.info(\"Total Elapsed Time Milliseconds: \" + this.getElapsedTimeMilliseconds(), \"StopWatch\");\n\t\t}\n\t},\n\n\t/**\n\t * \n\t * @returns the durations in seconds\n\t */\n\tgetElapsedTimeSeconds: function () {\n\t\tif(!this.endDateTime) throw new Error(\"Please call stop the watch by calling the Stop() method first.\");\n\t\treturn gs.dateDiff(this.startDateTime, this.endDateTime, true);\n\t},\n\n\t/**\n\t * \n\t * @returns the durations in milliseconds\n\t */\n\tgetElapsedTimeMilliseconds: function () {\n\t\tif(!this.endDateTime) throw new Error(\"Please stop the watch by calling the Stop() method first.\");\n\t\treturn this.endDateTime.getNumericValue() - this.startDateTime.getNumericValue();\n\t},\n\n\ttype: 'Stopwatch'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Store data in User Session/README.md",
    "content": "\nThe GlideSession API allows you to store the client data in session and retrieve it.\n\nFollowing are the example of the usage:\n\n```var sample = {'name':'xyz','email':'xyz@abc.com'};```\n\n```var sessionUpdate = new storeDataInSession();```\n\n```gs.print(sessionUpdate.putDataInSession(sample));```\n\n```var session = gs.getSession();```\n\n```gs.print(session.getClientData('name'));```\n\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Store data in User Session/storeDataInSession.js",
    "content": "var storeDataInSession = Class.create();\nstoreDataInSession.prototype = {\n    initialize: function() {},\n\n    putDataInSession: function(jsData) //pass the data in json format\n    {\n        var session = gs.getSession();\n        \n            for (var j in jsData) {\n                if (this.checkIfDataIsPresent(j) == 'true') {\n                    this.clearSessionData(j);\n\n                }\n\t\t\t\t\n                session.putClientData(j, jsData[j]);\n            }\n        \n        \n    },\n    checkIfDataIsPresent: function(key) {\n        var session = gs.getSession();\n        var clientData = session.getClientData(key);\n        if (clientData == null || clientData == undefined)\n            return 'true';\n        else\n            return 'false';\n    },\n    clearSessionData: function(key) {\n        var session = gs.getSession();\n        session.clearClientData(key);\n    },\n\n    type: 'storeDataInSession'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/StripHTML/README.md",
    "content": "# StripHTML\nScript Include that removes all HTML from a specific string.\n\nIt doesn't use the typical `Class.create`, instead it is a simple javascript function.\nCheck out this blog post for more info about the \"Function Pattern\": https://codecreative.io/blog/interface-design-patterns-function-pattern/\n\n## Example Script\n```javascript\nvar someHTML = \"<span><b>Eaque pariatur nemo facere accusantium non enim.</b></span>\";\nvar plainText = StripHTML(someHTML);\ngs.debug(plainText);\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/StripHTML/StripHTML.js",
    "content": "var StripHTML = function(htmlStr, keepLinebreaks) {\n    //GlideSPScriptable will not preserve linebreaks\n\tif (!keepLinebreaks) return GlideSPScriptable().stripHTML(htmlStr);\n\telse {\n        //so unfortunately we have to use regex to keep linebreaks\n\t\tvar strippedStr = htmlStr.replace(/<br\\s*\\/?>/gi, \"\\n\");\n\t\tstrippedStr = strippedStr.replace(/<\\/p>/gi, \"\\n\");\n\t\tstrippedStr = strippedStr.replace(/<\\/?[^>]+(>|$)/g, \"\");\n\t\treturn strippedStr;\n\t}\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/SubProdLogger/README.md",
    "content": "# ArtifactRank\n\nLogger script include that helps you to specify the environments that you want create log records without commenting\nyour logging lines in your code. The logger will check if it is one of the sub-prod environments that you have specified\nand log the line otherwise just ignore it. This means you just configure the logger once and then you don't have to\ncomment or remove your logging code at all!\n\n## Usage\n\n```javascript\nvar logger = new SubProdLogger(); // This will log for the default environments that is configured e.g. 'test', \"uat\", 'stage', 'qa', 'dev'. If you have other environment that you want to log for then register it in the line 35 if the SubProdLogger.js file i.e add new values here: this._subProdKeywords = ['test', \"uat\", 'stage', 'qa', 'dev'];\n\nlogger.warn('This is a warning.');\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/SubProdLogger/SubProdLogger.js",
    "content": "var SubProdLogger = Class.create();\n\n/**\n * Logs messages to system log only for sub-prods\n * \n * usage:\n * \n * var logger = new SubProdLogger(); // With default sub prod keywords and log prefix\n * \n * OR\n * \n * var logger = new SubProdLogger([\"dev\",\"uat\"], \"Rahman Logs:\");\n * \n * logger.warn(\"This is a warning.\");\n */\nSubProdLogger.prototype = {\n\n    /// properties\n    _subProdKeywords: null,\n    _logPrefix: null,\n    _instanceUrl: '',\n    _logApplicable: false,\n\n    /**\n     * Constructor\n     * @param {array} subProdKeywords array of sub-prod names e.g. partial url [\"dev\", \"uat\"] or full url [\"https://dev11111.service-now.com\"]\n     * @param {string} logPrefix prefix to be added to the log otherwise will be defaulted to\n     */\n    initialize: function (subProdKeywords, logPrefix) {\n        this._subProdKeywords = subProdKeywords;\n        this._logPrefix = logPrefix;\n\n        if (!this._subProdKeywords) {\n            // This is where you specify which environments to logger to log\n            this._subProdKeywords = ['test', \"uat\", 'stage', 'qa', 'dev'];\n        }\n\n        if (!this._logPrefix) {\n            this._logPrefix = \"VF:\";\n        }\n\n        // Do it once!\n        this._instanceUrl = gs.getProperty('glide.servlet.uri');\n        this._logApplicable = this._shouldLog();\n    },\n\n    /**\n     * Logs warning message\n     * @param {string} msg message to be logged\n     */\n    warn: function (msg) {\n        if (this._logApplicable) {\n            gs.warn(this._logPrefix + msg);\n        }\n    },\n\n    /**\n     * Logs error message\n     * @param {string} msg message to be logged\n     */\n    error: function (msg) {\n        if (this._logApplicable) {\n            gs.error(this._logPrefix + msg);\n        }\n    },\n\n    /**\n     * Logs info message\n     * @param {string} msg message to be logged\n     */\n    info: function (msg) {\n        if (this._logApplicable) {\n            gs.info(this._logPrefix + msg);\n        }\n    },\n\n    // Helper functions\n\n    /**\n     * Checks the URL of the instance and finds out if it's a non-prod environemnt based on \n     * the _subProdKeywords values e.g. https://dev1111.service-now.com will be considered as\n     * sub-prod for the _subProdKeywords=[\"dev\"]\n     */\n    _shouldLog: function () {\n        return this._stringContains(this._instanceUrl, this._subProdKeywords);\n    },\n\n    /**\n     * \n     * @param {string} stringToSearch instance url\n     * @param {*} fragments array of the sub-prod names\n     */\n    _stringContains: function (stringToSearch, fragments) {\n\n        for (var i = 0; i < fragments.length; i++) {\n            if (stringToSearch.indexOf(fragments[i]) > -1) {\n                return true;\n            }\n        }\n\n        return false;\n    },\n\n    type: 'SubProdLogger'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Table List Copy Context Options/Copy Field Display Value Context Menu.js",
    "content": "//See readme for full setup\n/**\n  * Script executed on the Client for this menu action \n  *\n  * The following variables are available to the script:\n  *    'g_list' the GlideList2 that the script is running against (only valid for List context menus)\n  *    'g_fieldName' the name of the field that the context menu is running against (only valid for List context menus)\n  *    'g_sysId' the sys_id of the row or form that the script is running against\n  *    'rowSysId' is also set to the sys_id of the row to support legacy actions, but g_sysId is preferred\n  */\n runContextAction();\n\n function runContextAction() {\n     var ga = new GlideAjax('ListCopyOptions'); \n     ga.addParam('sysparm_name','getDisplayValue'); \n     ga.addParam('sysparm_sys_id', g_sysId); \n     ga.addParam('sysparm_table', g_list.getTableName());\n     ga.addParam('sysparm_field', g_fieldName);\n     ga.getXML(updateContext); \n     \n     function updateContext(response){\n         var answer = response.responseXML.documentElement.getAttribute(\"answer\"); \n         copyToClipboard(answer); \n     }\n }"
  },
  {
    "path": "Server-Side Components/Script Includes/Table List Copy Context Options/Copy Field Name Context Menu.js",
    "content": "//See readme for full setup\n/**\n  * Script executed on the Client for this menu action \n  *\n  * The following variables are available to the script:\n  *    'g_list' the GlideList2 that the script is running against (only valid for List context menus)\n  *    'g_fieldName' the name of the field that the context menu is running against (only valid for List context menus)\n  *    'g_sysId' the sys_id of the row or form that the script is running against\n  *    'rowSysId' is also set to the sys_id of the row to support legacy actions, but g_sysId is preferred\n  */\n runContextAction();\n \n function runContextAction() {\n     copyToClipboard(g_fieldName); \n }"
  },
  {
    "path": "Server-Side Components/Script Includes/Table List Copy Context Options/Copy Field Value Context Menu.js",
    "content": "//See readme for full setup\n/**\n  * Script executed on the Client for this menu action \n  *\n  * The following variables are available to the script:\n  *    'g_list' the GlideList2 that the script is running against (only valid for List context menus)\n  *    'g_fieldName' the name of the field that the context menu is running against (only valid for List context menus)\n  *    'g_sysId' the sys_id of the row or form that the script is running against\n  *    'rowSysId' is also set to the sys_id of the row to support legacy actions, but g_sysId is preferred\n  */\n runContextAction();\n \n function runContextAction() {\n\t var this_gr = new GlideRecord(g_list.getTableName());\n\t this_gr.get(g_sysId);\n     copyToClipboard(this_gr.getValue(g_fieldName)); \n }"
  },
  {
    "path": "Server-Side Components/Script Includes/Table List Copy Context Options/README.md",
    "content": "# Add \"Copy Field Name, Value, Display Value\" to context menu for list records\n\nAdd context menu options allowing for admins to be able to right click a record's field in the list view and choose \"Copy Field Name\", \"Copy Field Value\", and \"Copy Field Display Value\" to quickly get the column variable name and values to their clipboard.\n\n## Setting up\n\n- Create a script include and copy the .js file. Set it as `Client callable = true`\n- Create sys_ui_context_menu (Context Menu) records, one each for:\n    - Copy Field Value\n    - Copy Field Name\n    - Copy Field Display Value\n\n## Context Menu records configuration\n\n- Table: Global [global]\n- Menu: List row\n- Type: Action\n- Name: Copy Field Value\n- Order: Use 51, 52, and 53\n- Acive: True\n- Run onShow script: False\n- Condition: `gs.hasRightsTo(\"ui/context_menu.copy_sysid/read\", null)`\n- Action Script: see .js files in this folder for each one"
  },
  {
    "path": "Server-Side Components/Script Includes/Table List Copy Context Options/Script Include.js",
    "content": "//See readme for full setup\nvar ListCopyOptions = Class.create();\nListCopyOptions.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\tgetDisplayValue: function(){\n\t\tvar sys_id = this.getParameter('sysparm_sys_id');\n\t\tvar field = this.getParameter('sysparm_field');\n\t\tvar table = this.getParameter('sysparm_table');\n\t\tvar this_gr = new GlideRecordSecure(table);\n\t\tthis_gr.get(sys_id);\n\t\treturn this_gr.getDisplayValue(field);\n\t},\n\ttype: 'ListCopyOptions'\n});\n"
  },
  {
    "path": "Server-Side Components/Script Includes/TableUtils Extension/Enhanced_TableUtils.js",
    "content": "var Enhanced_TableUtils = Class.create();\n\nEnhanced_TableUtils.prototype = Object.extendsObject(TableUtils, {\n\n    initialize: function(tableName) {\n        TableUtils.prototype.initialize.call(this, tableName);        \n    },\n\n    /**SNDOC\n        @name getFieldsAndAttributes\n\n        @description Returns a data structure with field name and field properties for a given table.\n        OOB getFields() methods from either GlideRecord() or GlideRecordUtil()\n        only work with an existing record and not just with the table name. This one goes\n        to sys_dictionary directly and therefore does not need a valid GlideRecord to work.\n        The returned object has this structure:\n        {\n            <field_name_1>: {\n               field_label: <label>,\n               field_size: <size>,\n               field_type: <type>,\n               reference_table: <table> (only for reference or glide_list types)\n            },\n            <field_name_2>: {\n               ...\n            }\n        }\n\n        @example\n        var fields = new SPOC_TableUtils('incident').getFieldsAndAttributes();\n        for (var fieldName in fields) {\n            gs.print('Field ' + fieldName + ' is of type ' + fields[fieldName].field_type);\n        }\n\n        @returns {object} [fields]\n        */\n\n    getFieldsAndAttributes: function() {\n\n        var fields = {};\n\n        // Get all the table names in the hierarchy and turn it into an array\n        // getHierarchy() is a method from the parent class TableUtils\n\n        var tableHierarchy = this.getHierarchy(this.tableName);\n        \n        // Go find all the fields for all the tables of the hierarchy\n\n        var dicGr = new GlideRecord('sys_dictionary');\n\n        dicGr.addQuery('name', 'IN', j2js(tableHierarchy).join(','));       \n        dicGr.addEncodedQuery('internal_type!=collection^ORinternal_type=NULL');\n        dicGr.query();\n\n        while (dicGr.next()) {\n\n            var fieldName = dicGr.getValue('element');\n\n            fields[fieldName] = {};\n            fields[fieldName].field_label = dicGr.getValue('column_label');\n            fields[fieldName].field_size = dicGr.getValue('max_length');\n\n            fields[fieldName].field_type = dicGr.getValue('internal_type');\n            if (fields[fieldName].field_type === 'reference' || fields[fieldName].field_type === 'glide_list') {\n                fields[fieldName].reference_table = dicGr.getValue('reference');\n            }\n            \n        }\n\n        return fields;\n\n    },\n\n    type: 'Enhanced_TableUtils'\n\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/TableUtils Extension/README.md",
    "content": "## Enhanced_TableUtils Script Include extends out of the box TableUtils\n\nIt has a getFieldsAndAttributes() method that does not require a GlideRecord. Out of the box getFields() methods from either GlideRecord() or GlideRecordUtil() only work with an existing record and not just with the table name. This one goes to sys_dictionary directly and therefore does not need a valid GlideRecord to work.\n\n**Usage**\n\n```\nvar fields = new Enhanced_TableUtils('incident').getFieldsAndAttributes();\ngs.debug('Field caller_id is of type ' + fields.caller_id.field_type + ' (to table ' + fields.caller_id.reference_table + ')');\n```\n\n**Output**\n\n```\n*** Script: [DEBUG] Field caller_id is a reference to table sys_user\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/Testing Script Include Using Jasmine/README.md",
    "content": "# Testing Script Include Using ATF and Jasmine\n\nStep 1.\nCreate a new Script Include based on Sample Calculator Script.js\n\nStep 2.\nCreate a sample ATF Test\n\nStep 3. Add a Test Step by clicking \"Add Test Step\". Update the content of the \"Test Script\" field based on content of the Sample Jasmine Script.js\n\nStep 4. Run the test\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Testing Script Include Using Jasmine/Sample Calculator Script Include.js",
    "content": "var VF_Calculator = Class.create();\nVF_Calculator.prototype = {\n    initialize: function () {\n    },\n\n    add: function (num1, num2) {\n        this._validateNumbers(num1, num2);\n\n        return num1 + num2;\n    },\n\n    divide: function (num1, num2) {\n\n        if (num2 == 0) {\n            throw new Error(\"Divide by zero is not permitted.\");\n        }\n\n        this._validateNumbers(num1, num2);\n\n        return num1 / num2;\n    },\n\n    _validateNumbers: function (num1, num2) {\n        if (!num1 || !num2) {\n            throw new Error(\"Both numbers should be valid.\");\n        }\n    },\n\n    type: 'VF_Calculator'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Testing Script Include Using Jasmine/Sample Jasmine Script.js",
    "content": "(function (outputs, steps, stepResult, assertEqual) {\n\n    describe(\"Test Sample Calculator\", function () {\n\n        it(\"Expect valid numbers returns a result\", function () {\n\n            var calc = new VF_Calculator();\n            var result = calc.divide(4, 2);\n\n            expect(result).toEqual(2);\n        });\n\n\n        // e.g. throw new Error(\"Both numbers should be valid.\");\n        it(\"Expect error if any of the number is null\", function () {\n\n            var calc = new VF_Calculator();\n\n            expect(function () {\n                calc.divide(1, null);\n            }).toThrowError(\"Both numbers should be valid.\");\n\n        });\n\n        it(\"Expect error if both of the number are null\", function () {\n\n            var calc = new VF_Calculator();\n\n            expect(function () {\n                calc.divide(null, null);\n            }).toThrowError(\"Both numbers should be valid.\");\n\n        });\n\n        it(\"Divide by zero should throw exception\", function () {\n\n            var calc = new VF_Calculator();\n\n            expect(function () {\n                calc.divide(5, 0);\n            }).toThrowError(\"Divide by zero is not permitted.\");\n\n        });\n\n    });\n\n})(outputs, steps, stepResult, assertEqual);\n// uncomment the next line to execute this script as a jasmine test\n\njasmine.getEnv().execute();"
  },
  {
    "path": "Server-Side Components/Script Includes/TimeZoneUtils/README.md",
    "content": "# TimeZoneUtils\nThis Script Include is a tool for handling time-zones in ServiceNow. Specifically, this solves the problem of being unable to get a GlideDate/GlideDateTime object in a SPECIFIED time-zone, without having to get a user object for a user who's already in that specific time-zone.\n\n## getGDT()\nGet the GlideDateTime object (as a reference).\n\nThis will return a *reference* to the GlideDateTime object. Note that because of JavaScript's\npass-by-reference jive, you should expect that if you set a variable using this method, then\ncall another method which modifies the GDT object referenced in this class, you will be modifying\nthe object to which your variable is a reference! In other words, your variable will be modified *in-place*.\n\n## setTZ(tz) / setTimeZone(tz)\nNote that you can specify time-zones in a number of formats, like \"US/Pacific\",\n\"US\\\\Eastern\", or by short name (such as \"mountain\").\n\nCurrently, this utility only understands a few time-zones by short name. You can print out a list of\npre-defined these supported short-names by printing out the keys in the timeZones property.\n\nExample: gs.print(Object.keys(new TimeZoneUtils().timeZones));\n\nYou can reference any time-zone using the following (case-sensitive) format:\n\n{Region}\\{Zone}\n\nExample: \"Pacific\\Guam\", or \"America\\Puerto_Rico\"\n\n## getValue()\nGets the value of the current GlideDateTime object.\n\nThis will get the value in SYSTEM-time, so it will return the same value no matter which time-zone you've specified.\n\nIf the GDT's time value was set prior to passing it into TimeZoneUtils, this will return that date/time\nin the specified time-zone.\n\n## getDisplayValue()\nGets the display value of the current GlideDateTime object.\n\nIf a time-zone was specified by calling .setTimeZone(), this will return the time in that time-zone.\n\nIf the GDT's time value was set prior to passing it into TimeZoneUtils, this will return that date/time\nin the specified time-zone.\n\n### Example server-side call (background script)\n```javascript\nvar bmr;\nvar argsArray = [\n\t{\n\t\t\"some_object\": {\n\t\t\t\"some_object\": {\n\t\t\t\t\"some_object\": {\n\t\t\t\t\t\"some_number\": 123,\n\t\t\t\t\t\"some_string\": \"abc123\",\n\t\t\t\t\t\"some_boolean\": 123,\n\t\t\t\t\t\"some_null\": null,\n\t\t\t\t\t\"some_method\": function() {\n\t\t\t\t\t\treturn \"some_string\";\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n];\n\nargsArray.push(JSON.stringify(argsArray[0]));\n\n/*\n\targsArray[0] now contains the object, and argsArray[1] contains the\n\t stringified object.\n\tWe'll test whether it's faster to stringify an object, or to parse an\n\t object string.\n */\n\nbmr = new BenchmarkRunner()\n\nbmr.printComparisonResults(\n\tbmr.compareFunctions(\n\t\tthingOne,\n\t\tthingTwo,\n\t\t20000,\n\t\targsArray\n\t),\n\t20000\n);\n\nfunction thingOne(arrArgs) {\n\tvar strObj = JSON.stringify(arrArgs[0]);\n\treturn strObj;\n}\n\nfunction thingTwo(arrArgs) {\n\tvar objObj = JSON.parse(arrArgs[1]);\n\treturn objObj;\n}"
  },
  {
    "path": "Server-Side Components/Script Includes/TimeZoneUtils/TimeZoneUtils.js",
    "content": "var TimeZoneUtils = Class.create();\nTimeZoneUtils.prototype = {\n\t\n\t/**\n\t * Upon initialization, you can pass in a GlideDateTime object you've already created and set to a specific time.\n\t * The reference to this object will be used, and your GDT will be modified in-place. Alternatively, you may choose\n\t * not to specify a parameter upon initialization, and a new GlideDateTime object will be created, used, and returned\n\t * with the current time in the specified time-zone.\n\t *\n\t * @param {GlideDateTime} [gdt] - A reference to the (optional) GlideDateTime object to be modified IN-PLACE.\n\t * If not specified, a new one will be generated, and a reference returned.\n\t */\n\tinitialize: function(gdt) {\n\t\tthis.gdt = (typeof gdt == 'undefined') ? (new GlideDateTime()) : gdt;\n\t},\n\t\n\t/**\n\t * Get the GlideDateTime object (as a reference).\n\t * This will return a *reference* to the GlideDateTime object. Note that because of JavaScript's\n\t *  pass-by-reference jive, you should expect that if you set a variable using this method, then\n\t *  call another method which modifies the GDT object referenced in this class, you will be modifying\n\t *  the object to which your variable is a reference! In other words, your variable will be modified *in-place*.\n\t * @returns {*|GlideDateTime}\n\t */\n\tgetGDT: function() {\n\t\treturn this.gdt;\n\t},\n\t\n\t/**\n\t * Get the number representing the current GDT object's offset from UTC, in hours.\n\t * If the GlideDateTime object is in the Pacific time zone for example, this method will return either\n\t * \"8\" or \"7\" (depending on DST).\n\t * @returns {number}\n\t */\n\tgetOffsetHours: function() {\n\t\treturn ((Number(this.gdt.getTZOffset() / 1000) / 60) / 60);\n\t},\n\t\n\t/**\n\t * Note that you can specify time-zones in a number of formats, like \"US/Pacific\",\n\t * \"US\\\\Eastern\", or by short name (such as \"mountain\").\n\t *\n\t * Currently, this utility only understands a few time-zones by short name. You can print out a list of\n\t *  pre-defined these supported short-names by printing out the keys in the timeZones property.\n\t *  Example: gs.print(Object.keys(new TimeZoneUtils().timeZones));\n\t *\n\t * You can reference any time-zone using the following (case-sensitive) format:\n\t *  <Region>\\<Zone>\n\t *  Example: \"Pacific\\Guam\", or \"America\\Puerto_Rico\"\n\t *\n\t * @param {Packages.java.util.TimeZone|string} tz - The TimeZone object to use to set the time-zone of\n\t *  the current GlideDateTime object.\n\t * @returns {*|GlideDateTime}\n\t */\n\tsetTimeZone: function(tz) {\n\t\t\n\t\t/*\n\t\t\tFYI: http://twiki.org/cgi-bin/xtra/tzdatepick.html\n\t\t\tClick any of the locations there, and on the corresponding page, find the\n\t\t\t\"Timezone\" value.\n\t\t\tThese are the valid rows for the time-zone parameter.\n \t\t*/\n\t\t\n\t\t//ensure we've got a string and that it's lower-case.\n\t\ttz = (typeof tz === 'string') ? tz : tz.toString();\n\t\t//Validate the TZ string, and get a TimeZone Java object from it.\n\t\ttz = this._getTimeZoneFromString(tz);\n\t\t\n\t\tthis.gdt.setTZ(tz);\n\t\treturn this.gdt;\n\t},\n\t\n\t/**\n\t * Gets the display value of the current GlideDateTime object.\n\t * If a time-zone was specified by calling .setTimeZone(), this will return the time in that time-zone.\n\t * If the GDT's time value was set prior to passing it into TimeZoneUtils, this will return that date/time\n\t * in the specified time-zone.\n\t * @returns {string} The current time, in the specified time-zone.\n\t */\n\tgetDisplayValue: function() {\n\t\treturn this.gdt.getDisplayValue();\n\t},\n\t\n\t/**\n\t * @returns {string} The current value, in SYSTEM time, of the GlideDateTime object.\n\t */\n\tgetValue: function() {\n\t\treturn this.gdt.getValue();\n\t},\n\t\n\t/**\n\t *\n\t * @param {Packages.java.util.TimeZone|string} tz - The TimeZone object to use to set the time-zone of\n\t * @returns {*} The TimeZone object, OR false if an invalid time-zone was passed in.\n\t * @private\n\t */\n\t_getTimeZoneFromString: function(tz) {\n\t\t//If it's a valid time-zone coming in, bob's our uncle.\n\t\tif (this._isValidTimeZone(tz)) {\n\t\t\tif (this.timeZones.hasOwnProperty(tz.toLowerCase())) {\n\t\t\t\treturn this.timeZones[tz.toLowerCase()];\n\t\t\t} else {\n\t\t\t\treturn Packages.java.util.TimeZone.getTimeZone(tz);\n\t\t\t}\n\t\t}\n\t\t//Otherwise, check if it matches one of our timeZone object properties.\n\t\tvar shortTZ = this._getShortTimeZoneName(tz);\n\t\tif (this._isValidTimeZone(shortTZ)) {\n\t\t\treturn this.timeZones[shortTZ.toLowerCase()];\n\t\t}\n\t\t\n\t\t//If nothing else has returned by now, it means the time zone isn't valid.\n\t\tgs.warn('Invalid time zone specified. Time zone: ' + tz, 'TimeZoneUtils Script Include, _getTimeZoneFromString method');\n\t\treturn false;\n\t},\n\t\n\t/**\n\t * Checks if the passed string is a valid time zone string.\n\t * @param {string} tz - The TimeZone string to use to set the time-zone of\n\t * @returns {boolean}\n\t * @private\n\t */\n\t_isValidTimeZone: function(tz) {\n\t\tvar tzObj = Packages.java.util.TimeZone.getTimeZone(tz);\n\t\t//If the tz string wasn't valid, then getID will return the string \"GMT\",\n\t\t//which - unless the user specified GMT as the time-zone, will not match the string argument.\n\t\t//However, if it does match, OR if the arg is found in the timeZones object, then we're good to go.\n\t\treturn ((String(tzObj.getID()) === tz) || this.timeZones.hasOwnProperty(tz.toLowerCase()));\n\t},\n\t\n\t/**\n\t * Try another way of getting the proper time-zone. This is used when to look for a time-zone based only on the short-name.\n\t * @param {string} tz - The time-zone name we're looking at, at a string.\n\t * @returns {string} The time-zone, or a valid version of it if it needs validation, in lower-case.\n\t * @private\n\t */\n\t_getShortTimeZoneName: function(tz) {\n\t\t//Check if the string contains a forward-slash, back-slash, or underscore.\n\t\tif (tz.indexOf('\\\\') >= 0 || tz.indexOf('/') >= 0 || tz.indexOf(' ') >= 0) {\n\t\t\t/*\n\t\t\t\tIf it contains a \"/\" or \"\\\", grab everything after that character.\n\t\t\t\tTrim the resulting (sub-)string.\n\t\t\t\tIf the remainder contains a space, replace it with an underscore.\n\t\t\t */\n\t\t\ttz = tz.slice(tz.indexOf('\\\\') + 1).slice(tz.indexOf('/') + 1).trim().replace(/ /g, '_');\n\t\t}\n\t\treturn tz.toLowerCase();\n\t},\n\t\n\t/**\n\t * Just a reference to the setTimeZone method.\n\t * @param {Packages.java.util.TimeZone|string} tz - The TimeZone object to use to set the time-zone of the current GlideDateTime object.\n\t * @returns {*}\n\t */\n\tsetTZ: function(tz) {\n\t\treturn this.setTimeZone(tz);\n\t},\n\t\n\t/**\n\t * These are the pre-defined short-names for certain common time-zones.\n\t * Feel free to expand upon this object.\n\t \n\t * Currently, this utility only understands a few pre-defined time-zones by short name.\n\t * You can print out a list of these supported short-names by printing out the keys in the timeZones property.\n\t * Example: gs.print(Object.keys(new TimeZoneUtils().timeZones));\n\t * In a future update, this list will update itself with rows from the sys_choice table, here:\n\t * https://YOUR_INSTANCE.service-now.com/sys_choice_list.do?sysparm_query=nameINjavascript%3AgetTableExtensions('sys_user')%5Eelement%3Dtime_zone\n\t */\n\ttimeZones: {\n\t\talaska:      Packages.java.util.TimeZone.getTimeZone('US/Alaska'),\n\t\teastern:     Packages.java.util.TimeZone.getTimeZone('US/Eastern'),\n\t\tcentral:     Packages.java.util.TimeZone.getTimeZone('US/Central'),\n\t\tmountain:    Packages.java.util.TimeZone.getTimeZone('US/Mountain'),\n\t\thawaii:      Packages.java.util.TimeZone.getTimeZone('US/Hawaii'),\n\t\tpacific:     Packages.java.util.TimeZone.getTimeZone('US/Pacific'),\n\t\tarizona:     Packages.java.util.TimeZone.getTimeZone('US/Arizona'),\n\t\tguam:        Packages.java.util.TimeZone.getTimeZone('Pacific/Guam'),\n\t\tpuerto_rico: Packages.java.util.TimeZone.getTimeZone('America/Puerto_Rico'),\n\t\tindia:       Packages.java.util.TimeZone.getTimeZone('Asia/Kolkata'),\n\t\tutc: \t\t Packages.java.util.TimeZone.getTimeZone('UTC')\n\t},\n\t\n\ttype: 'TimeZoneUtils'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/TinyURLHelper/README.md",
    "content": "This utility Helps to make a tiny url in code. For example, lets say you are creating a custom link\nto a long list of sys_idINa,b,c,etc and want the link to make the link look like this:\nhttps://<instance>.service-now.com/some_table_list.do?sysparm_tiny=3a2bbf87dbdc8890e670d48a489619bf\n\nUse this script include to do that, like below example usage:\n\n```r\nvar myTable = 'some_table_list';\nvar myLongQueryStr = 'sysparm_query=sys_idIN' + encodeURIComponent('pretend,long,list,of,sys_id');\nvar myCustomUrl = new TinyUrlHelper().getSert(table=myTable, queryStr=myLongQueryStr);\n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/TinyURLHelper/TinyUrlHelper.js",
    "content": "/**\n * Help to make a tiny url in code. For example, lets say you are creating a custom link\n * to a long list of sys_idINa,b,c,etc and want the link to make the link look like this:\n * https://<instance>.service-now.com/some_table_list.do?sysparm_tiny=3a2bbf87dbdc8890e670d48a489619bf\n * Use this script include to do that, like this:\n * var myTable = 'some_table_list';\n * var myLongQueryStr = 'sysparm_query=sys_idIN' + encodeURIComponent('pretend,long,list,of,sys_id');\n * var myCustomUrl = new TinyUrlHelper().getSert(table=myTable, queryStr=myLongQueryStr);\n *\n * NOTES:\n *    * if gs.getProperty('glide.use_tiny_urls') is false, then returns long url\n *    * The long url lenghth must be >= to gs.getProperty('glide.tiny_url_min_length', '1024')\n *        else, longUrl is returned\n */\nvar TinyUrlHelper = Class.create();\nTinyUrlHelper.prototype = {\n\tinitialize: function() {\n\t\tthis.tinyEnabled = (gs.getProperty('glide.use_tiny_urls', 'false') == 'true');\n\t\tthis.minLength = Number(gs.getProperty('glide.tiny_url_min_length', '1024'));\n\t\tthis.instanceName = gs.getProperty('instance_name');\n\t},\n\n\t/*\n\t * Return the corresponding tiny url, if it already exists, else Insert a new tiny url and return it\n\t * NOTES:\n\t *    * if gs.getProperty('glide.use_tiny_urls') is false, then returns long url\n\t *    * The long url lenghth must be >= to gs.getProperty('glide.tiny_url_min_length', '1024')\n\t *        else, longUrl is returned\n\t *\n\t * @param {string} table, the table part of the query. If you mean list view, append _list\n\t * @param {string} queryStr, the query string part of the long url\n     * @return {string} the tinyUrl or the longUrl, depending, see NOTES above\n\t*/\n\tgetSert: function(table, queryStr) {\n\t\tvar tinyHash, longUrl, grSysTinyUrl, tinyUrl;\n\t\tgs.debug('TinyUrlHelper.getSert:: table=' + table + ', queryStr=' + queryStr);\n\t\tlongUrl = 'https://' + this.instanceName + '.service-now.com/' + table + '.do?' + queryStr;\n\t\tif (!this.tinyEnabled || longUrl.length < this.minLength) {\n\t\t\tgs.debug('TinyUrlHelper.getSert:: disabled or too short. longUrl=' + longUrl);\n\t\t\treturn longUrl;\n\t\t}\n\t\t\n\t\ttinyHash = this._hashCode(queryStr);\n\t\tgs.debug('TinyUrlHelper.getSert:: tinyHash = ' + tinyHash);\n\n\t\tgrSysTinyUrl = new GlideRecord('sys_tiny_url');\n\t\tgrSysTinyUrl.addQuery('data_hash', tinyHash);\n\t\tgrSysTinyUrl.query();\n\t\tif (grSysTinyUrl.next()) {\n\t\t\tgs.debug('TinyUrlHelper.getSert:: already');\n\t\t} else {\n\t\t\tgs.debug('TinyUrlHelper.getSert:: insert');\n\t\t\tgrSysTinyUrl.initialize();\n\t\t\tgrSysTinyUrl.setValue('data', queryStr);\n\t\t\tgrSysTinyUrl.setValue('data_hash', tinyHash);\n\t\t\tgrSysTinyUrl.setValue('tiny_url', gs.generateGUID());\n\t\t\tgrSysTinyUrl.insert();    \n\t\t}\n\t\ttinyUrl = 'https://' + this.instanceName + '.service-now.com/' + table + '.do?sysparm_tiny=' + grSysTinyUrl.getValue('tiny_url');\n\t\treturn tinyUrl;\n\t},\n\t\n\n\n\t_hashCode: function(s) {\n\t\tvar x = 0;\n\t\tvar l = s.length;\n\t\tvar i = 0;\n\t\tif ( l > 0 ) {\n\t\t\twhile (i < l) {\n\t\t\t\tx = (x << 5) - x + s.charCodeAt(i++) | 0;\n\t\t\t}\n\t\t}\t\t\n\t\treturn x;\n\t},\n\n\ttype: 'TinyUrlHelper'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/TranslationUtil/README.md",
    "content": "Script include is  created as translationUtil for dynamic language translation.  for example english to French\n\nThis script include for language translation will invoke flow designer action and sublfow to complete the real time language transaltion for instance suppose group table is updated with new group record having english as description text that can't be translated using OOTB translation tables in such scenario this UTIL will be a saviour\n\nThe properties referred in this translation util is DUMMY name that needs to be replaced with actual property name \n\nYou need to Identify the AI translator for your language and update accordingly. \n\nMore details....\n\nThis PR created for script include that contain TranslationUtil script and readme file that descrive 'How it server the purpose for Dynamic field translation using specific translator'\n\nIt will Fetches and calculates runtime limits for translation requests from system properties. Further it will dynamically retrieves the translation API key using getSubscriptionKey().\n\nThe scope of this utility ranges from multilingual support based on the user's preferred language to dynamic field translation through integration with Flows and Actions.\n\nThe scope of this PR & SI to provide a translationUtil, the custom flow action and subflow is not within the scope of this Util, if anyone wants to use it they need to create there own subflow that detect and translate the language by using this SI.\n\nDetails of Utils\n\nTranslationUtils is a custom Script Include, created to manage dynamic text translation and language detection without depending on ServiceNow’s Out-of-the-box (OOTB) Translation plugin (like Dynamic Translation or Localization framework).\nHow it Works?\n\nIt will look for Custom REST connections (via http_connection and api_key_credentials)\nFlow actions / subflows for actual translation and detection (global.detect_language, global.hhfksjhd__translate_text)\nCustom batching, size limits, and buffer logic to optimize translation requests and avoid API overflows.\nThis SI will do the translation & detection of texts & give the translated data as JSON output\nHOPE THIS HELPS TO CLARIFY.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/TranslationUtil/translationUtil.js",
    "content": "/* \nThis SI is a translationUtil to for dynamic language translation for example english to French\n\nThis script include for language translation will invoke flow designer action and sublfow to complete the real time language transaltion\n\nThe properties referred in this translation util is DUMMY name that needs to be replaced with actual property name \n\nIdentify the right AI translator for language and Update \n*/\nvar TranslationUtils = Class.create();\nTranslationUtils.prototype = {\n    initialize: function() {},\n\n\tgetLimits: function() {\n\n        var charLimit = parseInt(gs.getProperty('mycompany.dynamic.translation.char.limit', 30720));//51200=50KB 30720 = 30KB\n        var requestBufferSize = parseInt(gs.getProperty('mycompany.dynamic.translation.request.buffer', 100));\n        charLimit = (charLimit >= requestBufferSize) ? (charLimit - requestBufferSize) : charLimit;\n        return {\n            'charLimit': charLimit,\n            'arrayLimit': parseInt(gs.getProperty('mycompany.dynamic.translation.array.limit', 10)),\n            'textBufferSize': parseInt(gs.getProperty('mycompany.dynamic.translation.text.buffer', 4))\n        };\n    },\n\n\n    getSubscriptionKey: function() {\n\n         /* try {\n\n            var result = sn_fd.FlowAPI.getRunner().action('<call FD action to get subscription key>').inForeground().run();\n            var get_key_outputs = result.getOutputs();\n            var subscription_key = get_key_outputs.subscription_key;\n\n\t\t\treturn subscription_key;\n\t\t\t\n        } catch (ex) {\n            var message = ex.getMessage();\n            gs.error(message);\n\n        }*/\n\t\ttry {\n\n\t\t\tvar alias = gs.getProperty('mycompany.alias.ia.translation.id');\n\t\t\tvar subscription_key = '';\n\n\t\t\tvar gr_connection = new GlideRecord('http_connection');\n\t\t\tgr_connection.addEncodedQuery('active=true^connection_alias=' + alias);\n\t\t\tgr_connection.query();\n\n\t\t\tif (gr_connection.next()) {\n\t\t\t\tvar gr_api_key = new GlideRecord('api_key_credentials');\n\t\t\t\tgr_api_key.get(gr_connection.getValue('credential'));\n\t\t\t\tsubscription_key = gr_api_key.api_key.getDecryptedValue();\n\t\t\t}\n\n\t\t\treturn subscription_key;\n\t\t\t\n        } catch (ex) {\n            var message = ex.getMessage();\n            gs.error(message);\n        }\n\n    },\n\n\n\tgetKBSize: function(text, limits) {\n        var bytes = text.length;\n        for (var i = text.length - 1; i >= 0; i--) {\n            var code = text.charCodeAt(i);\n            if (code > 0x7f && code <= 0x7ff)\n                bytes++;\n            else if (code > 0x7ff && code <= 0xffff)\n                bytes += 2;\n            if (code >= 0xDC00 && code <= 0xDFFF)\n                i--;\n        }\n        var textBufferSize = limits.textBufferSize;\n        return bytes + textBufferSize;\n    },\n\n\tgetBytesData: function(texts, limits) {\n        var bytesData = {};\n        for (var i = 0; i < texts.length; i++) {\n            var kbSize = this.getKBSize(texts[i], limits);\n            bytesData[texts[i]] = kbSize;\n        }\n        return bytesData;\n    },\n\n\tclassifyBulkTexts: function(texts, charLimit, bytesData) {\n        var count = 0;\n        var classifiedData = {};\n        classifiedData[\"smallTexts\"] = [];\n        classifiedData[\"largeTexts\"] = [];\n        classifiedData[\"isBatchingRequired\"] = false;\n        for (var i = 0; i < texts.length; i++) {\n            if (bytesData[texts[i]] > charLimit) {\n                classifiedData[\"largeTexts\"].push(texts[i]);\n            } else {\n                classifiedData[\"smallTexts\"].push(texts[i]);\n                count = count + bytesData[texts[i]];\n                if (count > charLimit) {\n                    classifiedData[\"isBatchingRequired\"] = true;\n                }\n            }\n        }\n        return classifiedData;\n    },\n\n\tsplitLargeTexts: function(texts, charLimit) {\n\n\t\tvar text_obj = {}; //text_obj = texts[0];\n        var text_transl = []; //text_transl = text_obj[\"texts_to_translate\"];\n\t\tvar text_splited = [];\n\t\t//gs.log(\"charLimit : \" + charLimit +'\\n\\ntext_transl : ' + JSON.stringify(text_transl), \"BELLTR\");\n\t\t//var text_array = texts[0].texts_to_translate;\n\t\t//for (var i = 0; i < text_transl.length; i++) {\n\t\t\t//var txt = text_transl[i];\n\t\t\tvar txt = texts\n\t\t\tvar kbSize = this.getKBSize(txt, limits);\n\t\tgs.log('kbSize : ' + kbSize +'\\n\\ntxt : ' + JSON.stringify(txt), \"TELCOTR\");\n\t\t\tif (kbSize > charLimit) {\n\t\t\t\tvar text = txt\n\t\t\t\tvar size = 0;\n\t\t\t\tvar stop = 0\n\t\tgs.log('text : ' + text, \"TELCOTR\");\n\t\t\t\twhile (size > charLimit && stop < 4) {\n\t\t\t\t\ttext = txt.subString(0, charLimit);\n\t\t\t\t\ttxt = txt.subString(charLimit);\n\t\t\t\t\ttext_splited.push(text);\n\t\tgs.log('text_splited : ' + text_splited, \"TELCOTR\");\n\t\t\t\t\tsize = this.getKBSize(txt, limits) \n\t\t\t\t\tstop++;\n\t\t\t\t}\n\t\t\t\ttext_splited.push(txt)\n\t\t\t}\n\t\t\t\n\t\t\n\t\treturn text_splited;\n\t},\n\n\n\taddLargeTextsToArray: function(result, largeTexts, targetLanguages) {\n\n        for (var large = 0; large < largeTexts.length; large++) {\n            this.transformBatchTextsResponse([largeTexts[large]], targetLanguages, result);\n        }\n    },\n\n\tgetSortedBytesMap: function(texts, bytesData) {\n        var bytesList = [];\n        for (var i = 0; i < texts.length; i++) {\n            var kbSize = bytesData[texts[i]];\n            if (kbSize) {\n                bytesList.push([kbSize, texts[i]]);\n            }\n        }\n        return bytesList.sort();\n    },\n\n    getBatchTexts: function(texts, bytesData, limits) {\n        var bytesList = this.getSortedBytesMap(texts, bytesData);\n        var weight = limits.charLimit;\n        var splitTexts = [];\n        var startIdx = 0;\n        var arrayLength = bytesList.length;\n        var endIdx = arrayLength - 1;\n        var textsProcessed = 0;\n        while (textsProcessed < arrayLength) {\n            var singleSplit = [];\n            var tempWeight = 0;\n            while (singleSplit.length != limits.arrayLimit &&\n                endIdx >= 0 &&\n                startIdx <= endIdx &&\n                weight >= (tempWeight + bytesList[endIdx][0])) {\n                tempWeight += bytesList[endIdx][0];\n                singleSplit.push(bytesList[endIdx][1]);\n                endIdx -= 1;\n                textsProcessed += 1;\n            }\n            while (singleSplit.length != limits.arrayLimit &&\n                startIdx < arrayLength &&\n                startIdx <= endIdx &&\n                weight >= (tempWeight + bytesList[startIdx][0])) {\n                tempWeight += bytesList[startIdx][0];\n                singleSplit.push(bytesList[startIdx][1]);\n                startIdx += 1;\n                textsProcessed += 1;\n            }\n            splitTexts.push(singleSplit);\n        }\n        return splitTexts;\n    },\n\n\n\ttransformBatchTextsResponse: function(texts, targetLanguages, result) {\n        for (var i = 0; i < targetLanguages.length; i++) {\n            result.push({\n                \"texts_to_translate\": texts,\n                \"target_language\": targetLanguages[i]\n            });\n        }\n    },\n\n\taddSmallTextsToArray: function(smallTexts, result, limits, targetLanguages) {\n        var tempSmallTexts = [];\n        for (var i = 0; i < smallTexts.length; i++) {\n            tempSmallTexts.push(smallTexts[i]);\n            if (tempSmallTexts.length == limits.arrayLimit) {\n                this.transformBatchTextsResponse(tempSmallTexts, targetLanguages, result);\n                tempSmallTexts = [];\n            }\n        }\n        if (tempSmallTexts.length > 0) {\n            this.transformBatchTextsResponse(tempSmallTexts, targetLanguages, result);\n        }\n\n    },\n\n\tsplitInputTextsIntoBatches: function(texts, limits, targetLanguages) {\n        var result = [];\n        var bytesData = this.getBytesData(texts, limits);\n        var classifiedData = this.classifyBulkTexts(texts, limits.charLimit, bytesData);\n        if (classifiedData[\"isBatchingRequired\"]) {\n            var splitTexts = this.getBatchTexts(classifiedData.smallTexts, bytesData, limits);\n\t\t\n            for (var idx = 0; idx < splitTexts.length; idx++) {\n                var selectedTexts = splitTexts[idx];\n                this.transformBatchTextsResponse(selectedTexts, targetLanguages, result);\n            }\n        } else {\n            this.addSmallTextsToArray(classifiedData.smallTexts, result, limits, targetLanguages);\n        }\n        this.addLargeTextsToArray(result, classifiedData.largeTexts, targetLanguages);\n        return result;\n    },\n\n\n\tbatchBulkTexts: function(texts, targetLanguages) {\n        var limits = this.getLimits();\n\t\t//var test = this.splitLargeTexts(texts, limits.charLimit)\n\t\t//gs.log('test: ' + JSON.stringify(test), \"TELCOTR\")\n        return this.splitInputTextsIntoBatches(texts, limits, targetLanguages);\n    },\n\n    _getProcessedTextResult: function(textResult) {\n        var processedTextResult = {};\n        var textResultKeys = Object.keys(textResult);\n        for (var keyIdx = 0; keyIdx < textResultKeys.length; keyIdx++) {\n            var key = textResultKeys[keyIdx];\n            if (key == 'text_translations') {\n                var textTranslations = [];\n                var languages = Object.keys(textResult.text_translations);\n                for (var idx = 0; idx < languages.length; idx++) {\n                    var language = languages[idx];\n                    var textTranslation = {\n                        'translated_text': textResult.text_translations[language],\n                        'target_language': language\n                    };\n                    textTranslations.push(textTranslation);\n                }\n                processedTextResult.text_translations = textTranslations;\n            } else {\n                processedTextResult[key] = textResult[key];\n            }\n        }\n        return processedTextResult;\n    },\n\n    rearrangeJSONResult: function(texts, result, isTranslation) {\n        var response = {\n            'status': 'Success'\n        };\n        var rearrangedResponse = [];\n        for (var i = 0; i < texts.length; i++) {\n            var eachTextResult = result[texts[i]];\n            if ('Error' === eachTextResult.status) {\n                response['status'] = 'Error';\n            }\n            var processedTextResult = this._getProcessedTextResult(eachTextResult);\n            rearrangedResponse.push(processedTextResult);\n        }\n        if (isTranslation)\n            response['translations'] = rearrangedResponse;\n        else\n            response['detections'] = rearrangedResponse;\n        return response;\n    },\n\n\n\tdetectLanguage: function(texts) {\n\n\t\ttry {\n\t\t\tvar inputs = {};\n\t\t\tinputs['texts'] = texts; // Array.String \n\n\t\t\t// Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.\n\t\t\t// sn_fd.FlowAPI.getRunner().subflow('<detect langugage FD Action>').inBackground().withInputs(inputs).run();\n\t\t\t\t\t\n\t\t\t// Execute Synchronously: Run in foreground. Code snippet has access to outputs.\n\t\t\tvar result = sn_fd.FlowAPI.getRunner().subflow('<detect langugage FD Action>').inForeground().withInputs(inputs).run();\n\t\t\tvar outputs = result.getOutputs();\n\n\t\t\t// Get Outputs:\n\t\t\t// Note: outputs can only be retrieved when executing synchronously.\n\t\t\tvar detections = outputs['detections']; // Array.Object\n\t\t\tvar status = outputs['status']; // Choice\n\t\t\tvar rest_calls = outputs['rest_calls']; // Integer\n\n\t\t\treturn detections;\n\t\t\t\n\t\t} catch (ex) {\n\t\t\tvar message = ex.getMessage();\n\t\t\tgs.log(message, \"AI Translator - detectLanguage\");\n\t\t}\n\n\t\treturn '';\n\t\t\n\t},\n\n\ttranslateFields: function(texts, source_language, target_languages, additional_parameters) {\n\n\t\ttry {\n\n\t\t\tvar inputs = {};\n\t\t\tinputs['texts'] = texts; // Array.String \n\t\t\tinputs['source_language'] = source_language; // String \n\t\t\tinputs['target_languages'] = target_languages; // Array.String \n\t\t\tinputs['additional_parameters'] = additional_parameters; // Array.Object \n\n\t\t\t// Start Asynchronously: Uncomment to run in background. Code snippet will not have access to outputs.\n\t\t\t// sn_fd.FlowAPI.getRunner().subflow('<FD ACTION for Text Translation>').inBackground().withInputs(inputs).run();\n\t\t\t\t\t\n\t\t\t// Execute Synchronously: Run in foreground. Code snippet has access to outputs.\n\t\t\tvar result = sn_fd.FlowAPI.getRunner().subflow('global.<FD ACTION for Text Translation>').inForeground().withInputs(inputs).run();\n\t\t\tvar outputs = result.getOutputs();\n\n\t\t\t// Get Outputs:\n\t\t\t// Note: outputs can only be retrieved when executing synchronously.\n\t\t\tvar translations = outputs['translations']; // Array.Object\n\t\t\tvar status = outputs['status']; // Choice\n\t\t\tvar rest_calls = outputs['rest_calls']; // Integer\n\n\t\t\treturn translations;\n\t\t\t\n\t\t} catch (ex) {\n\t\t\tvar message = ex.getMessage();\n\t\t\tgs.log(message, \"\n\t\t\tAI Translator - translateFields\");\n\t\t}\n\n\t\treturn '';\n\n\t},\n\n\n    type: 'TranslationUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Translations Import/README.md",
    "content": "# Translations import\n\n`runImportNow` is designed to load language files for the specified application scope name.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Translations Import/script.js",
    "content": "function runImportNow(scopeName) {\n  if (gs.tableExists(\"sys_language\")) {\n    var gr = new GlideRecord(\"sys_language\");\n    gr.addActiveQuery();\n    gr.query();\n\n    while (gr.next()) {\n      var lName = gr.name.toLowerCase();\n\n      if (lName !== \"english\") {\n        if (lName.contains(\" - \")) lName = lName.replace(\" - \", /-/g);\n        else if (lName.contains(\" \")) lName = lName.replaceAll(\" \", \"_\");\n\n        var tl = new global.TranslationLoader();\n        tl.languageAbbreviation = tl.getLanguageAbbreviation0(\"com.snc.i18n.\" + lName);\n\n        tl.loadTranslationsFromAppPlugin(scopeName);\n        gs.info(\"Completed loading application language files for: \" + scopeName + \" \" + lName);\n      }\n    }\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/UnloadXml/README.md",
    "content": "# Unload XML\nWith this script include you can export a GlideRecord Query to XML which can be imported via the \"Import XML\" functionality.\n\n```javascript\nvar exportedXmlStr = UnloadXml(\"incident\", \"active=true\");\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/UnloadXml/script.js",
    "content": "var UnloadXml = function(table, encQuery) {\n\tvar xmlStr = '<unload>';\n\tvar grTable = new GlideRecord(table);\n\tgrTable.addEncodedQuery(encQuery);\n\tgrTable.query();\n\twhile (grTable.next()) {\n\t\tvar xml = new GlideRecordXMLSerializer();\n\t\tvar result = xml.serialize(grTable);\n\t\tresult = result.replace('<?xml version=\"1.0\" encoding=\"UTF-8\"?>', '');\n\t\txmlStr += result.replace('<' + table + '>', '<' + table + ' action=\"INSERT_OR_UPDATE\">') + '\\n';\n\t}\n\txmlStr += '</unload>';\n\treturn xmlStr;\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/UpdateWatchlistFromJSON/README.md",
    "content": "UseCase - \n\nUpdate the watchlist of any table record with the provided JSON payload which should maintain the previous watchlist user and add new one from the payload\n\nPayload can be Array, String, List of String\n\n//Passing List of String of SysId of users\nvar payload = '43435efdsre4t5953439434,43434343436fdfsd343,frtgr6565hg676767gt';\nupdateWatchlistFromJSON('incident','a1b2c3d4e5f678901234567890abcdef', payload);\n\n//Passing Array of String of SysId of users\nvar payload = '[43435efdsre4t5953439434,43434343436fdfsd343]';\nupdateWatchlistFromJSON('incident','a1b2c3d4e5f678901234567890abcdef', payload);"
  },
  {
    "path": "Server-Side Components/Script Includes/UpdateWatchlistFromJSON/script.js",
    "content": "function updateWatchlistFromJSON(tableName, recordSysId, jsonPayload) {\n\t// Check for valid 'watch_list' data\n    \tif (jsonPayload) {\n        \tgs.error(\"JSON must not be empty\");\n        \treturn;\n    \t}    \n        var data = JSON.parse(jsonPayload);\n\t// Check if data is array else convert List of string to array\n\tvar newWatchers = Array.isArray(data) ? data : data.split(',');\n\n\t// fetch the record by table name and record sys id\n    \tvar gr = new GlideRecord(tableName);\n    \tif (gr.get(recordSysId)) {\n\t\t// get the existing watchlist data\n        \tvar existingWatchlist = gr.watch_list ? gr.watch_list.split(',') : [];\n\t\t\n\t\t// Add the new watchlist sys ids into exisitng watchlist\n        \tfor (var i = 0; i < newWatchers.length; i++) {\n            \t\tvar watcher = newWatchers[i];\n                \texistingWatchlist.push(watcher);\n            \t}\n\n        }\n\t// Remove the duplicate by using SET \n\tvar watcherSet = new Set(existingWatchlist);\n\t// Update the newlist into watchlist\n        gr.watch_list = Array.from(watcherSet).join(',');\n        gr.update();\n\n        gs.info(\"Watchlist updated successfully for Table \"+tableName+\": \" + gr.number);\n    } else {\n        gs.error(\"Record not found for Table \"+tableName+\": \" + recordSysId);\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/UserCriteriaUtil/README.md",
    "content": "# UserCriteriaUtil\nThis Script Include helps to evaluate if a certain UserCriteria did match with a User.\n\nIt doesn't use the typical `Class.create`, instead it is a simple javascript function.\nCheck out this blog post for more info about the \"Function Pattern\": https://codecreative.io/blog/interface-design-patterns-function-pattern/\n\n## Example Script\n```javascript\nvar myUserCriteriaSysId = gs.getProperty(\"<my_user_criteria_sys_id>\");\n//Will match against the current user\nUserCriteriaUtil().match(myUserCriteriaSysId);\n//Will match against a specific user\nUserCriteriaUtil(\"<user_sys_id>\").match(myUserCriteriaSysId);\n```"
  },
  {
    "path": "Server-Side Components/Script Includes/UserCriteriaUtil/UserCriteriaUtil.js",
    "content": "var UserCriteriaUtil = function(userSysId) {\n\t\n\t//returns all user criteria sys ids which evaluated to true for the current user\n\tvar _list = function() {\n\t\tif (!userSysId) userSysId = gs.getUserID();\n\t\treturn new sn_uc.UserCriteriaLoader.getAllUserCriteria(userSysId);\n\t};\n\t\n\t//checks if a specific user criteria evaluated to true for the current user\n\tvar match = function(userCriteriaSysIds) {\n\t\tvar listResult = _list();\n\t\tvar userCritList = userCriteriaSysIds.split(\",\");\n\t\tfor (var i = 0; i < userCritList.length; i++) {\n\t\t\tif (listResult.indexOf(userCritList[i]) >= 0) return true;\n\t\t}\n\t\treturn false;\n\t};\n\t\n\t//return public functions\n\treturn { match: match };\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/UserUtil/README.md",
    "content": "# UserUtil\n\nA collection of scripts related to some esoteric user functions.\n\n## userMemberOf();\n\nThe conceit of this function was to filter a list of assets assigned to the _members_ of a specific group. Returns the sys_ids of the group members. The argument passed in should be the display name of the group.\n\n### Example\n\nOn a list, in the condition builder, choose **assigned_to.sys_id** (or whatever sys_user field you need), select **is one of** for the operator and enter\n`javascript: new userUtil().userMemberOf(\"Service Desk\")` in the **assigned_to** field.\n\nExample Image\n![userMemberOf example](userMemberOf.png)\n\n## isManager();\n\nReturns true or false if the passed in user has direct reports or not. Useful in ACLs, User Criteria, and various access/UI checks.\n\n### Example\n\n`var manager_check = new userUtil().isManager(gs.getUser()); `gs.info(manager_check);\n\n> False\n\n## getUserAssets()\n\nReturns a comma-separated list of sys_ids for assets belonging to the user. Can be used to retrieve a user's current assets in a workflow for fulfillment activity.\n\n### Example\n\n`var util = new userUtil();`  \n`assets = util.getUserAssets(gs.getUserID());`\n\n> 3a3bf7421bdf9c50470a4267cc4bcb35,5e6930081b8f8c94470a4267cc4bcbaa\n\n\n## getUserEmail()\n\nReturns the email address of the user based on the provided user ID. This can be useful for sending notifications or for logging purposes.\n\n### Example\n\n`var util = new userUtil();`  \n`var email = util.getUserEmail(gs.getUserID());`  \n`gs.info(email);`\n\n> user@example.com\n\n## getUserDepartment()\n\nReturns the department name of the user based on the provided user ID. This can be useful for departmental reporting or access control.\n\n### Example\n\n`var util = new userUtil();`  \n`var department = util.getUserDepartment(gs.getUserID());`  \n`gs.info(department);`\n\n> IT Department\n\n## getCompanyUsers()\n\nReturns a list of users from a specified company who have a specific role. This can be useful for generating reports or managing user access based on company and role.\n\n### Example\n\n```javascript\nvar util = new userUtil();\nvar users = util.getCompanyUsers('company_sys_id', 'role_name');\ngs.info(JSON.stringify(users));\n```\n\n> [{\"email\":\"user1@example.com\",\"firstName\":\"John\",\"lastName\":\"Doe\",\"roleName\":\"role_name\"}, {\"email\":\"user2@example.com\",\"firstName\":\"Jane\",\"lastName\":\"Smith\",\"roleName\":\"role_name\"}]\n\n## emailValidation()\n\nValidates if the provided email exists in the system and returns the user details. This can be useful for form validations or user verifications.\n\n### Example\n\n```javascript\nvar util = new userUtil();\nvar user = util.emailValidation('user@example.com');\ngs.info(user);\n```\n\n> {\"email\":\"user@example.com\",\"firstName\":\"John\",\"lastName\":\"Doe\"}\n"
  },
  {
    "path": "Server-Side Components/Script Includes/UserUtil/UserUtil.js",
    "content": "var userUtil = Class.create();\nuserUtil.prototype = {\n  initialize: function () { },\n\n  //main use of this was to filter a list of assets assigned to the members of a specific group.\n\n  //On a list, in the condition builder, choose the user field you'd like to filter based on the assignee's group membership.\n\n  //Ex: javascript: new userUtil().userMemberOf(\"Service Desk\") in the assigned_to field\n\n  userMemberOf: function (group_name) {\n    var members = new GlideRecord(\"sys_user_grmember\");\n    members.addQuery(\"group.name\", group_name);\n    members.query();\n    var member_list = [];\n    while (members.next()) {\n      member_list.push(members.user.sys_id.toString());\n    }\n    return member_list;\n  },\n\n  isManager: function (userID) {\n    //checks if the user has direct reports. Useful in workflow or if you don't have a group to check against or need\n    //typically pass in logged-in user's sys_id\n    //var userObj = gs.getUserID();\n\n    var managerLookup = new GlideRecord(\"sys_user\");\n    managerLookup.addActiveQuery();\n    managerLookup.addQuery(\"manager\", userObj);\n    managerLookup.query();\n\n    if (managerLookup.next()) {\n      //gs.info(\"True - user has direct reports\");\n      return true;\n    } else {\n      //gs.info(\"False - user not have direct reports\");\n      return false;\n    }\n  },\n\n  //list assets belonging to the user. Can be used to retrieve a user's current assets in a workflow for fulfilment activity\n  getUserAssets: function (user) {\n    var assets = new GlideRecord(\"alm_hardware\");\n    assets.addQuery(\"install_status\", 1);\n    assets.addQuery(\"assigned_to\", user);\n    assets.query();\n\n    var list = [];\n    while (assets.next()) {\n      list.push(assets.sys_id.toString());\n    }\n\n    return list;\n  },\n\n  getCompanyUsers: function (companyId, roleName) {\n\n    var userGR = new GlideRecord('sys_user');\n    userGR.addQuery('company', companyId);\n    userGR.addQuery('roles', 'snc_external');\n    userGR.addQuery('roles', roleName);\n    userGR.query();\n    var users = [];\n    while (userGR.next()) {\n      var user = {};\n      user.email = userGR.email.toString();\n      user.firstName = userGR.first_name.toString();\n      user.lastName = userGR.last_name.toString();\n      user.roleName = roleName;\n      users.push(user);\n    }\n\n    return users;\n  },\n\n  emailValidation: function () {\n    //sysparam_catalog_req_email\n    var catalogReqEmail = this.getParameter('sysparam_email');\n    var userGR = new GlideRecord('sys_user');\n    userGR.addQuery('email', catalogReqEmail);\n    userGR.queryNoDomain();\n\n    var user = {};\n\n    if (userGR.next()) {\n      user.email = userGR.email.toString();\n      user.firstName = userGR.first_name.toString();\n      user.lastName = userGR.last_name.toString();\n    }\n\n    gs.log(\"Users - \" + JSON.stringify(user));\n    return JSON.stringify(user);\n  },\n\n  type: \"userUtil\",\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/Validate Data Before Insert/DataValidationUtils.js",
    "content": "var DataValidationUtils = Class.create();\nDataValidationUtils.prototype = {\n    initialize: function() {},\n\n    validateIncidentData: function(incidentRecord) {\n        var errors = [];\n\n        // Validate short description if the field exists\n        if (incidentRecord.isValidField('short_description')) {\n            if (gs.nil(incidentRecord.short_description)) {\n                errors.push('Short description is required.');\n            }\n        } else {\n            errors.push('Field \"short_description\" does not exist on the record.');\n        }\n\n        // Validate priority if the field exists\n        if (incidentRecord.isValidField('priority')) {\n            if (gs.nil(incidentRecord.priority) || incidentRecord.priority < 1 || incidentRecord.priority > 5) {\n                errors.push('Priority must be between 1 and 5.');\n            }\n        } else {\n            errors.push('Field \"priority\" does not exist on the record.');\n        }\n\n        return errors;\n    },\n\n    type: 'DataValidationUtils'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/Validate Data Before Insert/README.md",
    "content": "<h1>DataValidationUtils - Script Include</h1>\n\n<p><strong>DataValidationUtils</strong> is a reusable Script Include for ServiceNow that provides a set of utility methods to perform validation checks on <code>GlideRecord</code> objects. This Script Include helps enforce data integrity before records are inserted or updated in ServiceNow tables by validating fields like <code>short_description</code> and <code>priority</code> and any others as per your requirements.</p>\n\n<h2>Features</h2>\n<ul>\n  <li><strong>Field Validation with Error Handling</strong>: Validates that required fields (e.g., <code>short_description</code>, <code>priority</code>) are present and correctly populated. If any field fails validation, an error is returned.</li>\n  <li><strong>Field Existence Check</strong>: Prevents runtime errors by ensuring that validation is only performed on fields that exist in the <code>GlideRecord</code>. This is achieved using the <code>isValidField()</code> method, ensuring the script can be safely used across different tables or customized record structures.</li>\n  <li><strong>Flexible and Reusable</strong>: The validation logic is encapsulated in a Script Include, making it reusable across different Business Rules, Client Scripts, or other server-side scripts.</li>\n</ul>\n\n<h2>Usage Example</h2>\n<h3>Validating Incident Data Before Insert</h3>\n<p>This example demonstrates how to use <strong>DataValidationUtils</strong> to validate an incident record before inserting it into the database:</p>\n\n<pre><code>var inc = new GlideRecord('incident');\ninc.initialize();\ninc.short_description = '';  // Missing required field\ninc.priority = 6;  // Invalid priority (out of range)\n\nvar validator = new DataValidationUtils();\nvar validationErrors = validator.validateIncidentData(inc);\n\nif (validationErrors.length > 0) {\n    gs.error('Validation errors: ' + validationErrors.join(', '));\n} else {\n    inc.insert();  // Only insert if validation passes\n    gs.info('Incident created successfully with number: ' + inc.number);\n}\n</code></pre>\n\n<h2>Customization</h2>\n<p>The <strong>DataValidationUtils</strong> can be extended to include validation for additional fields or custom tables by simply adding more checks in the <code>validateIncidentData</code> method, or by creating new validation methods.</p>\n"
  },
  {
    "path": "Server-Side Components/Script Includes/VariableHelper/README.md",
    "content": "## Variable Helper\n\n Working with variables in ServiceNow is no fun sometimes, especially [Multi Row Variable Sets](https://docs.servicenow.com/bundle/paris-application-development/page/script/server-scripting/concept/c_ScriptableServiceCatalogVariables.html#d2332110e207).  For that reason I created a helper Script Include to make my life easier.  \n\n There are 4 parameters that can be passed as an object when instantiating the Script Include or after with setter functions.  All default to false;\n\n\n * <b>useDisplayValue</b>:  This will return the display value of all variables instead of value\n * <b>expandRef</b>      :  This will return any reference field as an object. \n * <b>useLabel</b>       :  Variable/Field Labels will be used instead of name.\n * <b>debug</b>          :  Enable additional logging\n\n\n### Example\n\n```\nvar helperOptions = {\n  \"useLabel\": true,\n  \"useDisplayValue\": true ,\n  \"expandRef\": false\n};\nvar varHelper = new variableHelper(helperOptions); \nvarHelper.setDebug(true); //example of using setter function\n\nvar myVariables = varHelper.getVariables(myGlideRecordObject); //Get an object containing all variables\nvar myMRVS = varHelper.getMRVS(myGlideRecordObject.variables[mrvsName]); //Get a specific MRVS as an array of objects\n  \n```\n"
  },
  {
    "path": "Server-Side Components/Script Includes/VariableHelper/variableHelper.js",
    "content": "var variableHelper = Class.create();\nvariableHelper.prototype = {\n    initialize: function(options) {\n\n        this.useDisplayValue = options.useDisplayValue || false;\n        this.expandRef = options.expandRef || false;\n        this.useLabel = options.useLabel || false;\n\t\tthis.debug = options.debug || false;\n\t\t\n\n    },\n\n    _print: function(msg) {\n        if (!this.debug) {\n            return;\n        }\n\n        gs.info(\"Variable Helper: \" + msg);\n    },\n\n    /**\n     * Description: Parse Multi Row Varible set and return array of objects\n     * Parameters: mrvs - multi row variable set variable\n     * Returns: array\n     */\n    getMRVS: function(mrvs) {\n        var dataToReturn = [];\n        this._print('IN GetMRVS');\n        this._print(mrvs);\n        if (typeof mrvs === 'undefined') {\n            return dataToReturn;\n        }\n\n        this._print('got past blank check');\n\n        var cellObj = {};\n\n        if (this.expandRef) {\n            var questionObj = this._getQuestions(mrvs.getQuestionIds().join(',')) || {};\n            this._print(JSON.stringify(questionObj, null, \"\\t\"));\n\n        }\n\n        var rows = mrvs.getRows();\n        this._print('Row Lengh-' + rows.length);\n        for (var j = 0; j < rows.length; j++) {\n            this._print('Row -' + j);\n            var row = rows[j];\n            var cells = row.getCells();\n            for (var k = 0; k < cells.length; k++) {\n                var cell = cells[k];\n                var varName = cell.getName();\n\t\t\t\tvar objKey = this.useLabel ? cell.getLabel() : varName;\n                this._print('MRVS -' + varName);\n                if (this.expandRef && questionObj[varName].table && questionObj[varName].table != '') {\n                    if ([\"21\"].indexOf(questionObj[varName].type_id) >= 0) {\n                        cellObj[objKey] = this.getMultipleRef(questionObj[varName].table, cell.getCellValue().split(','));\n                    } else {\n                        cellObj[objKey] = this.getRef(questionObj[varName].table, cell.getCellValue());\n                    }\n\n                } else {\n                    cellObj[objKey] = this.useDisplayValue ? cell.getCellDisplayValue() : cell.getCellValue();\n                }\n\n            }\n            dataToReturn.push(cellObj);\n            cellObj = {};\n        }\n        return dataToReturn;\n    },\n    /**\n     * Description: Returns Object of questions with thir type and reference table.  Used for MRVS when the expandRef function is true\n     * Parameters: questionArr - array of sys_id\n     * Returns: Object\n     */\n    _getQuestions: function(questionArr) {\n\n        var q = {};\n        var grQuestion = new GlideRecordSecure('question');\n        grQuestion.addQuery('sys_id', 'IN', questionArr);\n        grQuestion.query();\n        while (grQuestion.next()) {\n            q[grQuestion.getValue('name')] = {\n                \"type\": grQuestion.getDisplayValue('type'),\n                \"type_id\": grQuestion.getValue('type'),\n                \"table\": grQuestion.getValue('reference') || grQuestion.getValue('lookup_table') || grQuestion.getValue('list_table') || grQuestion.getValue('choice_table')\n            };\n        }\n        return q;\n\n    },\n    getMultipleRef: function(tbl, arr) {\n        var arrToReturn = [];\n        for (var i = 0; i < arr.length; i++) {\n            arrToReturn.push(this.getRef(tbl, arr[i]));\n        }\n        return arrToReturn;\n    },\n    /**\n * Description: Return object containing all fields and values of reference field\n * Parameters: \n\t tbl - table name\n\t id - sys_id from reference field\n * Returns: array\n*/\n    getRef: function(tbl, id) {\n        this._print('Table: ' + tbl + \"\\nsys_id: \" + id);\n        if (!tbl || tbl == '' || !id || id == '') {\n            return false;\n        }\n\n\n        var objToReturn = {};\n        var grObj = new GlideRecordSecure(tbl);\n        if (!grObj.get(id)) {\n            return false;\n        }\n\n\n        var fields = grObj.getFields();\n        objToReturn['sys_id'] = grObj.getValue('sys_id');\n        for (var f = 0; f < fields.size(); f++) {\n            var field = fields.get(f);\n            if (field.hasValue()) {\n                objToReturn[this.useLabel ? field.getLabel() :  field.getName()] = this.useDisplayValue ? field.getDisplayValue() : field.getValue();\n            }\n        }\n\n        return objToReturn;\n    },\n\n    /**\n     * Description: Return object containing all variables for a given record.  The object will possibly be nested if expandRef is true\n     * Parameters: rec - GlideRecord\n     * Returns: Object\n     */\n    getVariables: function(rec) {\n        rec = rec || current;\n        if (!rec || rec == '') {\n            return;\n        }\n        var variablesToReturn = {};\n        if (!rec.isValidRecord()) {\n            return variablesToReturn;\n        }\n\n        //var variables = rec.variables;\n\t\tvar variables = rec.variables.getElements(true);\n        var refTable,objKey,question;\n\t\t\n        //for (var v in variables) {\n\t\tfor (var v = 0; v < variables.length;v++) {\n\t\t\tquestion = variables[v].getQuestion();\n\t\t\tobjKey = this.useLabel ? question.getLabel() : v;\n            if (variables[v].isMultiRow()) {\n                this._print(variables[v] + ' MRVS');\n                variablesToReturn[this.useLabel ? variables[v].getLabel() : v] = this.getMRVS(variables[v]);\n            } else {\n                \n                this._print(variables[v] + ' - ' + question.getLabel());\n\t\t\t\t\n                if (this.expandRef) {\n\n                    if (question.type == '21') {\n                        variablesToReturn[objKey] = this.getMultipleRef(question.lookupTable || question.listTable, question.getValue().split(','));\n                    } else {\n                        variablesToReturn[objKey] = this.getRef(question.reference || question.choice_table || question.choiceTable, question.getValue());\n                    }\n\n                } else {\n                    variablesToReturn[objKey] = this.useDisplayValue ? question.getDisplayValue() : question.getValue();\n                }\n            }\n        }\n\n        return variablesToReturn;\n    },\n\n    /**\n * Description: get Record from sys_id.\n * Parameters: \n\t sysid = sys_id of record to retrieve\n\t tbl = table to retrieve record from.  Defaults to task\n * Returns: Object\n*/\n    getRecord: function(sysid, tbl) {\n        if (!sysid || sysid == '') {\n            return;\n        }\n        tbl = tbl || 'task';\n        var grRec = new GlideRecordSecure(tbl);\n        if (!grRec.get(sysid)) {\n            return;\n        }\n\t\t\n\t\tvar recClassName = grRec.getRecordClassName() + '';\n        if (recClassName != tbl) {\n            return this.getRecord(sysid, recClassName);\n        }\n\n        return grRec;\n\n    },\n\n    //Setters\n    setUseDisplayValue: function(val) {\n        if (typeof val === 'undefined') {\n            return;\n        }\n        this.useDisplayValue = val;\n    },\n\n    setExpandRef: function(val) {\n        if (typeof val === 'undefined') {\n            return;\n        }\n        this.expandRef = val;\n    },\n\n    setDebug: function(val) {\n        if (typeof val === 'undefined') {\n            return;\n        }\n        this.debug = val;\n    },\n    setUseLabel: function(val) {\n        if (typeof val === 'undefined') {\n            return;\n        }\n        this.useLabel = val;\n    },\n\n\n\n    type: 'variableHelper'\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/VariableToDescription/README.md",
    "content": "This script include can be used to get all of the variables from a RITM as text. It is client callable, so it can be used from any client script as well as Server Side script like a business rule."
  },
  {
    "path": "Server-Side Components/Script Includes/VariableToDescription/VariableToDescription.js",
    "content": "var VariablesToDescription = Class.create();\nVariablesToDescription.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n\t//For GlideAjax\n    getDescriptionClient: function() {\n        var ritm = this.getParameter('sysparm_ritm');\n\t\tvar descStr = this.getDescription(ritm);\n\t\treturn descStr;\n    },\n\t\n\t//For ServerSide Access\n\tgetDescription: function(ritm) {\n        var descStr = '';\n        var ritmGr = new GlideRecord(\"sc_req_item\");\n        if (ritmGr.get(ritm)) { //sys_id of RITM record\n            descStr = ritmGr.getDisplayValue() + ': ' + ritmGr.cat_item.getDisplayValue() + '\\n';\n            descStr += this.getVariablesAsText(ritm);\n        }\n        return descStr;\n    },\n\n    getVariablesAsText: function(ritm) {\n        var descStr = '';\n        var ritmGr = new GlideRecord(\"sc_req_item\");\n        if (ritmGr.get(ritm)) { //sys_id of RITM record\n            var varDataGr = new GlideRecord('sc_item_option_mtom');\n            varDataGr.addQuery('request_item', ritmGr.getUniqueValue());\n            varDataGr.orderBy('sc_item_option.order');\n            varDataGr.query();\n\n            var question_text = '';\n            var answer_text = '';\n            while (ritmGr._next()) {\n                question_text = varDataGr.sc_item_option.item_option_new.getDisplayValue();\n                answer_text = ritmGr.variables[varDataGr.sc_item_option.item_option_new.name].getDisplayValue();\n                if (!gs.nil(answer_text)) {\n                    descStr += question_text + ': ' + answer_text + '\\n';\n                } else {\n                    descStr += question_text + ': \\n';\n                }\n            }\n        }\n        return descStr;\n    },\n\n    type: 'VariablesToDescription'\n});"
  },
  {
    "path": "Server-Side Components/Script Includes/attachments/README.md",
    "content": "Little utility for attachment types\n\n## Example Script\nvar att = new Attachment(current);\nif(att.hasImage())\n    var attID = att.getImageID();"
  },
  {
    "path": "Server-Side Components/Script Includes/attachments/attachment.js",
    "content": "var Attachment = Class.create();\n\nAttachment.prototype = {\n\tinitialize: function(arg) {\n\t\tthis.current = arg;\n\t},\n\n\thasImage : function() {\n\n\t\tvar attGr = new GlideRecord(Constants.ATTACHMENT_TABLE);\n\t\tattGr.addQuery('table_name', this.current.getTableName());\n\t\tattGr.addQuery('table_sys_id', this.current.getUniqueValue());\n\t\tattGr.addQuery('content_type', 'STARTSWITH', 'image');\n\t\tattGr.setLimit(1);\n\t\tattGr.query();\n\n\t\tif (attGr.hasNext()) {\n\t\t\treturn true;\n\t\t}\n\n\t\treturn false;\n\t},\n\n\tgetImageID : function() {\n\n\t\tvar attGr = new GlideRecord(Constants.ATTACHMENT_TABLE);\n\t\tattGr.addQuery('table_name', this.current.getTableName());\n\t\tattGr.addQuery('table_sys_id', this.current.getUniqueValue());\n\t\tattGr.addQuery('content_type', 'STARTSWITH', 'image');\n\t\tattGr.setLimit(1);\n\t\tattGr.query();\n\n\t\tif (attGr.next()) {\n\t\t\treturn attGr.getUniqueValue();\n\t\t}\n\n\t\treturn;\n\t},\n\n\ttype: 'Attachment'\n};"
  },
  {
    "path": "Server-Side Components/Script Includes/get field values for multiple records from a table/README.md",
    "content": "## Script contains scalable method to get display value of particular field from a table for any number of records filtered by a encoded query\n\n> Method: \\_getFieldDisplayValues(tableName, query, fieldName)\n\n-   @param {String} tableName: Table name\n-   @param {String} query: query to filter the records\n-   @param {String} fieldName: Field name for which display value is required\n\n-   @returns {String OR boolean}: comma separated field display values of filtered records\n    **OR** false if no record/no display value if found on filtered records\n\n### Example Methods\n\n> getUserEmailAddressesBySysIDs(sysIDs)\n\n-   @param {String} sysIDs: Comma separated list of sysIDs (can also be single sysID)\n-   @returns {String}: comma separated email addresses of user profiles sys_id passed in as comma separated values.\n\n> getUserNamesBySysIDs(sysIDs)\n\n-   @param {String} sysIDs: Comma separated list of sysIDs (can also be single sysID)\n-   @returns {String}: comma separated names of user profiles sys_id passed in as comma separated values.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/get field values for multiple records from a table/script.js",
    "content": "var CustomUtils = Class.create();\nCustomUtils.prototype = {\n\tinitialize: function () {},\n\n\t/**\n\t *\n\t * @param {String} tableName: Table name\n\t * @param {String} query: query to filter the records\n\t * @param {String} fieldName: Field name for which display value is required\n\t * @returns {String OR boolean}: comma separated field display values of filtered records\n\t * \t\t\t\t\t\t\t\tOR false if no record/no display value if found on filtered records\n\t */\n\t_getFieldDisplayValues: function (tableName, query, fieldName) {\n\t\tvar result = false;\n\t\tif (tableName && query && fieldName) {\n\t\t\tvar arr = [];\n\t\t\tvar gr = new GlideRecord(tableName);\n\t\t\tgr.addEncodedQuery(query);\n\t\t\tgr.query();\n\t\t\twhile (gr.next()) {\n\t\t\t\tif (!gr[fieldName].nil()) {\n\t\t\t\t\tarr.push(gr[fieldName].getDisplayValue());\n\t\t\t\t}\n\t\t\t}\n\t\t\tresult = arr.join(\", \");\n\t\t}\n\n\t\treturn result;\n\t},\n\n\t/****** Example functions *******/\n\n\t/**\n\t *\n\t * @param {String} sysIDs: Comma separated list of sysIDs (can also be single sysID)\n\t */\n\tgetUserEmailAddressesBySysIDs: function (sysIDs) {\n\t\treturn this._getFieldDisplayValues(\"sys_user\", \"sys_idIN\" + sysIDs, \"email\");\n\t},\n\n\t/**\n\t *\n\t * @param {String} sysIDs: Comma separated list of sysIDs (can also be single sysID)\n\t */\n\tgetUserNamesBySysIDs: function (sysIDs) {\n\t\treturn this._getFieldDisplayValues(\"sys_user\", \"sys_idIN\" + sysIDs, \"name\");\n\t},\n\n\ttype: \"CustomUtils\",\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/getCountFunction/README.md",
    "content": "Introduction :\n\nWe have two script includes in here. Main aim for this is to have a generic count function which can be called from all server side scripts and you don’t have to write GlideAggregate every time. It is scripted in such a way that you can pass table name and query dynamically and get the count.\n\nScript Include Significance :\n1)\tCode.js – This is the generic script include used to return the count of the table passed as parameters.\n2)\tcallingSI.js – This script include shows how to call the code.js script include and how to pass the parameters to get the count dynamically.\n\nParameters in script include to be passed:\n•\tTable Name.\n•\tQuery.\n•\tSys Id which is mentioned as id in callingSI.js\n\nOutput:\nCount of records matching the query.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/getCountFunction/callingSI.js",
    "content": "var IncidentSI = Class.create();\nIncidentSI.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n    type: \"IncidentSI\",\n    generic: new GenericNOW(), \n/**\n     * Retrieve the number of active (non closed) incident tasks\n     * @param {sys_id} id - The id of an incident\n     * @returns {integer} - The number of open incident tasks\n     */\n    getActiveIncTasksCount: function(id) {\n        // Return the count of all open incident tasks associated with the incident\n        var result = this.generic.count({\n            table: \"incident_task\",\n            query: \"active=true^incident=\" + id\n        });\n        if (result.error) {\n            gs.error(\"getActiveIncTasksCount => Error message: \" + result.error);\n        }\n        return result.count;\n    },\n    \n    \n    /** Example calling script\nvar siinc = new GenericNOW();\nvar result = siinc.count({\n            table: \"incident_task\",\n            query: \"active=true^incident=b07db7f9878f345034e864a80cbb35e6\"\n        });\ngs.log(result.count.toString());\n*/\n"
  },
  {
    "path": "Server-Side Components/Script Includes/getCountFunction/code.js",
    "content": "var GenericNOW, _count, _get, _insert, _remove, _update;\nGenericNOW = Class.create();\n\nGenericNOW.prototype = {\n    type: \"GenericNOW\",\n    initialize: function() {},\n\n    count: function() {\n        return _count.apply(this, arguments);\n    },\n    tableExists: function(table) {\n        return new TableUtils(table).tableExists();\n    },\n\n};\n\n\n\n_count = function(arg) {\n    var count, ga, query, res, table;\n    table = arg.table;\n    query = arg.query;\n    if (!table) {\n        return {\n            error: \"Attribute 'table' is not provided\"\n        };\n    }\n    if (query == null) {\n        return {\n            error: \"Attribute 'query' is not provided\"\n        };\n    }\n    if (!this.tableExists(table)) {\n        return {\n            error: \"Provided 'table' is not a valid table\"\n        };\n    }\n    res = {\n        data: {},\n        error: null,\n        count: 0\n    };\n    ga = new GlideAggregate(table);\n    ga.addEncodedQuery(query);\n    ga.addAggregate(\"COUNT\");\n    ga.query();\n    res.count = ga.next() ? (ga.getAggregate(\"COUNT\") * 1) : 0;\n    return res;\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/getGlideRecordObject/README.md",
    "content": "# CustomUtils\n\nA Script utils containing utility functions, patterns and coding standards.\n\n> Notes:\n\n-   Utility method names starts with \"\\_\"\n-   each method is followed by an example code snippet displaying usage of the method.\n\n---\n\n### \\_getGlideRecordObject\n\n> \\_getGlideRecordObject(sysID, tableName)\n\nparameters:\n\n-   sysID: sys_id of the record\n-   tableName: table name of the record\n\nreturns:\n\n-   GlideRecord object of the record OR false if record could not be found\n\nUsage:\n\n-   Instead of having multiple GlideRecord code (when you have access ro record sys_id) in different places in your code, call this method to get the record.\n-   This method will return false if record could not be found.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/getGlideRecordObject/getGlideRecordObject.js",
    "content": "var CustomUtils = Class.create();\nCustomUtils.prototype = {\n\tinitialize: function () {},\n\n\t/**\n\t *\n\t * @param {String} sysID: record sys_id\n\t * @param {String} tableName: record table name\n\t * @returns {glideRecord Obj, boolean}: GlideRecord object of the record OR false\n\t */\n\t_getGlideRecordObject: function (sysID, tableName) {\n\t\tif (sysID && tableName) {\n\t\t\tvar gr = new GlideRecord(tableName);\n\t\t\tgr.get(sysID);\n\t\t\tif (gr) {\n\t\t\t\treturn gr;\n\t\t\t}\n\t\t}\n\n\t\treturn false;\n    },\n    \n    // example _getGlideRecordObject\n    getIncidentState: function (sysID) {\n        var incidentTableName = 'incident';\n\n        var incidentGR = this._getGlideRecordObject(recordSysID, incidentTableName);\n        if (incidentGR) {\n            return incidentGR.getValue('state');\n        }\n\n        return false;\n    }\n\n\ttype: \"CustomUtils\",\n};\n"
  },
  {
    "path": "Server-Side Components/Script Includes/regexCheckerScript/README.md",
    "content": "Script Include to check regex \n\nThis script include can be used in a report or any other script/BR/Action to check if a particular type of text or a field matches the required regex. \nReturns the sys_id of the records that match the regex. You can select your desired table, query and field, also update the required regex in the script. \n\nThankyou.\n"
  },
  {
    "path": "Server-Side Components/Script Includes/regexCheckerScript/regexCheckerScript.js",
    "content": "var regexCheckerScript = Class.create();\nregexCheckerScript.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n\n    getFieldforRegex: function() {\n        var dRecordSysId = [];\n        var grDRecordTable = new GlideRecord('incident'); //ADD YOUR DESIRED TABLE NAME \n        grDRecordTable.addEncodedQuery('active=true'); //ADD YOUR DESIRED QUERY\n        grDRecordTable.query();\n        while (grDRecordTable.next()) {\n            var description = grDRecordTable.getValue('description'); //ADD YOUR DESIRED FIELD HERE\n            var re = YOUR REGEX GOES HERE; //ADD YOUR DESIRED REGEX HERE\n            if (re.test(description)) {\n                dRecordSysId.push(grDRecordTable.sys_id.toString()); //IF MATCHES FIELD IT PUSHES THE SYS_ID TO THE ARRAY\n            }\n        }\n        return dRecordSysId.toString(); //RETURNS ALL THE RECORDS THAT MATCH THE REGEX\n    },\n\n    type: 'regexCheckerScript'\n});\n"
  },
  {
    "path": "Server-Side Components/Server Side/CallScriptIncludeWithParameters/README.md",
    "content": "We will have client callable script include function which takes inputs by using this.getParameter('sysparm_xxx')\n\nSometimes we want to use the same function from server side code, the given code will help us in dealing those situations. \n\nclient callable script include function\n![image](https://github.com/seviceN/code-snippets/assets/46869542/4347802a-baa3-4d57-9967-73b357723d8c)\n\n\ncalling that function from Background script\n![image](https://github.com/seviceN/code-snippets/assets/46869542/582b8a69-63a8-47d8-a22c-cdc856f6f92c)\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/CallScriptIncludeWithParameters/script.js",
    "content": "// client callable script include function using this.getParameter('sysparm_xxx') for inputs. \n//below client callable script include function, checks whether the given sysID and table records exists in SN or NOT !\nvar CheckRecord = Class.create();\nCheckRecord.prototype = Object.extendsObject(AbstractAjaxProcessor, {\n  \n    isValidRecInTbl: function() {\n      \n        var table = this.getParameter('sysparm_table');\n        var recordID = this.getParameter('sysparm_sys_id');\n\n        var recs = new GlideRecord(table);\n        recs.addQuery('sys_id', recordID);\n        recs.query();\n\n        if (recs.next()) {\n            return 'true';\n        } else {\n            return 'false';\n        }\n    },\n  \n    type: 'CheckRecord'\n});\n\n\n// we normally use above like functions to be called from GlideAjax but when we want to call the above from server side we can use below code.\n\n//we can use below code in any server side script \nvar tableToCheck = 'incident';\nvar sysIDOfRec ='df3e9b822fb83110c083d0ccf699b639';\n\nvar scriptIncObj = new global.CheckRecord(); //creating script include class object\n\nscriptIncObj.getParameter = function(name){\n    if(name == 'sysparm_table') return tableToCheck; //passing input value to get used in script include\n    if(name == 'sysparm_sys_id') return sysIDOfRec; //passing input value to get used in script include\n}\n\nvar result = scriptIncObj.isValidRecInTbl(); //calling required function in script include which uses this.getParameter()\n\ngs.info('record '+sysIDOfRec+' exists in '+tableToCheck+ '? '+result)\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/CheckTableExtension/README.md",
    "content": "The snippet validates whether a child table is extended from a parent table. You could provide both the table names as input and it would respond back with a boolean output.\n\nSample Usage\n\ngs.info(isTableExtended(\"cmdb_ci\", \"cmdb_ci_win_server\"));  //true\n\ngs.info(isTableExtended(\"cmdb_ci\", \"cmdb_ci_hardwares\"));   //false\n"
  },
  {
    "path": "Server-Side Components/Server Side/CheckTableExtension/istableextended.js",
    "content": "/***********************************************************/\nInput:\n1. String - parent - Parent table name\n2. String - child - Child table name\n\nOutput:\n1. Boolean: \ntrue - Child table is an extension of parent table\nfalse - Child table is not an extension of parent table\n\n/***********************************************************/\n\nfunction isTableExtended(parent, child){\n    gs.include(\"j2js\");\n    var tu = new TableUtils(parent);\n    if(tu.tableExists() && tu.hasExtensions()){\n        var tableArrayList = tu.getTableExtensions();\n        var tableArray = j2js(tableArrayList);\n        if(tableArray.indexOf(child) > -1){\n            return true;\n        }else{\n            return false;\n        }\n    }else{\n        return false;\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/Create Admin Users/README.md",
    "content": "# Purpose\nAfter first login to a freshly provisioned ServiceNow instance it is recommended creating personalized admin accounts. The following script will do the job for you. Just pass the most important properties to the method and a new user is being created with some preconfigured values such as roles and preferences.\n# Usage\n```javascript\nvar strUserSysID = createAdminUser(\n  'john.doe',\n  'John',\n  'Doe',\n  'john.doe@example.com'\n);\n\nif (strUserSysID != null) {\n    //user has been created successfully\n}\n```\n"
  },
  {
    "path": "Server-Side Components/Server Side/Create Admin Users/create_admin_user.js",
    "content": "    function createAdminUser(strUserName, strFirstName, strLastName, strEmail) {\n\n        try {\n            var grUser               = new GlideRecord('sys_user');\n            var strPreferredLanguage = 'en';\n            var stTimeZone           = 'Europe/Berlin';\n            var strDateFormat        = 'dd.MM.yyyy';\n            var strStartPassword     = 'Start1234!';\n            var strUserSysId         = null;\n            var arrRoles             = [\n                'admin', \n                'workspace_admin',\n                'security_admin',\n            ];\n            var objUserPreferences   = [\n                {'name' : 'glide.ui.application_picker.in_header', 'value' : 'true'},\n                {'name' : 'glide.ui.update_set_picker.in_header', 'value' : 'true'},\n                {'name' : 'glide.ui.related_list_timing', 'value' : 'deferred'},\n                {'name' : 'glide.css.theme.ui16', 'value' : '7547a6e0673102008dd992a557415af1'},\n                {'name' : 'rowcount', 'value' : '100'},\n                {'name' : 'com.glideapp.dashboards.homepage_notification.dont_ask_me_again', 'value' : 'true'},\n            ];        \n\n  \n            //----------------------------------------------------------------------------------\n            //perform some tests on method parameters\n            //----------------------------------------------------------------------------------\n\n            if (!(typeof strUserName === \"string\" && strUserName.trim().length > 3)) {\n                gs.addErrorMessage('Please pass a valid value for Parameter \"strUserName\"!');\n                return null;\n            }\n\n            if (!(typeof strFirstName === \"string\" && strFirstName.trim().length > 2)) {\n                gs.addErrorMessage('Please pass a valid value for Parameter \"strFirstName\"!');\n                return null;\n            }\n\n            if (!(typeof strLastName === \"string\" && strLastName.trim().length > 2)) {\n                gs.addErrorMessage('Please pass a valid value for Parameter \"strLastName\"!');\n                return null;\n            }\n\n \n            if (!(typeof strEmail === \"string\" && strEmail.trim().length > 10)) {\n                gs.addErrorMessage('Please pass a valid value for Parameter \"strEmail\"!');\n                return null;\n            }\n\n            strUserName  = strUserName.trim();\n            strFirstName = strFirstName.trim();\n            strLastName  = strLastName.trim();\n            strEmail     = strEmail.trim();\n            \n            if (grUser.get('user_name', strUserName)) {\n                gs.addErrorMessage('User with user name \"' + strUserName  + '\" already exists!');\n                return null;\n            }\n\n            if (grUser.get('email', strEmail)) {\n                gs.addErrorMessage('User with email \"' + strEmail  + '\" already exists!');\n                return null;\n            }\n\n\n            //----------------------------------------------------------------------------------\n            //test, whether all roles exists and current logged-in is allowed to \n            //assign a priviledged role to a new user\n            //----------------------------------------------------------------------------------\n\n            var grRole = new GlideRecord('sys_user_role');\n\n            arrRoles.forEach(function(strRoleName) {\n                if (grRole.get('name', strRoleName)) {\n                    if (grRole.getValue('elevated_privilege') == 1 && !gs.hasRole('security_admin')) {\n                        gs.addErrorMessage('Your are not allowed to assign role \"' + strRoleName + '\" to antother user!');\n                        return null;\n                    }\n                }\n                else {\n                    gs.addErrorMessage('Role \"' + strRoleName + '\" does not exist!');\n                    return null;\n                }\n            });\n\n            \n            //----------------------------------------------------------------------------------\n            //create record at table <sys_user>\n            //----------------------------------------------------------------------------------\n\n            grUser.initialize();\n            grUser.setValue('user_name', strUserName);\n            grUser.setValue('email', strEmail);\n            grUser.setValue('first_name', strFirstName);\n            grUser.setValue('last_name', strLastName);\n            grUser.setValue('preferred_language', strPreferredLanguage);\n            grUser.setValue('time_zone', stTimeZone);\n            grUser.setValue('date_format', strDateFormat);\n            grUser.setValue('password_needs_reset', true);\n            grUser.getElement('user_password').setDisplayValue(strStartPassword);\n    \n            if (grUser.insert()) {\n                strUserSysId = grUser.getUniqueValue();\n            }\n            else {\n                gs.addErrorMessage('Could not create user with user name \"' + strUserName + '\"!');\n                return null;\n            }\n\n\n            //----------------------------------------------------------------------------------\n            //add roles to user\n            //----------------------------------------------------------------------------------\n\n            arrRoles.forEach(function(strRoleName) {\n                if (grRole.get('name', strRoleName)) {\n                    var grUserHasRole = new GlideRecord(\"sys_user_has_role\");\n    \n                    grUserHasRole.initialize();\n                    grUserHasRole.setValue('role', grRole.getUniqueValue());\n                    grUserHasRole.setValue('user', strUserSysId);\n        \n                    if (!grUserHasRole.insert()) {\n                        gs.addErrorMessage(\n                            'Could not assign role \"' + strRoleName + '\"' +\n                            ' to user with user name \"' + strUserName + '\"!'\n                        );\n                    }\n                }\n                else {\n                    gs.addErrorMessage(\n                        'Role \"' + strRoleName + '\"' +\n                        ' is not available and thus cannot be assigned to user with user name \"' + strUserName + '\"!'\n                    );\n                }\n            });\n \n\n            //----------------------------------------------------------------------------------\n            //add some user preferences\n            //----------------------------------------------------------------------------------\n\n            var grUserPreference = new GlideRecord('sys_user_preference');    \n    \n            for (var i = 0; i < objUserPreferences.length; i++) {\t\t\n                grUserPreference.initialize();\n                grUserPreference.setValue('user', strUserSysId);\n                grUserPreference.setValue('name', objUserPreferences[i].name);\n                grUserPreference.setValue('value', objUserPreferences[i].value);\n                grUserPreference.insert();\n            }\n\n            return strUserSysId;\n        }\n        catch (e) {\n            gs.addErrorMessage('Unexpected error! See syslog for more information!');\n            gs.error(e);\t\t\n        }\n\n        return null; \n    }\n"
  },
  {
    "path": "Server-Side Components/Server Side/Create Tiny Url with API's/README.md",
    "content": "we can turn any servicenow url to tiny url with the code attached.\n\nit just need a user authentication details, any user who dont even have any roles also fine, the user just need to able to login to instance.\n\nexample:\nWe can turn this url :\nhttps://devxxxx.service-now.com/incident_list.do?sysparm_query=caller_id%3D681ccaf9c0a8016400b98a06818d57c7%5Epriority%3D1%5Estate%3D2%5Esys_updated_by%3Dadmin%5Eshort_description%3DManager%20can%27t%20access%20SAP%20Controlling%20application&sysparm_first_row=1&sysparm_view=\n\n\nTo this:\nhttps://devxxxx.service-now.com/incident_list.do?sysparm_tiny=cb4c40e12fd61d10c083d0ccf699b62a\n"
  },
  {
    "path": "Server-Side Components/Server Side/Create Tiny Url with API's/tinyUrl.js",
    "content": "/* \n*PLEASE REPLACE devxxxxx TO YOUR RELEVANT INSTANCE DOMAIN ex:dev67089.serice-now.com\n*PLEASE ADD YOUR RELEVANT USERNAME AND PASSWORD \n*/\n\n//url that need to be shortenered\nvar urlToShortern = \"https://devxxxxx.service-now.com/incident_list.do?sysparm_query=caller_id%3D681ccaf9c0a8016400b98a06818d57c7%5Epriority%3D1%5Estate%3D2%5Esys_updated_by%3Dadmin%5Eshort_description%3DManager%20can%27t%20access%20SAP%20Controlling%20application&sysparm_first_row=1&sysparm_view=\";\n\n//we just need user who can login into instance even if he dont have roles also fine\nvar username = ‘ADD_YOUR_USERNAME’;  \nvar password = 'ADD_YOUR_PASSWORD’;  \n\n//BELOW CODE NEED NO CHANGES\n\nvar request = new sn_ws.RESTMessageV2();\n// SN Api that we use for generating tiny url\nrequest.setEndpoint(gs.getProperty('glide.servlet.uri')+'api/now/tinyurl');  \nrequest.setHttpMethod('POST');\n\nvar body={};\nbody.url=urlToShortern;  \n\nrequest.setBasicAuth(username, password);\nrequest.setRequestHeader(\"Accept\", \"application/json\");\nrequest.setRequestHeader('Content-Type', 'application/json');\nrequest.setRequestBody(JSON.stringify(body));\n\nvar response = request.execute();\nvar responseBody = JSON.parse(response.getBody());\nvar tinyUrl = responseBody.result; //IT WILL HAVE OUR REQUIRED TINY URL \n\ngs.info(tinyUrl);  \n"
  },
  {
    "path": "Server-Side Components/Server Side/CreateUpdateCIThroughIRE/README.md",
    "content": "ServiceNow CMDB should be updated through the Identification and Reconciliation Engine. There are several legacy ways of populating CMDB and the most commonly used ways are via transform maps or through any external integration scripts. Whenver data is added to the CMDB, it should go through the IRE as it helps you maintain the quality of your CI data by avoiding duplicates and ensuring that only authorized source can updated the data in your CMDB. The code snippet allows you to update CMDB through IRE thereby reducing the maintanance effort required on your CMDB. You could call it from a transform map script, integration scripts or via any other scripted methods used to integrate.\n\nSample Usage\n````\n//This has the list of all CIs and its related CI that need to be added to CMDB\nvar items = [{\n        \"className\": 'cmdb_ci_win_server',\n        \"values\": {\n            \"name\": 'xServerV1',\n            \"serial_number\": \"12342-drrrcf-3332dd-2222\",\n            \"ip_address\": \"10.20.30.40\",\n            \"mac_address\": \"ZFSTFS-XXJSDD-QWWJES-AJDH2D\",\n            \"ram\": \"4096\",\n            \"os\": \"Windows 2016 Datacenter\"\n        }\n    },\n    {\n        \"className\": 'cmdb_ci_app_server_tomcat',\n        \"values\": {\n            \"name\": 'Tomcat@ip-10.20.30.40:8005',\n            \"version\": '7.0.42',\n            \"install_directory\": \"sdd\",\n            \"running_process_key_parameters\": \"ss\",\n            \"server_port\": \"8005\"\n        }\n    }\n];\n\n//This defines the relationship between the CIs mentioned in the items array and created the same in cmdb_rel_ci table\nvar relations = [{\n    \"child\": 0,\n    \"parent\": 1,\n    \"type\": \"Runs on::Runs\"\n}];\n\ncreateOrUpdateCIThroughIRE(items, relations, \"ServiceNow\");\n````````\n"
  },
  {
    "path": "Server-Side Components/Server Side/CreateUpdateCIThroughIRE/createupdateciinire.js",
    "content": "/************************************************************************************************/\nInput\n\nitems -   List of all CI and its attributes in a JSON array that need to inserted or updated\nrelations - List of all CI relationship in a JSON array that need to be setup when creating or updating a CI\nsource - The discovery source of the CI\n\nOutput - The CI, its related CIs and their relationships are either created or updated through the Identification & Reconciliation engine\n  \n/************************************************************************************************/\n\n\nfunction createOrUpdateCIThroughIRE(items, relations, source) {\n    var payload = {\n        \"items\": items,\n        \"relations\": relations\n    };\n    var jsonUtil = new JSON();\n    var input = jsonUtil.encode(payload);\n    var response = JSON.parse(SNC.IdentificationEngineScriptableApi.createOrUpdateCI(source, input));\n    for (var ci in response.items) {\n        gs.info(response.items[ci].className + \" with Id \" + response.items[ci].sysId + \" was \" + response.items[ci].operation);\n    }\n\n    for (var rel in response.relations) {\n        gs.info(\"CMDB relationship with Id \" + response.relations[rel].sysId + \" was \" + response.relations[rel].operation);\n    }\n"
  },
  {
    "path": "Server-Side Components/Server Side/Custom Relationship/README.md",
    "content": "Instead of duplicating attachment by use of GlideSysAttachment.copy() simplest approach is to create a relationship from System Definition >> Relationship & then display it as a Related list on required set of Tables were attachments are to be shown.\n\nSo, for a case where attachments from REQ (sc_request) are to be on RITM (sc_req_item) table then a relationship as below would suffice.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Custom Relationship/script.js",
    "content": "(function refineQuery(current, parent) {\n\n    var queryString = \"table_nameINsc_request^table_sys_idIN\" + parent.getValue(\"request\");\n    var gr = new GlideRecord(\"sc_req_item\");\n    gr.addQuery(\"request\", parent.getValue(\"request\"));\n    gr.query();\n    while (gr.next()) {\n        queryString += \",\" + gr.sys_id.toString();\n    }\n    current.addEncodedQuery(queryString);\n\n})(current, parent);\n"
  },
  {
    "path": "Server-Side Components/Server Side/DiscoveryDeviceHistory/devicehistory.js",
    "content": "/********************************************************************************************/\nInput\nstatusRecord - A GlideRecord object for Discovery Status record\n\nOutput\ndevices - An Array object of list of devices that have been discovered and their related attributes\n  operational_status – A choice value and contains three options\n    Processing – Discovery is still being executed\n    Successful – CI is either created or updated or there are no issues reported\n    Not Successful – CI is not discovered or having issues in discovery\n  device_id – The Sys Id of the device that was discovered\n  ip_address – The IP Address or the Source name of the CI\n  issue_count – Number of issues derived from the discovery log. This value is generated from the device history(discovery_device_history) record and is mapped to the Issues column\n  isse_link – An HTML link that could be used in Journal field like work notes. The link will direct you to the list of all warnings and error logs associated with your source address. Use this for cases where Operational Status is Not Successful\n  device_class – The CI Class of the device discovered eg Windows Server, Linux Server\n  last_status – The last status column value of the device history eg Created CI, Updated CI, Alive not active not classified, Active couldn’t classify\n\n/********************************************************************************************/\n\nfunction getDiscoveryResults(statusRecord) {\n\n    var devices = [];\n    var discoveryStatusId = statusRecord.getUniqueValue();\n    var discoveryStatus = statusRecord.getValue(\"state\");\n\n    //Query all device history into usable format\n    var deviceHistory = new GlideRecord(\"discovery_device_history\");\n    deviceHistory.addEncodedQuery(\"status=\" + discoveryStatusId);\n    deviceHistory.query();\n    while (deviceHistory.next()) {\n        var devicesDiscovered = {};\n        var issueCount = deviceHistory.getValue(\"issues\");\n        var lastState = deviceHistory.getValue(\"last_state\");\n\n        if (!!lastState)\n            lastState = lastState.toLowerCase();\n        else\n            lastState = \"\";\n\n        if (discoveryStatus != \"Completed\" && discoveryStatus != \"Cancelled\") {\n            devicesDiscovered.operational_status = \"Processing\";\n        } else if (lastState.indexOf(\"created\") > -1 || lastState.indexOf(\"updated\") > -1) {\n            devicesDiscovered.operational_status = \"Successful\";\n        } else if (!lastState && issueCount == 0) {\n            devicesDiscovered.operational_status = \"Successful\";\n        } else {\n            devicesDiscovered.operational_status = \"Not Successful\";\n        }\n\n        devicesDiscovered.cmdb_device = deviceHistory.getValue(\"cmdb_ci\");\n        devicesDiscovered.device_class = deviceHistory.getDisplayValue(\"classified_as\");\n        devicesDiscovered.issue_count = issueCount;\n        devicesDiscovered.issue_link = deviceHistory.getValue(\"issues_link\");\n        devicesDiscovered.ip_address = deviceHistory.getValue(\"source\");\n        devicesDiscovered.last_status = deviceHistory.getValue(\"last_state\");\n        devices.push(devicesDiscovered);\n    }\n\n    return devices;\n\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/DiscoveryDeviceHistory/readme.me",
    "content": "The code snippet provides you a readable output from a discovery status record and gives you detail information regarding the devices that were discovered in it.\nThis allows you perform the required remediation action on the issues associated with your discovery prcoess.\n\nSample Usage\n\nvar discoveryStatus = new GlideRecord(\"discovery_status\");\nif(discoveryStatus.get(\"sys_id\")){ //Replace with sys_id\n  var devices = getDiscoveryResults(discoveryStatus);\n  for(var entry in devices){\n     if(devices[entry].operational_status = \"Not Successful\"){\n        //Your business logic here... create incident etc\n        //gs.info(devices[entry].issue_count+ \" issues \"+ devices[entry].issue_link); \n     }\n  }\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/Dynamic Catalog Task Creation/README.md",
    "content": "**Dynamic Catalog Task Generator**\n\nThis Script Include provides a flexible, maintainable way to create one or more Service Catalog Tasks (sc_task) on a Request Item (sc_req_item). Instead of relying on complex, branching logic within a single Workflow or Flow, this script determines which tasks to create based on the value selected by the user in a single variable on the catalog form.\n\n\n**Centralizes Task Logic**: Keeps all task definitions (short descriptions, assignment groups, order) in one easy-to-read script.\n\n**Improves Maintainability**: You only update this single script when task requirements change, not a sprawling visual flow.\n\n**Increases Flow Reusability**: The core Flow/Workflow remains simple, focused only on calling this generator.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Dynamic Catalog Task Creation/dynamic_catalog_task.js",
    "content": "var CatalogTaskGenerator = Class.create();\nCatalogTaskGenerator.prototype = {\n    initialize: function() {\n        this.taskTable = 'sc_task';\n    },\n    generateTasks: function(ritmSysId, variableName) {\n        var ritmGR = new GlideRecord('sc_req_item');\n        if (!ritmGR.get(ritmSysId)) {\n            gs.error('RITM not found for sys_id: ' + ritmSysId);\n            return;\n        }\n        var taskType = ritmGR.variables[variableName].toString();\n        switch (taskType) {\n            case 'Laptop':\n                this._createTask(ritmGR, 'Assign Laptop Asset', 'Hardware Fulfillment', 10);\n                this._createTask(ritmGR, 'Install Core Software', 'Software Team', 20);\n                break;\n            case 'Desktop':\n                this._createTask(ritmGR, 'Deploy Desktop Image', 'Infrastructure Team', 10);\n                this._createTask(ritmGR, 'Setup Docking Station', 'Hardware Fulfillment', 20);\n                break;\n            case 'Mobile Phone':\n                this._createTask(ritmGR, 'Order SIM Card', 'Telecom Team', 10);\n                this._createTask(ritmGR, 'Configure MDM Profile', 'Mobile Support', 20);\n                break;\n            // add Cases as Catalog Tasks Required....\n            default:\n                gs.info('CatalogTaskGenerator: No specific tasks defined for ' + variableName + ' value: ' + taskType);\n                break;\n        }\n    },\n\n    _createTask: function(ritmGR, shortDesc, assignmentGroupName, order) {\n        var taskGR = new GlideRecord(this.taskTable);\n        taskGR.initialize();\n        taskGR.request_item = ritmGR.sys_id;\n        taskGR.request = ritmGR.request;\n        taskGR.short_description = shortDesc;\n        taskGR.order = order;\n        var groupGR = new GlideRecord('sys_user_group');\n        if (groupGR.get('name', assignmentGroupName)) {\n            taskGR.assignment_group = groupGR.sys_id;\n        } else {\n            gs.warn('CatalogTaskGenerator: Assignment Group not found: ' + assignmentGroupName);\n        }\n\n        taskGR.insert();\n    },\n\n    type: 'CatalogTaskGenerator'\n};\n"
  },
  {
    "path": "Server-Side Components/Server Side/Email Bounce Alert System/Email_Bounce_Alert_System.js",
    "content": "// 📧 Email Bounce Alert Script\n// --------------------------------------------------------------\n// Purpose:\n// This mail script detects bounced-back emails in the Junk module\n// and identifies recipient addresses from the email body.\n//\n// When a bounce is detected, it automatically triggers a notification\n// containing the affected email IDs and a link to the email record.\n//\n// Type: Email Script\n// Trigger: Notification \n\n(function runMailScript(current, template, email, email_action, event) {\n\n    // --- Step 1: Function to extract email addresses from email body ---\n    function extractEmails(html) {\n        // Regular expression to match email patterns\n        var emailPattern = /[\\w\\-]+@[\\w\\-]+/g;\n\n        // Find all matching email addresses in the body\n        var emails = html.match(emailPattern);\n\n        // Return the array of emails or an empty array if none found\n        return emails || [];\n    }\n\n    // --- Step 2: Convert the email body into plain text ---\n    var body = current.body.toString().replaceAll('\"', '');\n\n    // --- Step 3: Extract email addresses from the content ---\n    var extractedEmails = extractEmails(body);\n\n    // --- Step 4: Build the notification message ---\n    template.print(\"This alert has been generated because a bounced email incident may have occurred due to possible IP blacklisting.\"); //example: IP blacklisting use case\n    template.print(\"<br><strong>User(s) who missed the email:</strong> \" + extractedEmails.join(', '));\n    template.print(\"<br><strong>Link to the bounced email record:</strong> \");\n    template.print('<a href=\"' + gs.getProperty('glide.servlet.uri') + 'sys_email.do?sys_id=' + current.sys_id + '\">Click here</a>');\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Server-Side Components/Server Side/Email Bounce Alert System/README.md",
    "content": "📧 Email Bounce Alert System\n📘 Overview\n\nThe Email Bounce Alert System automatically detects bounced-back emails in the Junk Mail module and alerts administrators or support teams about affected recipients. It helps identify communication failures quickly and ensures that important client messages aren’t missed due to IP blacklisting or delivery issues.\n\n🧩 Problem Statement\n\nIn large organizations, emails sometimes bounce back because of mail server issues, blacklisted IPs, or invalid addresses.\nCurrently, identifying which users didn’t receive an important message requires manual checking of the Junk Email module, leading to delays in response and potential loss of critical communication.\n\n💡 Solution\n\nThis email script automates the detection and notification process.\n\nThe email script is called from a Notification configured on the sys_email table.\n\nEmail Script:\n\nExtracts affected recipient email addresses from the email body using RegEx pattern matching.\n\nGenerates an automatic notification alert listing all affected users.\n\nProvides a direct link to the bounced email record for quick investigation.\n\nNotification settings:\n\nWhen to send: Record inserted or updated\n\nInserted checkbox: Marked\n\nCondition: Mailbox is Junk AND Body is not empty\n\nProper recipient(s) and subject line set in the notification.\n\n\n🚀 Benefits\n\n⚡ Immediate visibility of bounced or undelivered emails.\n\n🧠 Automated extraction of recipient information — no manual tracking required.\n\n📩 Faster communication recovery, ensuring critical business messages reach the intended audience.\n\n🔗 Direct record access for faster troubleshooting and action.\n"
  },
  {
    "path": "Server-Side Components/Server Side/ExecuteWorkOnMidServer/README.md",
    "content": "The snippet can be used to dynamically execute any work on the mid server. You could run any commands on a server, powershell scripts, LDAP queries and other functions depending on the output probes which are supported by ServiceNow. You need to be aware of the ECC queue output and input structure used by ServiceNow to issue these commands. To get these formats, you could execute the same functionality and monitor the ECC queue for its structure. \n\nSample Usage.\n\nFor eg, inorder to query on LDAP for all users whose name is starting with A. You could call the following function.\n\n```\nvar outputId = executeWorkOnMid('Mid-Server-01', 'LDAPQuery', 'LDAPBrowseProbe', 'fdc4cd3fdb6a8f003a339e04db96199d',  '<?xml version=\"1.0\" encoding=\"UTF-8\"?><parameters><parameter name=\"type\" value=\"\"/><parameter name=\"value\" value=\"(&amp;(objectClass=person)(cn=A*))\"/><parameter name=\"chars\" value=\"*\"/></parameters>'));\n```\n```\nThe payload argument contains the LDAP query as (&amp;(objectClass=person)(cn=A*)) which would return the results in an input ECC queeue\n```\nThe outputId can then be passed to the second function to fetch the response as an XML object. The ECC queue works asynchronously, so may need to wait for few seconds until the response is received in the input queue.\n\n```\ngs.sleep(5000);\nvar xmlResponse = fetchResponseFromMid(outputId);\n//gs.print(xmlResponse);\n```\n"
  },
  {
    "path": "Server-Side Components/Server Side/ExecuteWorkOnMidServer/executeworkonmid.js",
    "content": "/*******************************************************************************/\nFunction executeWorkOnMid\n\nInput: The format of input queue attributes should be same as what is generated by ServiceNow for different operations.\nmidServer - The name of your mid server \nqueue - Name of ECC queue entry to identify the record\ntopicName - Topic name of the ECC queue entry. This should be same as the topic name used by ServiceNow as it determines the type of work and its execution\nsource - The source which is issuing the work. This should be same as the source used by ServiceNow. It can also remain empty for many cases\nwork - XML payload which is the actual work or commands that you are issuing towards the mid server\n\nOutput: Returns the sys_id of the output queue to fetch the results\n\nFunction fetchResponseFromMid\n\nInput: \noutputQueue - The sys_id of output queue created from the executeWorkOnMid function\n\nOutput: Returns the response in XML. You will need to understand the response structure and further access different nodes to process your business logic\n\n/*******************************************************************************/\n\nfunction executeWorkOnMid(midServer, queueName, topicName, source, work){\n    var eccQueue = new GlideRecord(\"ecc_queue\");\n    eccQueue.newRecord();\n    eccQueue.agent = \"mid.server.\" + midServer;\n    eccQueue.name = queueName;\n    eccQueue.queue = \"output\";\n    eccQueue.topic = topicName;\n    eccQueue.source = source;\n    eccQueue.payload = work;\n    return eccQueue.insert();\n}\n\nfunction fetchResponseFromMid(outputQueue){\n    var eccQueue = new GlideRecord(\"ecc_queue\");\n    eccQueue.addEncodedQuery(\"response_to=\"+outputQueue+\"^queue=input^state=ready\");\n    eccQueue.query();\n    if(eccQueue.next()){\n        var response = eccQueue.getValue(\"payload\");\n        var xmlDocument = new XMLDocument2();\n        xmlDocument.parseXML(response);\n        return xmlDocument;\n    }else{\n        return \"No response\";\n    }\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/Fetch dynamic value from decision table/README.md",
    "content": "Fetch dynamic value from decision table\n\nUse case: Map relevant values of field1 to field2 with large list of choices and often changing and needs to be editable by specific admin team.\n\nSolution: If we want to have a scenario like based on \"Field1\" of big list of comma separated values, We need to provide its relevant \"Field2\" value, Which need not to be repeated again and the key value pair is huge and often changing, In that case we can make use of decision table to fetch key value pair without interrupting any scripts and saving value in \"Field2\" which will get changed according to \"Field1\" value as it is a calculated field.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Fetch dynamic value from decision table/code.js",
    "content": "(function calculatedFieldValue(current) {\n\n    var fieldA = current.u_list_items.toString(); // Get value of field 1\n\n    if (fieldA != '') { // Check if field value is empty\n\n        var individualInformation = fieldA.split(\",\");\n        var resultElements = [];\n        for (var i in individualInformation) {\n            try {\n                var inputs = {};\n                inputs['u_decision_field'] = individualInformation[i];\n\n                var dt = new sn_dt.DecisionTableAPI(); // Calling decision table by API call\n                var response = dt.getDecision('sysid_of_decision_table', inputs);\n\n                var result_elements = response.result_elements;\n                var u_return_value = result_elements.u_decision_result.getValue(); // String\n                if (resultElements.indexOf(u_return_value) == -1)\n                    resultElements.push(u_return_value);\n\n            } catch (e) {\n                gs.log(\"Couldn't run this script Error: \" + e);\n                resultElements.push('');\n            }\n\n        }\n        return resultElements.toString(); // Return end result as string to get stored in field 2\n\n    }\n})(current);\n"
  },
  {
    "path": "Server-Side Components/Server Side/FetchJSONObject/README.md",
    "content": "The `fetchJSONObject` function is designed to retrieve a JSON object containing specified field values from a GlideRecord instance in ServiceNow. \nIt allows for optional input of field names to fetch; if no fields are specified, all fields will be retrieved. \n"
  },
  {
    "path": "Server-Side Components/Server Side/FetchJSONObject/script.js",
    "content": "function fetchJSONObject(gr, desiredFields /* optional */) {\n    // Ensure this is a valid GlideRecord\n    if (!(gr instanceof GlideRecord) || !gr.isValidRecord()) {\n        return {};\n    }\n\n    // Determine fields to retrieve; if none specified, get all fields\n    if (!Array.isArray(desiredFields) || desiredFields.length === 0) {\n        var gRU = new GlideRecordUtil();\n        desiredFields = gRU.getFields(gr); // Retrieves all field names for the record\n    }\n\n    var fieldValues = {};\n    // Use forEach to loop through the desired fields and add their values to the JSON object\n    desiredFields.forEach(function(fieldName) {\n        if (gr.isValidField(fieldName)) { // Ensure the field exists in the GlideRecord\n            var fieldValue = gr.getValue(fieldName);\n            fieldValues[fieldName] = fieldValue ? fieldValue : ''; // Use empty string if value is null\n        }\n    });\n\n    return fieldValues;\n}\n\n// Usage example:\n//var gr = new GlideRecord('incident');\n//gr.get('sys_id'); // Replace with an actual sys_id or use query methods to get a record\n//var jsonObject = fetchJSONObject(gr); // Optionally, add an array of specific fields as the second argument\n//gs.info(JSON.stringify(jsonObject)); // Logs the JSON object\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/Generate Attachment and add it to the email/README.md",
    "content": "ServiceNow Incident CSV Export Email Script\n\nThis ServiceNow email script automatically generates and attaches a CSV file containing incident data to email notifications. The script extracts active incidents from your ServiceNow instance, formats them into a structured CSV file, and attaches the file to outbound email notifications, providing recipients with a comprehensive incident report in a portable format.\n\nWhat This Script Does:\nThe email script performs the following operations:\nData Extraction: Queries all active incidents from the ServiceNow incident table\nCSV Generation: Formats incident data into a structured CSV file with predefined headers\nFile Attachment: Automatically attaches the generated CSV file to email notifications\nDynamic Content: Creates fresh data exports each time the notification is triggered\nPortable Format: Provides incident data in a universally readable CSV format\n"
  },
  {
    "path": "Server-Side Components/Server Side/Generate Attachment and add it to the email/emailAttachment.js",
    "content": "(function runMailScript( /* GlideRecord */ current, /* TemplatePrinter */ template,\n    /* Optional EmailOutbound */\n    email, /* Optional GlideRecord */ email_action,\n    /* Optional GlideRecord */\n    event) {\n\n    var Headers = [\"Number\", \"Caller\", \"Short Desc\", \"Assignment Group\", \"Assigned To\"];\n    var currentDate = new GlideDateTime();\n    var fileName = 'Incidents' + '.csv';\n    var csvData = ''; //The variable csvData will contain a string which is used to build the CSV file contents\n    for (var i = 0; i < Headers.length; i++) { //Build the Headers\n        csvData = csvData + '\"' + Headers[i] + '\"' + ',';\n    }\n    csvData = csvData + \"\\r\\n\";\n\n    var gr = new GlideRecord(\"incident\");\n    gr.addActiveQuery();\n    gr.query();\n    while (gr.next()) {\n        csvData = csvData + '\"' + gr.number + '\",' + '\"' + gr.caller_id.getDisplayValue() + '\",' + '\"' + gr.short_description + '\",' + '\"' + gr.assignment_group.getDisplayValue() + '\",' + '\"' + gr.assigned_to.getDisplayValue() + '\"';\n        csvData = csvData + \"\\r\\n\";\n    }\n\n    var grAttachment = new GlideSysAttachment();\n    grAttachment.write(event, fileName, 'application/csv', csvData);\n\n})(current, template, email, email_action, event);\n"
  },
  {
    "path": "Server-Side Components/Server Side/Get all Catalog items associated to variable set/README.md",
    "content": "Use this script to get List of Catalog Items associated to the Variable set.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Get all Catalog items associated to variable set/script.js",
    "content": "var catlist = '';\nvar ItemGR = new GlideRecord(\"io_set_item\");\nItemGR.addEncodedQuery('variable_set={add variable set sysid}');  \nItemGR.query();\nwhile (ItemGR.next()) {\ncatlist = catlist + ItemGR.sc_cat_item +',';\n}\n\ngs.info('List of Catalog Items associated to the Variable set  :  ' +catlist);\n"
  },
  {
    "path": "Server-Side Components/Server Side/Get all variables of catalog item/README.md",
    "content": "sometimes we need to get all varibles related to Catalog item / record producer, but as some varibles are placed in variable set its not that easy to get all.\n\nfor this we can use addJoinQuery of GlideRecord. so we can get all varibles related to Catalog item / record producer with attached script.\n\nexample output:\n\n![image](https://user-images.githubusercontent.com/46869542/193516504-901c2456-8fb4-46b2-ac64-b236976f258b.png)\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/Get all variables of catalog item/script.js",
    "content": "function getAllVariables(itemSysID) {\n  \n  var varSetIDs = [],\n    allVarbls = {};\n  allVarbls.itemName = 'EMPTY';\n\n  var varset = new GlideRecord('item_option_new_set');\n  var joingr = varset.addJoinQuery('io_set_item');\n  varset.addActiveQuery();\n  joingr.addCondition('sc_cat_item', itemSysID);\n  varset.query();\n  while (varset.next()) {\n    varSetIDs.push(varset.getValue('sys_id'));\n  }\n\n  var varbls = new GlideRecord('item_option_new');\n  varbls.addActiveQuery();\n  var condition = varbls.addQuery('cat_item', itemSysID);\n  condition.addOrCondition('variable_set', 'IN', varSetIDs);\n  varbls.orderBy('name');\n  varbls.query();\n\n  while (varbls.next()) {\n    if (allVarbls.itemName == 'EMPTY' && varbls.cat_item.name != '') {\n      allVarbls.itemName = varbls.cat_item.name + '';\n    }\n\n    allVarbls[varbls.getValue('sys_id')] = varbls.getValue('name');\n\n  }\n\n  var print = '\\n\\n\"' + allVarbls.itemName + '\" has the following variables with \"sys_id\" : \"name\" as\\n\\n';\n\n  for (var i in allVarbls) {\n    if (i == 'itemName') {\n      continue;\n    }\n    print += i + ' : \"' + allVarbls[i] + '\"\\n';\n  }\n\n  return print;\n\n}\n\n\ngs.info(getAllVariables('3a25637b47701100ba13a5554ee490a0')); // sys_id of catalog item \"Service Category Request\"\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/MarkInactiveUsersonList/README.md",
    "content": "There is a list type field named 'Reviewers' on Policy records.\nOn the list view of Policies, we want to highlight with field styles, where any of the user listed under Reviewers is inactive.\nIn the screenshot attached below, Daniel Zill is inactive user, and if he is present in Reviewers, the respective column value is applied with defined field styles.\n<img width=\"809\" height=\"370\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b483207e-f3ba-4db7-a717-d392694eaf50\" />\n<img width=\"433\" height=\"107\" alt=\"image\" src=\"https://github.com/user-attachments/assets/5129038f-d210-40f4-bcd8-1727d791edca\" />\n\nThe condition to check if any inactive user is present in Reviewers must be written on 'Value' (actual Server script) and the styles to applied must be mentioned on 'Style'.\nRefer below screenshot:\n<img width=\"911\" height=\"239\" alt=\"image\" src=\"https://github.com/user-attachments/assets/477e52cb-bdad-439d-a1cc-5b6be0415c20\" />\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/MarkInactiveUsersonList/fieldStyleforListfields.js",
    "content": "//The below code followed by \"javascript:\", inside 'Value' field for the List type Reviewers field's Style record will do the condition check.\nvar answer = false;\nvar arr=[];\narr = current.reviewers.split(',');\nfor(i=0; i<arr.length; i++){\n  var gr = new GlideRecord('sys_user');\n  gr.addQuery('sys_id',arr[i]);\n  gr.query();\n  if(gr.next()){\n    if(gr.active == false){\n      answer = true;\n    }\n  }\n}\nanswer;\n\n//The Style field then must be populated with the style we want to apply. I have applied \"background-color: blue;\" \"text-decoration:line-through;\"\nbackground-color: blue;\ntext-decoration:line-through;\n"
  },
  {
    "path": "Server-Side Components/Server Side/Parse csv file and read each row/README.md",
    "content": "We have a ServiceNow api to parse Excel file(GlideExcelParser) however it wouldn't work for parse CSV as a file (not a CSV string using \"sn_impex.CSVParser()\")\nthere is a alternate way to read csv logic.js logic parse the CSV as file and can read all the rows/columns\nthis can be used in any server side scripting , just need to pass the attachment sys_id\nThis comes very handy when your receving a CSV as email to serviceNow and need to update records based on incoming CSV file vales\nEx:input CSV:![image](https://github.com/gowdah/code-snippets/assets/42912180/857f8fa1-6671-44ae-b105-779f2d7d4fc5)\n\n\n\nex:output:XYZ,abc,Hemanth\n"
  },
  {
    "path": "Server-Side Components/Server Side/Parse csv file and read each row/read csv logic.js",
    "content": "//read the file\n  var gsa = new GlideSysAttachment(); //attachment api to read the attachment \n  var bytesInFile = gsa.getBytes('sys_email', table_sys_id); // tablename, table sysID from the attachment table\n  var originalContentsInFile = Packages.java.lang.String(bytesInFile); // java package get bytes in the file\n  originalContentsInFile = String(originalContentsInFile);\n  var fileData = originalContentsInFile.split('\\n');\n  var csvHeaders = fileData[0] ; //get header(first row from the CSV file)\n var arrOfarr =[]; //array to store\nfor(i=1 ; i<fileData.length -1 ; i++){ //loop through each row\n    var rowDetails = fileData[i] ;\n    var rowValues = rowDetails.split(',');\n    var firstColumn = rowValues[0] //first row , first column after headers\n    var firstColumn = rowValues[2] //2nd column\n    //we can get all the columns, rows in the same way and can store as an array or validate/update to tables if required.\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/Phone Number formating(US Region)/README.md",
    "content": "Use this script to format Phone Number in standard format 3-3-4 example if phone is  1234567891  then output will be 123-456-7891\n"
  },
  {
    "path": "Server-Side Components/Server Side/Phone Number formating(US Region)/script.js",
    "content": "\n// Format Phone Number in standard format 3-3-4 example if phone is  1234567891  then output will be 123-456-7891\n\n\nfunction normalize(phone) {\n  //normalize string and remove all unnecessary characters\n  phone = phone.replace(/[^\\d]/g, \"\");\n  //check if number length equals to 10\n  if (phone.length == 10) {\n      //reformat and return phone number\n      return phone.replace(/(\\d{3})(\\d{3})(\\d{4})/, \"$1-$2-$3\");\n  }\n\n  return null;\n}\n\n//Example for Background scripts\nvar k = '1234567891';\nk = k.substring(0, 10);\nvar phone = normalize(k);\ngs.info(phone)\n"
  },
  {
    "path": "Server-Side Components/Server Side/Random Password generator/README.md",
    "content": "This script is used to generate a random password, you can update the length parameter to adjust the length of password.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Random Password generator/script.js",
    "content": "\t// Add your code here\n\n\tvar length = 18; // you can use length as option or static\n\tvar letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz';\n\tvar numbers = '0123456789';\n\tvar symbols = '!#$%&\\()*+,-./:;<=>?@^[\\\\]^_`{|}~';\n\n\tvar password = '';\n\n\tvar validChars = '';\n\n\n\tvalidChars += letters;\n\tvalidChars += numbers;\n\tvalidChars += symbols;\n\n\tvar generatedPassword = '';\n\n\tfor (var i = 0; i < length; i++) {\n\t\tvar index = Math.floor(Math.random() * validChars.length);\n\t\tgeneratedPassword += validChars[index];\n\t}\n\n\tpassword = generatedPassword;\ngs.info(password);\n"
  },
  {
    "path": "Server-Side Components/Server Side/Record as Link/README.md",
    "content": "# Record as a Link Utility\n\nThe **Record as a Link Utility** is a versatile tool designed to dynamically render records as clickable links in any HTML field for any table. This utility allows you to choose specific fields to display, making it ideal for use cases such as notifications, generating PDFs, and more.\n\n## Features\n\n- Dynamically render records based on selected table and fields.\n- Generate clickable links for seamless navigation.\n- Compatible with various use cases like notifications, reports, and document generation.\n\n## Use Cases\n\n- **Notifications**: Display dynamic links to records in notification messages for any record in serviceNow\n- **PDF Generation**: Embed clickable record links in generated PDFs.\n- **Custom Applications**: Use in any HTML field to enhance interactivity.\n\n## How to Use\n\nUse the utility as used given code and merge it with other html. example attached for reference.\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/Record as Link/example-pdf-generation.js",
    "content": "var recordToLinkUtil = new RecordToHTML(\"incident\", \"1c741bd70b2322007518478d83673af3\",\n\"incident: ${number}-${short_description}\",true);\n\n//Adding link as html \n var html =  '<h1>Incident Link is genearted</h1>\\n' + recordToLinkUtil.toString();\n\n\n\n var fileName = 'Test File with RecordLink';\n var tableName =  'incident';\n var recordSysId = \"a623cdb073a023002728660c4cf6a768\";\n\n // Generate PDF and attach\n var pdfResult = new sn_pdfgeneratorutils.PDFGenerationAPI().convertToPDF(\n     html,\n     tableName,\n     recordSysId,\n     fileName,\n     ''\n );\n"
  },
  {
    "path": "Server-Side Components/Server Side/Record as Link/recordAsLink.js",
    "content": "function getLink() {\n    var recordToLinkUtil = new RecordToHTML(\"YOUR_TABLE_NAME\", \"RECORD_SYS_ID\",\n        \"CUSTOM_TEXT_TO_ADD_BEFORE_LINK: ${number}-${short_description}\", true); \n        return recordToLinkUtil.toString();\n}\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/Remove HTML Tags/README.md",
    "content": "Removing the HTML tags from the HTML fields in the list view.\nFor example, the 'Fix Notes' field in the problem table is an HTML field and displays with all the HTML tags in the list view.\nThe screenshot below shows a difference in the list view.\n\n<img width=\"1255\" height=\"172\" alt=\"image\" src=\"https://github.com/user-attachments/assets/bff0f4ce-2d63-4ed9-9e70-3f3e27cc3f18\" />\n"
  },
  {
    "path": "Server-Side Components/Server Side/Remove HTML Tags/script.js",
    "content": "//Create a new string field called 'Fix Notes(u_fix_notes)' in the problem table.\n//Select the 'calculated' checkbox in the field dictionary, and add the code to the script section.\n//This would be helpful if you want to export HTML fields in a report.\n(function calculatedFieldValue(current) {\n    var htmlText = current.fix_notes; // Getting the value of the current fix notes (HTML field)\n    var decodedText = htmlText.replace(/&#(\\d+);/g, function(match, dec) {\n            return String.fromCharCode(dec);\n        }).replace(/<[^>]*>/g, '').replace(/&amp;/g, '&').replace(/&lt;/g, '<')\n        .replace(/&gt;/g, '>').trim();\n    return decodedText;\n})(current);\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/Restart Flow on RITM/README.md",
    "content": "Restart a flow designer flow on a Requested Item.  I use this in a UI Action, but it could also be modified and used in a background script if needed.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Restart Flow on RITM/restartFlow.js",
    "content": "if (!current.cat_item.flow_designer_flow.nil() && !current.flow_context.nil()) {\n    sn_fd.FlowAPI.cancel(current.flow_context.sys_id.toString(), 'Cancelling Flow');\n    var inputs = {\n        \"request_item\": current\n    };\n    var flowName = current.cat_item.flow_designer_flow.sys_scope.scope.toString() + \".\" + current.cat_item.flow_designer_flow.internal_name.toString();\n    var newContext = sn_fd.FlowAPI.startFlow(flowName, inputs);\n    current.flow_context = newContext;\n}\n\ncurrent.update();\n"
  },
  {
    "path": "Server-Side Components/Server Side/Restart a workflow via any server side script/README.md",
    "content": "Use this to attach a new workflow to your old records.\n"
  },
  {
    "path": "Server-Side Components/Server Side/Restart a workflow via any server side script/script.js",
    "content": "triggerWorkflow();\nfunction triggerWorkflow(){\ntry{\nvar rec = new GlideRecord('table'); // give your table name\nrec.addEncodedQuery('YOUR QUERY HERE FOR OLDER RECORDS');\nrec.query();\nwhile(rec.next()){\nvar wf = new Workflow();\nwf.startFlow(wf.getWorkflowFromName('give the workflow name'), rec, 'update'); // give workflow name here\n}\n}\ncatch(ex){\ngs.info('Exception'+ex);\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/Trigger Assessments through Script/README.md",
    "content": "If we want to trigger assessments onDemand, We can use this logic (ex: Trigger assessment on click of a button or trigger assessment when approver approvers etc....  )\nCreate Assessment using Survey Designer , create Metric category and Metric type as usual.\nHave this logic in any server side logc and trigger onDemand by passing below inputs.\n  Pass Metric category\n  Pass Metric Types\n"
  },
  {
    "path": "Server-Side Components/Server Side/Trigger Assessments through Script/trigger assessment.js",
    "content": "var metricType = 'sys_id of the metric type'; // enter Sys Id for the Metric type\nvar GENERAL = 'sys_id of the metric Category'; // enter Sys Id for the Metric Category\n\n// Function to fetch the assessable record for the current record if exists\nfunction getAssessableRecord(current) {\nvar assessableRecordGR = new GlideRecord('asmt_assessable_record');\n    assessableRecordGR.addQuery(\"source_id\", current.sys_id);\n    assessableRecordGR.addQuery(\"source_table\", current.getTableName());\n    assessableRecordGR.addQuery(\"metric_type\", metricType);\n    assessableRecordGR.query();\n    if (assessableRecordGR.hasNext()) {\n        assessableRecordGR.next();\n        return assessableRecordGR;\n    }\n    return null;\n}\ntry {  \n    // Fetch assessable record for current record\n    var assessable = getAssessableRecord(current); //call getAssessableRecord function\n    if (!assessable) { //if no assessable record found\n        // Create assessable record for current record\n        new global.AssessmentUtils().checkRecord(current, metricType, true //replace current with glide varibale object if this is called inside script include\n        assessable = getAssessableRecord(current);\n        // Map all the metric categories to the assessible record (Create records in m2m table)\n        for (var i = 0; i < metricCategories.length; i++) { //this handles if more than one metric category assosciated to this.\n\t   var m2mGR = new GlideRecord(\"asmt_m2m_category_assessment\");\n            m2mGR.initialize();\n            m2mGR.category = metricCategories[i];\n            m2mGR.assessment_record = assessable.sys_id;\n            m2mGR.insert();\n        }\n    }\n  \n    // Generate an Assessment Instance\n    var assessment = new global.AssessmentUtils().createAssessments(String typeID, String sourceRecordID, String userID);\n  //String typeID -The sys_id of the metric type\n  //String sourceRecordID - Sys_id of the assessment target record\n  //String userID -One or more comma-separated sys_ids of users to which to send assessment \n  \n  // Extract the Sys Id for the Assessment Instance\n    var assessmentID = assessment.split(',')[0];\n    var assessmentInstanceGR = new GlideRecord(\"asmt_assessment_instance\");\n    assessmentInstanceGR.get(assessmentID);\n    // Set Trigger table to current table\n    assessmentInstanceGR.trigger_table = current.getTableName(); //replace a variable which holds target assessment record table name if its inside script include logic or any server side script\n    // Set Trigger record to current record\n    assessmentInstanceGR.trigger_id = current.sys_id; ////replace a variable which holds target assessment recordsys_id  if its inside script include logic or any server side script\n    assessmentInstanceGR.task_id = current.sys_id;\n    assessmentInstanceGR.update();\n    gs.addInfoMessage(\"Assessment Instance Generated\"); //show info messgae that assesement has been created \n} catch (e) {\n    gs.addErrorMessage(\"Error Generating Assessment Instance\"); //sheow if any error\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/Update Sets Scopes Issues Fix Automation/FixUpdatesScope.js",
    "content": "var util = new global.UpdateSetUtilCustom();\nvar message = util.fixScopeBatch(current);\ngs.addInfoMessage(message);\naction.setRedirectURL(current);\n"
  },
  {
    "path": "Server-Side Components/Server Side/Update Sets Scopes Issues Fix Automation/PreventCompletiononScopeConflict.js",
    "content": "(function executeRule(current, previous /*null when async*/) {\n\tvar util = new global.UpdateSetUtilCustom();\n\tif (util.checkForScopeConflict(current)) {\n\t\tcurrent.setAbortAction(true);\n\t\tgs.addErrorMessage('This update set has a scope conflict in it. Please click the \"Fix Updates Scope\" button to fix.');\n\t\taction.setRedirectURL(current);\n\t}\n})(current, previous);\n"
  },
  {
    "path": "Server-Side Components/Server Side/Update Sets Scopes Issues Fix Automation/README.md",
    "content": "ServiceNow Developers work with update set batching and many times it happens that customer updates gets captured in a wrong scope. As of Xanadu release, there is no way to fix these scoping issues in child updates.\nThis utility will perform following and implement a way of taking in a parent/batch update set:\n\n-\tNavigate all children, all updates in children.\n\n-\tDetermine scope issues with updates.\n\n-\tCreate new update set in correct scopes.\n\n-\tMove updates to those new update sets.\n\n-\tAssociate them with parent/batch.\n\n-\tStop action on completion of update sets if there are any scope issues found and direct the user to click on the Fix Scope button.\n\n\n\nThis functionality has following:\n\n-\tBusiness rule to abort transaction if scoping issues are found in batched update sets.\n-\tScript include which does job of scoping issues conflict as well as logic for fixing batch scope issues.\n-\tUI action which can be used by developers to fix scoping issues conflict.\n\n![image](https://github.com/user-attachments/assets/0a7c5127-7c15-4bb6-bf96-17de3b81a334)\n\n![image](https://github.com/user-attachments/assets/87872d20-f2c2-42ac-8c69-d095cc7ddaf3)\n\n![image](https://github.com/user-attachments/assets/94890946-395c-476f-a9b9-b81e94a801c9)\n\n![image](https://github.com/user-attachments/assets/96bcea79-a06e-4891-aace-3bbba81e9cb4)\n\n\n\n\n\n"
  },
  {
    "path": "Server-Side Components/Server Side/Update Sets Scopes Issues Fix Automation/UpdateSetUtilCustom.js",
    "content": "var UpdateSetUtilCustom = Class.create();\nUpdateSetUtilCustom.prototype = {\n    initialize: function() {},\n\n    fixScopeBatch: function(grParent) {\n        var grUpdate = new GlideRecord('sys_update_xml');\n        grUpdate.addEncodedQuery('update_set.parent.parent=' + grParent.getValue('sys_id') + '^ORupdate_set.parent=' + grParent.getValue('sys_id') + '^ORupdate_set=' + grParent.getValue('sys_id') + '^ORupdate_set.base_update_set=' + grParent.getValue('sys_id'));\n        grUpdate.query();\n\n        var count = 0;\n        var newUpdateSets = {};\n\n        while (grUpdate.next()) {\n            if (!grUpdate.getValue('application')) { // No app, should be in a global update set\n                if (grUpdate.update_set.application != 'global' && grUpdate.update_set.application.scope != 'global') {\n                    count++;\n\n                    if (!newUpdateSets['global']) {\n                        newUpdateSets['global'] = {\n                            'name': 'Global',\n                            'updates': []\n                        };\n                    }\n                    newUpdateSets['global']['updates'].push(grUpdate.getValue('sys_id'));\n                }\n            }\n            // We don't have the same scope for each update. \n            else if (grUpdate.application != grUpdate.update_set.application) {\n                count++;\n\n                if (!newUpdateSets[grUpdate.getValue('application')]) {\n                    newUpdateSets[grUpdate.getValue('application')] = {\n                        'name': grUpdate.getDisplayValue('application'),\n                        'updates': []\n                    };\n                }\n                newUpdateSets[grUpdate.getValue('application')]['updates'].push(grUpdate.getValue('sys_id'));\n            }\n        }\n\n        var parentName = grParent.getValue('name');\n        var keys = Object.keys(newUpdateSets);\n        for (i in keys) {\n            var updates = newUpdateSets[keys[i]]['updates'];\n            // Create new update set in the correct scope.\n            var grNewSet = GlideRecord('sys_update_set');\n            grNewSet.initialize();\n            grNewSet.setValue('application', keys[i]);\n            grNewSet.setValue('name', parentName + ' - ' + newUpdateSets[keys[i]]['name']);\n            grNewSet.setValue('parent', grParent.getValue('sys_id'));\n            var newSetSysId = grNewSet.insert();\n\n            for (ii in updates) {\n                // Get each update and set the new update set. \n                var updateSysId = updates[ii];\n                var grUpdate = new GlideRecord('sys_update_xml');\n                if (grUpdate.get(updateSysId)) {\n                    grUpdate.setValue('update_set', newSetSysId);\n                    grUpdate.update();\n                }\n            }\n        }\n        if (count > 0) {\n            return count + ' updates were in the wrong scope. ' + keys.length + ' new update sets were created and associated with this parent update set.';\n        }\n        return 'No update scope issues were found.';\n    },\n\n    checkForScopeConflict: function(grParent) {\n        var application = grParent.getValue('application');\n        var query = 'update_set=' + grParent.getValue('sys_id') + '^application!=' + application + '^ORapplication=NULL';\n        if (grParent.application == 'global' || grParent.application.scope == 'global') {\n            query = 'update_set=' + grParent.getValue('sys_id') + '^application!=' + application + '^applicationISNOTEMPTY^application.scope!=global';\n        }\n\n        var grUpdate = new GlideRecord('sys_update_xml');\n        grUpdate.addEncodedQuery(query);\n        grUpdate.setLimit(1);\n        grUpdate.query();\n\n        if (grUpdate.hasNext()) {\n            return true;\n        }\n\t\treturn false;\n    },\n\n    type: 'UpdateSetUtilCustom'\n};\n"
  },
  {
    "path": "Server-Side Components/Server Side/Update Variable Choices/README.md",
    "content": "Programatically update add a new choice for a service catalog variable and reorder all choices alphabetically. Can be helpful as part of a workflow where a fulfiller chooses logic to \"update the list\" variable, such as a small product or category list that may require ongoing updates as a result of fulfilling the request. Often unlisted choices are handled with an \"Other\" option and a text field to include the unlisted option. This script will take the value from the \"Other\" variable and add it to the choice list in the workflow."
  },
  {
    "path": "Server-Side Components/Server Side/Update Variable Choices/updateVariableChoices.js",
    "content": "var variable_id = 'YOUR SYS_ID HERE'; //Select box/choice Variable sys_id \nvar option_name = current.variables.other_string; //add your variable or otherwise that captures the new choice here\nvar value = option_name.replaceAll(' ', '_'); //remove spaces and replace with underscores to create the internal name\nvalue = value.toLowerCase(); //make the internal name lower case\n\nvar question_item = new GlideRecord('question_choice');\nquestion_item.initialize();\nquestion_item.setValue('text', option_name);\nquestion_item.setValue('value', value);\nquestion_item.setValue('question', variable_id); // using variable sys_Id here\n//question_item.setValue('order', 100);\nquestion_item.insert();\n\n//reorder in alphabetical\n//NOTE: Make sure your \"other\" choice has a very high order value (like 5000+ depeding on how you increment/number of choices)\n\nvar choices = new GlideRecord('question_choice');\nchoices.addQuery('question', variable_id);\nchoices.addQuery('value', '!=', 'other'); //don't include the \"Other\" choice in the query, it's order is very high to pin to the bottom of the list (8000)\nchoices.orderBy('text');\nchoices.query();\nvar order_increment = 100; //start order at 100\nwhile(choices.next()){\n\tchoices.setValue('order', order_increment);\n\tchoices.update();\n\torder_increment += 100; //increment order by 100 for each choice\n}"
  },
  {
    "path": "Server-Side Components/Server Side/Use System Property as an Object to Store Multiple Values and Retrieve Attributes When Needed/readme.md",
    "content": "**Create a property as shown below.**\n<img width=\"804\" height=\"219\" alt=\"image\" src=\"https://github.com/user-attachments/assets/6cf04f6c-35f1-49a4-977d-5f85abf8c119\" />\n\n**Use JSON.parse to convert it to an object.**\n**Handle errors using try-catch and retrieve attributes as required in any server-side configuration.**\nRefer script.js file\n\n\n**Output:**\n<img width=\"947\" height=\"404\" alt=\"image\" src=\"https://github.com/user-attachments/assets/e0fd6ca1-2e83-4c6d-949f-6f6878b51d7a\" />\n\n<img width=\"343\" height=\"103\" alt=\"image\" src=\"https://github.com/user-attachments/assets/fc8a4d4b-6879-4644-bd45-5f039ee5dd65\" />\n"
  },
  {
    "path": "Server-Side Components/Server Side/Use System Property as an Object to Store Multiple Values and Retrieve Attributes When Needed/script.js",
    "content": "var sysProperty = gs.getProperty(\"multi_value_object\");\ntry {\n    var out = JSON.parse(sysProperty); //convert string property value to object\n    for (var key in out) {\n        if (out[key]) {\n            gs.info(key + \":: \" + out[key]);\n        }\n    }\n} catch (e) {\n    gs.info(\"Failed to parse property: \" + e.message);\n}\n"
  },
  {
    "path": "Server-Side Components/Server Side/User Criteria/Does User Match Criteria(s).js",
    "content": "/*Use the following to determine if a user passes an array of user criterias\n/*@param userID - String of a sys_user sys_id\n/*@param userCriteria - Array of user_criteria sys_ids to check again\n/*@return Boolean\n*/\n\nvar userID = \"\",\n    userCriteria = [];\n\nvar result = sn_uc.UserCriteriaLoader.userMatches(userID , userCriteria);\n"
  },
  {
    "path": "Server-Side Components/Server Side/User Criteria/README.md",
    "content": "Use this script to determine if a user passes an array of user criterias\n\n\nuserID - String of a sys_user sys_id\n\nuserCriteria - Array of user_criteria sys_ids to check again\n"
  },
  {
    "path": "Server-Side Components/Server Side/Version 4 UUID Generator/README.md",
    "content": "# Description\n\nWhen creating events for any message buses it might happen that you have to provide a so-called UUID within the payload. However you cannot just use any ServiceNow Sys ID as unique identifier as a version 4 UUID has to follow a certain format (see [Wikipedia](https://en.wikipedia.org/wiki/Universally_unique_identifier)). \n\nAs I could not find any helper method within the ServiceNow API library I decided to implement my own version.\n\n# Usage\n\nJust call the function `generateUUID()` as often as you want. It will always generate a different UUID.\n\nExample Result:\n\n01ce5586-db98-1837-91cd-739e63c895b2\n"
  },
  {
    "path": "Server-Side Components/Server Side/Version 4 UUID Generator/uuid_generator.js",
    "content": "function generateUUID() { \n\tvar d1 = new Date().getTime();\n\tvar d2 = 0;\n\n\treturn 'xxxxxxxx-xxxx-zxxx-yxxx-xxxxxxxxxxxx'.replace(\n\t\t/[xyz]/g, \n\t\tfunction(c) {\n\t\t\tif (c === 'z') {\n\t\t\t\treturn String(Math.floor(Math.random() * 5) + 1);  \n\t\t\t}\n\n\t\t\tvar d3 = Math.random() * 16;\n\n\t\t\tif (d1 > 0) {\n\t\t\t\td3 = (d1 + d3) % 16 | 0;\n\t\t\t\td1 = Math.floor(d1 / 16);\n\t\t\t} \n\t\t\telse {\n\t\t\t\td3 = (d2 + d3) % 16 | 0;\n\t\t\t\td2 = Math.floor(d2 / 16);\n\t\t\t}\n\n\t\t\treturn (c === 'x' ? d3 : (d3 & 0x3 | 0x8)).toString(16);\n\t\t}\n\t);\n}\n\ngs.info(generateUUID());\n"
  },
  {
    "path": "Server-Side Components/Server Side/getUserGroupMembers/README.md",
    "content": "Script to get members of groups which our users also belongs to.\n\nexample: added Abel to 2 HR groups and then printing the names of members who belong to those groups.\n\n![image](https://user-images.githubusercontent.com/46869542/193464977-53b613db-650b-452c-ac72-fbee5196605f.png)\n"
  },
  {
    "path": "Server-Side Components/Server Side/getUserGroupMembers/script.js",
    "content": "function getUserGroupMembers(userSysID) {\n\n  var ourUser = userSysID || gs.getUserID(),\n    usrdetails = {};\n  usrdetails.grps = [],\n    usrdetails.sharedUsrs = {};\n\n  var grmemberGRPS = new GlideRecord('sys_user_grmember');\n  grmemberGRPS.addQuery('user', ourUser)\n  grmemberGRPS.query();\n  while (grmemberGRPS.next()) {\n\n    var user = grmemberGRPS.user.getRefRecord();\n    usrdetails.user = user.getValue('name');\n\n    var grp = grmemberGRPS.group.getRefRecord();\n    usrdetails.grps.push(grp.getValue('name'));\n\n    usrdetails['sharedUsrs'][grp.getValue('name')] = [];\n\n    var grmemberUSRS = new GlideRecord('sys_user_grmember');\n    grmemberUSRS.addQuery('group', grp.getValue('sys_id'));\n    grmemberUSRS.addQuery('user', '!=', ourUser);\n    grmemberUSRS.query();\n    while (grmemberUSRS.next()) {\n      var usr = grmemberUSRS.user.getRefRecord();\n      usrdetails['sharedUsrs'][grp.getValue('name')].push(usr.getValue('name'));\n    }\n\n  }\n\n  var result = \"\\nUSER '\" + usrdetails.user + \"' BELONGS TO FOLLOWING GROUPS: \" + usrdetails.grps.sort().join(', ') + '\\n\\n';\n\n  for (var i in usrdetails.sharedUsrs) {\n    result += ('Group \"' + i + '\" shared members : ' + usrdetails['sharedUsrs'][i].sort().join(', ') + '\\n\\n');\n  }\n\n  return result;\n}\n\n// added Abel to 2 HR groups and then printing the names of members who belong to those groups.\ngs.info(getUserGroupMembers('62826bf03710200044e0bfc8bcbe5df1')); //Abel Tuter sys_id\n\n\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Check if the Import file is valid/README.md",
    "content": "**Example use Case:**\n\nVendor data is periodically imported into ServiceNow via a scheduled data load (import set) sourced from an external application. These files contain only valid vendor records. After the import, any existing vendor records in ServiceNow that are not present in the latest file should be marked as inactive.\n\n**Risk:**\n\nIf the incoming file is empty due to an issue in the source application, all existing vendor records in ServiceNow could be incorrectly marked as inactive, resulting in data loss or disruption.\n\n**Solution:**\n\nTo prevent this, implement an \"onStart\" transform script that checks whether the import set contains any data before proceeding with the transformation. If it is found to be empty, the script should:\n\n1. Abort the transformation process.\n2. Automatically raise a ticket to the responsible team for investigation.(Optional)\n\n\n   \nThis ensures that the existing vendor data in ServiceNow remains unchanged until the issue is resolved.\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Check if the Import file is valid/script.js",
    "content": "/*\nWhen : onStart\nActive : True\n*/\n\n(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {\n\n    var table = import_set.table_name;\n    var run = new GlideAggregate(table);\n    run.addQuery(\"sys_import_set\", import_set.sys_id);\n    run.addAggregate('COUNT');\n    run.query();\n    while (run.next()) {\n        var count = parseInt(run.getAggregate('COUNT'));\n        if (count < 1) { // Check the row count of the latest import job. If it's 0, then abort the transformation and raise a ticket (Optional)\n            ignore = true;\n            gs.error(\"File is empty. Hence aborting the transformation\");\n\n\t\t\t// Creating a ticket to the fulfillment team. This step is optional.\n            var incident = new GlideRecord('incident');\n            incident.initialize();\n            incident.short_description = \"Import failed due to empty file\";\n            incident.description = \"The incoming file was empty. Please investigate.\";\n            incident.assignment_group = gs.getProperty('Fallback queue'); // Store the resolver group sys id in the system property and use it here.\n            incident.impact = 3; // Modify as required\n            incident.urgency = 3; // Modify as required\n            incident.insert();\n\n        }\n    }\n\n})(source, map, log, target);\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Choice Field Validator/README.md",
    "content": "# **Choice Field Validator**\n\nFunction that returns the value of a choice by its display value. Initially created to be used in field map scripts.\nUsed to return the choice even if the instance is in different language.\n\n\n## *Important points*\n- It is imperative that the display value in the transform map table exists in the instance\n- It is possible to validate the values of choices dependent on other choices\n- To get the dependent choice you need set and static value or run the function for the first choice and then with the dependent choice (as in the second example).\n\n\n## **Example configuration**\n\n1. Category validation:\n![categoryvalidation](choice_validador1.png)\n\n2. Category and subcategory validation:\n![categorysubcategoryvalidation](choice_validador1.png)\n\n\nIn these previous cases we used the validator because some users use Portuguese language and all options in the excel are in English. With the functions we don't need to worry about the different languagues.\n\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Choice Field Validator/choiceValidatorUtil.js",
    "content": "    /**SNDOC\n        @name choiceValidador\n\n        @param {string} [table] - table that the choice was created\n        @param {string} [fieldValue] - field name value\n        @param {string} [inputValue] - choice display value (source.field)\n        @param {boolean} [isDependent] - if true, add one more parameter to find the depedent\n        @param {string} [dependentValue] - value of the parent choice\n        \n        @return {string} - null || the value of the choice\n    */\n\n    choiceValidador: function(table, fieldValue, inputValue, isDependent, dependentValue) {\n        var answer = null;\n\n        var grChoice = new GlideRecord('sys_choice');\n        grChoice.addQuery('name', table);\n        grChoice.addQuery('element', fieldValue);\n        grChoice.addQuery('label', inputValue);\n        \n        if (isDependent)\n            grChoice.addQuery('dependent_value', dependentValue);\n\n        grChoice.query();\n\n        if (grChoice.next()) {\n            answer = grChoice.getValue('value');\n        }\n\n        return answer;\n    }\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Conditional Coalesce/README.md",
    "content": "**Conditional Coalesce on Trasnform Maps**\n\nWhen you have more than one field you want to coalesce based on some conditions, you can create a field mapping where source is a script and target field is SYS_ID.\nYou can put your conditional logic in the source script to do a conditional coalesce. Return the sys_id of the matched record for the transform to update it. Return -1 when there is not a match and you want to create a new record.\n\n**Example configuration**\n\n![Configuration](conditional_coalesce.png)\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Conditional Coalesce/conditional_coalasce.js",
    "content": "answer = (function transformEntry(source) {\n\n    var grServer = new GlideRecord('cmdb_ci_server');\n\n    if (source.u_mac_address != '' && grServer.get('mac_address', source.u_mac_address)) {\n        return grServer.sys_id;\n    } else if (source.u_serial_number != '' && grServer.get('serial_number', source.u_serial_number)) {\n        return grServer.sys_id;\n    } else if (source.u_name != '' && grServer.get('name', source.u_name)) {\n        return grServer.sys_id;\n    } else {\n        // No match. Create a new record\n        return -1;\n    }\n\n})(source);\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Email Formatter/README.md",
    "content": "The purpose of this script is to validate email addresses during a data import in ServiceNow. It ensures that the email addresses conform to a standard format (i.e., are valid email addresses), converts them to lowercase, trims any extra spaces, and logs invalid email addresses for review or further action.\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Email Formatter/emailFormatterValidator",
    "content": "(function checkEmail(source, target, map, log, isUpdate) {\n// Replace with your source email field\n    var email = source.u_email; \n    if (email) {\n        email = email.trim().toLowerCase();\n        //Regular Expression to validate email pattern\n        var emailPattern = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n        if (emailPattern.test(email)) {\n        // Replace with your target email field\n            target.email = email; \n        } else {\n            log.info('Invalid email format for ' + email);\n        }\n    }\n})(source, target, map, log, isUpdate);\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Global Variable in Transform Map/README.md",
    "content": "# Global Manager Map in Transform Script\r\n\r\n## Overview\r\n\r\nThis snippet demonstrates how to build and reuse a **global lookup map** across Transform Map scripts in ServiceNow — specifically, mapping **Manager Email → sys_id** from the `sys_user` table.\r\n\r\nBy defining the map using `this.managerMap` in an **onStart Transform Script**, the data becomes available to **onBefore** or **onAfter** scripts during the same import execution, eliminating repetitive queries and improving performance.\r\n\r\n> **DISCLAIMER**  \r\n> This script was developed and tested on a **ServiceNow Personal Developer Instance (PDI)**.  \r\n> It is intended for **educational and demonstration purposes only**.  \r\n> Please **test thoroughly in a non-production environment** before deploying to production.\r\n\r\n---\r\n\r\n## What it does\r\n\r\n- Preloads a dictionary of all system users (`sys_user` table) into memory.\r\n- Maps user **email addresses** (lowercased) to their corresponding **sys_ids**.\r\n- Shares the map globally across transform script stages (onStart → onBefore → onAfter).\r\n- Eliminates the need for repeated `GlideRecord` queries in each transform stage on each row.\r\n- Significantly improves transform performance when importing large data sets.\r\n\r\n---\r\n\r\n## Prerequisites & Dependencies\r\n\r\nBefore using this snippet, ensure that:\r\n\r\n1. **Transform Map Context**\r\n\r\n   - The script must be placed in a **Transform Map > Script** (e.g., “onStart” or “Run Script”).\r\n   - Other transform scripts in the same map (e.g., onBefore or transformRow) can then reference `this.managerMap`.\r\n\r\n2. **sys_user Table Access**\r\n\r\n   - The transform user must have permission to **read** the `sys_user` table.\r\n\r\n3. **Valid Email Data**\r\n   - Ensure the `sys_user.email` field is populated and unique for all users who may appear as managers.\r\n\r\n---\r\n\r\n## Script Details\r\n\r\n- **Author:** Anasuya Rampalli ([anurampalli](https://github.com/anurampalli))\r\n- **Version:** 1.0\r\n- **Date:** 2025-10-10\r\n- **Context:** Transform Map → Run Script (onStart)\r\n- **Tested On:** ServiceNow Personal Developer Instance (PDI)\r\n\r\n---\r\n\r\n## Example Scripts\r\n\r\n### onBefore Script — Use Global Variable\r\n\r\n```javascript\r\n(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {\r\n\r\n    // Name split\r\n    if (source.u_full_name) {\r\n        var parts = source.u_full_name.trim().split(/\\s+/);\r\n        target.first_name = parts[0];\r\n        if (parts.length > 1) {\r\n            target.last_name = parts.slice(1).join(\" \");\r\n        }\r\n    }\r\n\r\n    // Email normalize\r\n    if (source.u_email) {\r\n        target.email = source.u_email.toString().toLowerCase();\r\n    }\r\n\r\n    // Manager mapping\r\n    var managerMap = this.managerMap;\r\n    gs.info('manager map: ' + managerMap);\r\n    if (source.u_manager_email && managerMap) {\r\n\r\n        var managerEmail = source.u_manager_email.toString().toLowerCase();\r\n        var managerSysId = managerMap[managerEmail];\r\n        gs.info('TM HR User Dump managerSysId: ' + managerSysId);\r\n\r\n        if (managerSysId) {\r\n            target.manager = managerSysId;\r\n        } else {\r\n            log.warn(\"Manager email not found: \" + managerEmail + \" for user \" + source.u_full_name);\r\n            target.manager = \"\"; // optional: blank manager instead of error\r\n        }\r\n    }\r\n\r\n\r\n})(source, map, log, target);\r\n```\r\n\r\n---\r\n\r\n## Screenshots\r\n\r\n| Screenshot                                                                                                | Description                                                      |\r\n| --------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------- |\r\n| ![UsingGlobalManagerMapInOnBeforeScriptScreenshot](./UsingGlobalManagerMapInOnBeforeScriptScreenshot.png) | onBefore script referencing the shared variable.                 |\r\n| ![OnstartScriptScreenshot](./OnstartScriptScreenshot.png)                                                 | onStart script defining the global variable (`this.managerMap`). |\r\n\r\n\r\n---\r\n\r\n```\r\n\r\n```\r\n\r\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Global Variable in Transform Map/manager_email_to_sysid_map_onStartScript.js",
    "content": "/**\r\n * Script: Manager Email → Sys_id Map\r\n * Type: Transform Map Script (Run Script)\r\n *\r\n * Purpose:\r\n * This transform script builds an in-memory dictionary (map) that links\r\n * manager email addresses to their corresponding sys_ids in the `sys_user` table.\r\n *\r\n * Why:\r\n * During a data import or transform, multiple records may need to look up\r\n * the same manager repeatedly. Instead of running a GlideRecord query every time,\r\n * this preloads all manager sys_ids once for efficient lookup.\r\n *\r\n * Key Concept:\r\n * - `this.managerMap` is used instead of `var managerMap` so that the map\r\n *   persists across multiple transform runs (e.g., onBefore/transformRow/onAfter)\r\n *   within the same Transform Map execution context.\r\n * - Variables defined with `this` become properties of the transform script's\r\n *   execution object, allowing reuse across all functions available with the specific transform map pbject.\r\n *\r\n * Example usage in an onBefore/transformRow script:\r\n *   var managerSysId = this.managerMap[source.manager_email.toLowerCase()];\r\n */\r\n\r\n(function runTransformScript(source, map, log, target /*undefined onStart*/) {\r\n  // Build dictionary of Manager Emails → Sys_id\r\n  this.managerMap = {};\r\n  var grManagerUser = new GlideRecord(\"sys_user\");\r\n  grManagerUser.query();\r\n  while (grManagerUser.next()) {\r\n    managerMap[grManagerUser.email.toString().toLowerCase()] =\r\n      grManagerUser.sys_id.toString();\r\n  }\r\n})(source, map, log, target);\r\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Global Variable in Transform Map/onBefore_transform_script.js",
    "content": "(function runTransformScript(source, map, log, target /*undefined onStart*/) {\n  // Name split\n  if (source.u_full_name) {\n    var parts = source.u_full_name.trim().split(/\\s+/);\n    target.first_name = parts[0];\n    if (parts.length > 1) {\n      target.last_name = parts.slice(1).join(\" \");\n    }\n  }\n\n  // Email normalize\n  if (source.u_email) {\n    target.email = source.u_email.toString().toLowerCase();\n  }\n\n  // Manager mapping\n  var managerMap = this.managerMap;\n  gs.info(\"manager map: \" + managerMap);\n  if (source.u_manager_email && managerMap) {\n    var managerEmail = source.u_manager_email.toString().toLowerCase();\n    var managerSysId = managerMap[managerEmail];\n    gs.info(\"TM HR User Dump managerSysId: \" + managerSysId);\n\n    if (managerSysId) {\n      target.manager = managerSysId;\n    } else {\n      log.warn(\n        \"Manager email not found: \" +\n          managerEmail +\n          \" for user \" +\n          source.u_full_name\n      );\n      target.manager = \"\"; // optional: blank manager instead of error\n    }\n  }\n})(source, map, log, target);\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Incident Priority Set on Insert Only/README.md",
    "content": "# Incident priority set on insert only\n\n## What this solves\nRecurring imports often overwrite priority even after the service desk has triaged the ticket. This onBefore script sets priority only when a row is inserted. Updates pass through without touching priority.\n\n## Where to use\nAttach to your Incident Transform Map as an onBefore script.\n\n## How it works\n- Checks the Transform Map action variable for insert vs update\n- On insert, computes priority from impact and urgency\n- On update, does nothing to priority\n\n## References\n- Transform Map scripts  \n  https://www.servicenow.com/docs/bundle/zurich-integrate-applications/page/administer/import-sets/task/t_AddOnBeforeScriptToTransformMap.html\n- Incident fields and priority logic  \n  https://www.servicenow.com/docs/bundle/zurich-it-service-management/page/product/incident-management/concept/incident-fields.html\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Incident Priority Set on Insert Only/onBefore_set_priority_insert_only.js",
    "content": "// Transform Map onBefore script: set priority on insert, not on update\n(function runTransformScript(source, map, log, target) {\n  try {\n    if (String(action).toLowerCase() !== 'insert') {\n      log.info('Priority unchanged on update for number: ' + (target.number || '(new)'));\n      return;\n    }\n\n    var impact = Number(source.impact) || 3;   // 1..3 typical\n    var urgency = Number(source.urgency) || 3; // 1..3 typical\n\n    // Simple matrix: priority = impact + urgency - 1, clamped 1..5\n    var p = Math.max(1, Math.min(5, (impact + urgency) - 1));\n    target.priority = String(p);\n    log.info('Priority set on insert to ' + p);\n  } catch (e) {\n    log.error('Insert-only priority failed: ' + e.message);\n  }\n})(source, map, log, target);\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Verify headers of a CSV attached file/README.md",
    "content": "# Checking we have the right headers in a CSV file we want to transform\n\nMaking sure we're dealing with the right headers in a CSV file we want to import can be handy, especially in an automated CSV file transform process.\n\nThis snippet takes a Data Source, read the attachments (assumed being CSV here) and make sure the first line contains the right headers (passed as a variable). It can also make sure no headers are missing. Being sure we have the right headers can make the Transform Map scripts a lot easier to develop.\n\nThere is an example of CSV file with this code snippet (example.csv). To test this code snippet, attach the file to any data source record, get its sys_id and update the script accordingly.\n\nOutput should be like this:\n```\n*** Script: [DEBUG] File example.csv:\n*** Script: [DEBUG] The following columns are unknown: \"Middle Name\", \"Age\", \"City\"\n*** Script: [DEBUG] The following columns are missing: \"Country\"\n```"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Verify headers of a CSV attached file/example.csv",
    "content": "First Name,Last Name,Middle Name,Age,City\nRobert,Nancy,Jr.,53,Boston\n"
  },
  {
    "path": "Server-Side Components/Transform Map Scripts/Verify headers of a CSV attached file/verifyCSVHeaders.js",
    "content": "function checkCSVHeaders(grAttachment, separator, acceptedColumns, reverseMatch) {\n\n    var i = 0, l = 0;\n\n    // Arrays of file columns and their corresponding lower case version\n    var lcAcceptedColumns = [], attColumns = [], lcAttColumns = [];\n\n    // Returned object\n    var result = {\n        error: false,\n        unknownColumns: [],\n        missingColumns: []\n    };\n\n    // Get the first line of the attachment (only support CSV for now)\n    var firstLine = '';\n\n    if (grAttachment.getValue('content_type') == 'text/csv') {\n\n        // A pointer to the attachment in streaming mode\n        var attachmentContentStream = (new GlideSysAttachment()).getContentStream(grAttachment.getValue('sys_id'));\n\n        // Read the first line \n        firstLine =  new GlideTextReader(attachmentContentStream).readLine();\n\n    } else {\n\n        return undefined;\n    }   \n\n    // Remove all Byte Order Mark that may have been hidden in the CSV file\n    firstLine = firstLine.replace(/\\uFEFF/g, '');\n\n    // Exit now if the first line has nothing\n    if (JSUtil.nil(firstLine)) {\n\n        return undefined;\n\n    } else {\n\n        // Make sure all accepted column names are in LC\n        lcAcceptedColumns = acceptedColumns.map(function(value) {\n            return value.toLowerCase();\n        });\n\n        // Get a lowercase version of all the columns of the attachement\n        attColumns = firstLine.split(separator);\n        lcAttColumns = attColumns.map(function(value) {\n            return value.toLowerCase();\n        });\n\n        // Verify that all the columns of the template are accepted\n        for (i = 0, l = lcAttColumns.length; i < l; i++) {\n            if (lcAcceptedColumns.indexOf(lcAttColumns[i]) == -1) {\n                result.unknownColumns.push('\"' + attColumns[i] + '\"');\n            }\n        }\n        // Optionally, verify that we're not missing any columns in the template\n        if (reverseMatch) {\n            for (i = 0, l = lcAcceptedColumns.length; i < l; i++) {\n                if (lcAttColumns.indexOf(lcAcceptedColumns[i]) == -1) {\n                    result.missingColumns.push('\"' + acceptedColumns[i] + '\"');\n                }\n            }\n        }\n    }\n\n    result.error = ((result.unknownColumns.length > 0) || (result.missingColumns.length > 0));\n    return result;\n}\n\n// Testing the function by reading the attachment of a data source (could work with any record type though)\n\nvar checkMissingColumns = true;\n\nvar grAttachment = (new GlideSysAttachment()).getAttachments('sys_data_source', '01177d530a2581020015e062c8ef3bda');\n\nwhile (grAttachment.next()) {\n\n    var headersCheck = checkCSVHeaders(grAttachment, ',' , ['First Name', 'Last Name', 'Country'], checkMissingColumns);\n    if (headersCheck.error) {\n        gs.debug('File ' + grAttachment.getValue('file_name') + ':');\n        gs.debug('The following columns are unknown: ' + headersCheck.unknownColumns.join(', '));\n        if (checkMissingColumns) {\n            gs.debug('The following columns are missing: ' + headersCheck.missingColumns.join(', '));\n        }\n    }\n\n}\n"
  },
  {
    "path": "Specialized Areas/ATF Steps/Count table records/README.md",
    "content": "Use Case: Find the Total Number of Records in a Table Using the ATF Step \"Run Server Side Script\"\n\nUsing existing ATF steps (without scripting), it is very difficult to find the record count of a table.\n\nBy using the ATF test step \"Run Server Side Script\" with a simple script, we can count the records and also log/pass the count to the next ATF step if required.\n\nSteps:\n\nNavigate to Automated Test Framework >> Tests >> Click on New Test.\nGive a name to the test and provide a description.\nGo to the Test Steps related list and click Add Test Step.\nNavigate to Server and choose Run Server Side Script.\nAdd the script (the script is in the script.js file).\nSave the test and run it to see the results.\n"
  },
  {
    "path": "Specialized Areas/ATF Steps/Count table records/script.js",
    "content": "(function(outputs, steps, params, stepResult, assertEqual) {\n// add test script here\n  var gr = new GlideAggregate('incident');\n  gr.addAggregate('COUNT');\n  gr._query();\n  if (gr.next()) {\n    stepResult.setOutputMessage(\"Successfully Calculated the Count\");\n\treturn gr.getAggregate('COUNT'); // pass the step\n  } else { \n    stepResult.setOutputMessage(\"Failed to Count\");\n    return false; // fail the step\n  }\n\n})(outputs, steps, params, stepResult, assertEqual);\n"
  },
  {
    "path": "Specialized Areas/ATF Steps/Validate RITM Due Date/README.md",
    "content": "## Description:\nThis script will calculate a due date for a RITM based off of the delivery date of the Catalog Item and validate it matches the actual due date.\n\n## Usage Instructions/Examples:\nYou can use this in a \"Run Server Side Script\" ATF test step. This is specfic for RITM's and Catalog Item's with Delivery Times\n\n## Prerequisites/Dependencies:\n1) In your ATF Test Case, you need to create a ATF Test Step that does a Record Query for the RITM record before running this script. The sys id of that Record Query Step is used in the scipt to obtain a GlideRecord of the RITM.\n2) You need a system property called \"glide.sc.item.delivery_schedule\" that contains the sys id of a record on the cmn_schedule table that will be used for the due date calculation\n"
  },
  {
    "path": "Specialized Areas/ATF Steps/Validate RITM Due Date/atf_ritm_due_date_script.js",
    "content": "(function(outputs, steps, params, stepResult, assertEqual) {\n    // Calculate what the due date should be and compare to the actual due date\n    var ritmQuerySysId = ''; // This is the sys id of the Record Query ATF Test Step for the RITM record (NOTE: This needs toe be updated to match your test step)\n    var grRITM = new GlideRecord('sc_req_item');\n    grRITM.get(steps(ritmQuerySysId).first_record);\n    var calculatedDue; // set up our calculation\n\n    var deliveryTime = grRITM.cat_item.delivery_time.dateNumericValue(); // get delivery time in ms\n\n    if (deliveryTime) {\n        var dur = new GlideDuration(deliveryTime);\n        var scheduleID = gs.getProperty('glide.sc.item.delivery_schedule'); // Property contains the sys id of the schedule used for this date calculation\n        var schedule = new GlideSchedule(scheduleID);\n        var gdt = new GlideDateTime(grRITM.opened_at); // due date should be set based on the item's opened timestamp\n        calculatedDue = schedule.add(gdt, dur);\n    }\n\n    var actualDue = new GlideDateTime(grRITM.due_date);\n\n    testAssertion = {\n        name: \"The Due Dates Match!\",\n        shouldbe: calculatedDue,\n        value: actualDue\n    };\n    assertEqual(testAssertion);\n\n})(outputs, steps, params, stepResult, assertEqual);\n"
  },
  {
    "path": "Specialized Areas/Advanced Conditions/Exclude Email Reply Comment Notifications by Group/README.md",
    "content": "# Exclude Email Replies from Comment Notifications based on Group Membership\n\n### This is an advanced condition script for new comment notifications that will exclude new comments that are email replies for specific groups.  \n\nStep 1.\nSpecify the user field such as assigned_to, caller_id, or opened_by, etc in the line of code below:\n\n```js \nvar groupMember = current.getValue('assigned_to'); //get value of the desired field\n```\n\nStep 2.\nSpecify the group name such as Service Desk, Network Team, or CAB Approvers etc in the line of code below:\n\n```js\nif (groupMember.isMemberOf('Special Group')) { //check if membership is true\n```\n"
  },
  {
    "path": "Specialized Areas/Advanced Conditions/Exclude Email Reply Comment Notifications by Group/exclude_email_reply_comment_notifications_by_group.js",
    "content": "(function() {\n    //exclude email replies based on group membership\n    var answer = true; //default is true\n    var groupMember = current.getValue('assigned_to'); //get value of the desired field\n    groupMember = gs.getUser().getUserByID(groupMember); //prepare the user data for the isMemberOf lookup function\n    if (groupMember.isMemberOf('Special Group')) { //check if membership is true\n        var notes = current.comments.getJournalEntry(1).split('\\n'); //gather most recent comment and split each new line into a new array\n        if (notes[1].indexOf('reply from:') == 0) { //check first new line and if it starts with 'reply from:', don't send an email\n            answer = false;\n        }\n    }\n    return answer;\n})();\n"
  },
  {
    "path": "Specialized Areas/Advanced Conditions/Group Approval Check/README.md",
    "content": "# Group Approval Check\n\n### This is an email notification advanced condition script for approval requests that will check if an approval has already been granted prior to sending out a request.\n\nThis script can be applied to any approval request notification on the sysapproval_approver table with no additional configuration necessary\n"
  },
  {
    "path": "Specialized Areas/Advanced Conditions/Group Approval Check/group_approval_check.js",
    "content": "(function () {\n    if (!JSUtil.nil(current.group)) { // is there a group approval record?\n        var grg = new GlideRecord(\"sysapproval_approver\");\n        grg.addQuery('group', current.group); // if so, check for other approvers in the same group\n        grg.addQuery('state', 'approved'); // look for approved records\n        grg.query();\n        if (grg.hasNext()) { // if any records exist\n            return false; // return false, don't send an email\n        }\n    }\n    return true; // otherwise, return true and send an email\n})();\n"
  },
  {
    "path": "Specialized Areas/Agile Development/Burndown Chart/README.md",
    "content": "## Description\nThe planned lines of the ServiceNow burndown chart do not take holidays into account.  \nBy using this Python snippet, you can create a burndown chart with planned lines that take holidays into account.  \nThe generated burndown chart can also be automatically deployed as an image to Slack and other tools.  \n\n## Requirements\nOS: Windows/MacOS/Unix  \nPython: Python3.x  \nServiceNow: More than Vancouver  \nPlugins: Agile Development 2.0 (com.snc.sdlc.agile.2.0) is installed\n\n## Installation\nClone the repository and place the \"Burndown Chart\" directory in any location.  \nExecute the following command to create a virtual environment.  \n<code>python3 -m venv .venv\nmacOS/Unix: source .venv/bin/activate\nWindows: .venv\\Scripts\\activate\npip install -r requirements.txt\n</code>\n\n## Usage\n1. Go to the Burndown Chart directory.\n2. Prepare the following values ​​according to your environment:\n- InstanceName: Your instance name (e.g. dev000000 for PDI)\n- Credentials: Instance login information in Base64 (Read permission to the rm_sprint table is required.)\n- Sprint Name: Target sprint name from the Sprint[rm_sprint] table\n\n3. Run the command to install the required modules.  \n<code>pip install -r equirements.txt</code>\n\n5. Run sprint_burndown_chart.py.  \n<code>python3 sprint_burndown_chart.py INSTANCE_NAME BASE64_ENCODED_STRING(USERID:PASSWORD) SPRINT_NAME</code>  \nexample:  \n<code>python3 sprint_burndown_chart.py dev209156 YXBpOmpkc0RhajNAZDdKXnNmYQ== Sprint1</code>  \n\nWhen you run it, a burndown chart image like the one shown will be created.\n![figure](https://github.com/user-attachments/assets/50d3ffc2-4c66-4f4d-bb69-c2b98763621d)\n"
  },
  {
    "path": "Specialized Areas/Agile Development/Burndown Chart/requirements.txt",
    "content": "jpholiday==0.1.10\nmatplotlib==3.9.2\nnumpy==2.0.2\npandas==2.2.3\npytz==2024.2\nrequests==2.32.3\nurllib3==1.26.13\n"
  },
  {
    "path": "Specialized Areas/Agile Development/Burndown Chart/sprint_burndown_chart.py",
    "content": "import argparse\nimport pprint\nimport requests\nimport datetime\nimport matplotlib.dates as mdates\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport urllib.request\nimport urllib.parse\nimport json\nimport datetime\nfrom pytz import timezone\n\n# ---- #\n# init #\n# ---- #\npoint_dict = {}\ntotal_points = 0\ndone = 0\nundone = 0\nparser = argparse.ArgumentParser()\nparser.add_argument('instancename')\nparser.add_argument('authstring')\nparser.add_argument('sprintname')\nargs = parser.parse_args()\nBASIC = 'Basic ' + args.authstring\n\n# ---------- #\n# Get Sprint #\n# ---------- #\nparams = {\n    'sysparm_query': 'short_description=' + args.sprintname\n}\nparam = urllib.parse.urlencode(params)\nurl = \"https://\" + args.instancename + \".service-now.com/api/now/table/rm_sprint?\" + param\nreq = urllib.request.Request(url)\nreq.add_header(\"authorization\", BASIC)\nwith urllib.request.urlopen(req) as res:\n    r = res.read().decode(\"utf-8\")\nobj = json.loads(r)\n# Get the start and end dates of a Sprint\nstart_date = obj['result'][0]['start_date']\nstart_date = (datetime.datetime.strptime(start_date, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=9)).date()\nprint(start_date)\nend_date = obj['result'][0]['end_date']\nend_date = (datetime.datetime.strptime(end_date, '%Y-%m-%d %H:%M:%S') + datetime.timedelta(hours=9)).date()\n# Initializing the points array\nwhile start_date <= end_date:\n    point_dict[str(start_date)] = 0\n    start_date = start_date + datetime.timedelta(days=1)\n# --------- #\n# Get Story #\n# --------- #\nparams = {\n    'sysparm_query': 'sprint.short_descriptionLIKE' + args.sprintname\n}\nparam = urllib.parse.urlencode(params)\nurl = \"https://\" + args.instancename + \".service-now.com/api/now/table/rm_story?\" + param\nreq = urllib.request.Request(url)\nreq.add_header(\"authorization\", BASIC)\nwith urllib.request.urlopen(req) as res:\n    r = res.read().decode(\"utf-8\")\nobj = json.loads(r)\n# Story Loop\nfor name in obj['result']:\n    if len(name['story_points']) > 0:\n        total_points += int(name['story_points'])\n        if name['closed_at'] != '':\n            close_date = datetime.datetime.strptime(\n                name['closed_at'], '%Y-%m-%d %H:%M:%S')\n            close_date = close_date.date()\n            if name['state'] == '3':\n                if str(close_date) in point_dict:\n                    point_dict[str(close_date)] += int(name['story_points'])\n                else:\n                    point_dict[str(close_date)] = int(name['story_points'])\n        if name['state'] == '3':\n            done += int(name['story_points'])\n        else:\n            undone += int(name['story_points'])\ncounta = 0\nfor i in point_dict.items():\n    counta += int(i[1])\n    point_dict[i[0]] = total_points - counta\nplt.xkcd()\nfig, ax = plt.subplots()\n# Creating a performance line\nx = []\ny = []\nplt.ylim(0, total_points + 5)\ncounta = 0\nfor key in point_dict.keys():\n    if datetime.datetime.today() >= datetime.datetime.strptime(key, '%Y-%m-%d'):\n        x.append(datetime.datetime.strptime(key, '%Y-%m-%d'))\n        y.append(point_dict[key])\n# Holiday determination\nDATE = \"yyyymmdd\"\ndef isBizDay(DATE):\n    Date = datetime.date(int(DATE[0:4]), int(DATE[4:6]), int(DATE[6:8]))\n    if Date.weekday() >= 5:\n        return 0\n    else:\n        return 1\n# Get the number of weekdays\ntotal_BizDay = 0\nfor key in point_dict.keys():\n    if isBizDay(key.replace('-', '')) == 1:\n        total_BizDay += 1\n# Creating an ideal line\nx2 = []\ny2 = []\npoint_dict_len = len(point_dict)\naverage = total_points / (total_BizDay - 1)\nfor key in point_dict.keys():\n    dtm = datetime.datetime.strptime(key, '%Y-%m-%d')\n    x2.append(dtm)\n    y2.append(total_points)\n    # If the next day is a weekday, consume the ideal line.\n    if isBizDay((dtm + datetime.timedelta(days=1)).strftime(\"%Y%m%d\")) == 1:\n        total_points -= average\ndays = mdates.DayLocator()\ndaysFmt = mdates.DateFormatter('%m/%d')\nax.xaxis.set_major_locator(days)\nax.xaxis.set_major_formatter(daysFmt)\nplt.title(\"\" + args.sprintname + \" Burndown\")\nplt.plot(x2, y2, label=\"Ideal\", color='green')\nplt.plot(x2, y2, marker='.', markersize=20, color='green')\nplt.plot(x, y, label=\"Actual\", color='red')\nplt.plot(x, y, marker='.', markersize=20, color='red')\nplt.grid()\nplt.xlabel(\"Days\")\nplt.ylabel(\"Remaining Effort(pts)\")\nplt.subplots_adjust(bottom=0.2)\nplt.legend()\n# Viewing the graph\n# plt.show()\n# Saving a graph\nplt.savefig('figure.png')\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Copy URL to ServiceNow Journal/README.md",
    "content": "## Copy URL to ServiceNow Journal\nA bookmarklet that can be used on any website to copy the website's title and URL to the clipboard as a [code]...[/code] snippet that can be pasted to a Journal field to create a \"fancy\" clickable link in Comments or Work notes.\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Copy URL to ServiceNow Journal/copy url to servicenow journal.js",
    "content": "javascript:function copyToClipboard(t,e){link_plaintext='[code]<a href=\"'+e+'\" target=_blank>'+t+\"</a>[/code]\",link_formatted='<a href=\"'+e+'\">'+t+\"</a>\";var a=function(t){t.preventDefault(),t.clipboardData.setData(\"text/html\",link_formatted),t.clipboardData.setData(\"text/plain\",link_plaintext)};document.addEventListener(\"copy\",a),document.execCommand(\"copy\"),document.removeEventListener(\"copy\",a)}copyToClipboard(document.title,window.location.href);\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Create new update set/README.md",
    "content": "# Create udpate set\n\nWhen viewing a record in the rm_story table, this bookmarklet will create a update set in a DIFFERENT instance and enable you to pre-populate values. The example below will create an update set in the ficticious \"MYDEV\" instance and set the Name (`STRY1234 - Short Description`) and Description fields based on values taken from the story record. To use this bookmarklet, udpate the instance name and query string as needed.\n\n```js\njavascript:\nvar w=window.frames[\"gsft_main\"]!==undefined?window.frames[\"gsft_main\"]:window;\nvar q=\"name=\"+w.g_form.getValue(\"number\")+\" - \"+w.g_form.getValue(\"short_description\")+\n    \"^description=Description:  @\"+w.g_form.getValue(\"description\");\ntop.open(\"https://MYDEV.service-now.com/sys_update_set.do?sys_id=-1&sysparm_query=\"+q);\n\n```"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Create new update set/create_update_set.js",
    "content": "javascript:var w=window.frames[\"gsft_main\"]!==undefined?window.frames[\"gsft_main\"]:window;var q=\"name=\"+w.g_form.getValue(\"number\")+\" - \"+w.g_form.getValue(\"short_description\")+\"^description=Description:  @\"+w.g_form.getValue(\"description\");top.open(\"https://MYDEV.service-now.com/sys_update_set.do?sys_id=-1&sysparm_query=\"+q);\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Create story task/README.md",
    "content": "# Create a new story task\n\nWhen viewing a record in the rm_story table, this bookmarklet will create a new child task and enable you to pre-populate values. The example below will create a task of type `Testing` and set the short description to `Test STRY12345 - Short Description` where the story number and short description values are taken from the story record.\n\n```js\njavascript:\nvar w=window.frames[\"gsft_main\"]!==undefined?window.frames[\"gsft_main\"]:window;\nvar q=\"parent=\"+w.g_form.getUniqueValue()+\n\"^type=4\"+\n\"^short_description=Test \"+w.g_form.getValue(\"number\")+\" - \"+w.g_form.getValue(\"short_description\")+\n\"^EQ\";\ntop.open(\"rm_scrum_task.do?sys_id=-1&sysparm_query=\"+q);\n```"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Create story task/create_story_task.js",
    "content": "javascript:var w=window.frames[\"gsft_main\"]!==undefined?window.frames[\"gsft_main\"]:window;var q=\"parent=\"+w.g_form.getUniqueValue()+\"^type=4\"+\"^short_description=Test \"+w.g_form.getValue(\"number\")+\" - \"+w.g_form.getValue(\"short_description\")+\"^EQ\";top.open(\"rm_scrum_task.do?sys_id=-1&sysparm_query=\"+q);\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Highlight Mandatory fields on form/Highlight mandatory fields on form.js",
    "content": "javascript:(function(){try{let f=window;if(window.frames.length&&window.frames['gsft_main'])f=window.frames['gsft_main'];if(!f||typeof f.g_form==='undefined'){console.log('No ServiceNow form detected.');return;}const g=f.g_form,d=f.document,sid='sn-mandatory-highlight-style',es=d.getElementById(sid);if(es){es.remove();d.querySelectorAll('.mandatory-glow').forEach(e=>e.classList.remove('mandatory-glow'));return;}const st=d.createElement('style');st.id=sid;st.innerHTML='.mandatory-glow{outline:3px solid #FFD700!important;box-shadow:0 0 10px 3px rgba(255,215,0,0.9)!important;border-radius:6px;transition:all 0.2s ease-in-out;}';d.head.appendChild(st);g.getEditableFields().forEach(n=>{if(g.isMandatory(n)){const e=g.getElement(n);if(!e)return;let c=e.closest('.form-group,.question,.input-group,.field');if(!c)c=e.parentElement;c.classList.add('mandatory-glow');}});}catch(e){console.error('Error:',e);}})();\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Highlight Mandatory fields on form/README.md",
    "content": "## Highlight Mandatory Fields\n\n**Description**\n- This bookmarklet visually highlights all mandatory fields on a ServiceNow form by adding a glowing border or background around them.\n- It helps developers, admins, or QA testers quickly see which fields are marked as mandatory.\n- It also helps partial visually paired people to find the mandatory fields instead of looking for small * icon for field.\n- This works as a toggle. One click highlights the mandatory fields and clicking again removes the highlight.\n\n**Example :**\n\n- When activated on a form (e.g. Incident, Request Item):\n- Mandatory fields like Short description, Caller, etc get a soft glowing yellow border.\n- Click again → glow is removed.\n\n**How it works:**  \n- Detects `g_form` context.\n- Adds a temporary CSS class (`.mandatory-glow`) to all mandatory fields.\n- Click again to remove the highlights.\n\n**Sample screenshot**\n<img width=\"1882\" height=\"674\" alt=\"image\" src=\"https://github.com/user-attachments/assets/1320c9c3-976d-4bf0-92d5-e051825dbe6c\" />\n\n\n\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Impersonation/README.md",
    "content": "# User Impersonation\nWhen a user impersonates another user, the page is redirected to the home page for the new user. This bookmarklet will open the impersonation page in a popup window so the user impersonation can be completed without redirecting the paging being viewed. After selecting the new user, the popup will close and the instance page will refresh with the new user context.\n\nThis was updated from a new tab to the popup based on the \"Quick Login to current instance\" bookmarklet by OrgovanGeza.\n\n```js\njavascript:let impWin=window.open(\"/impersonate_dialog.do\", \"Impersonation\", \"scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=300,height=300,left=100,top=100\");setInterval(()=>{if(!impWin.location.pathname.includes(\"impersonate\")){impWin.close();window.location.reload();};},500);\n```\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Impersonation/impersonation.js",
    "content": "javascript:let impWin=window.open(\"/impersonate_dialog.do\", \"Impersonation\", \"scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=300,height=300,left=100,top=100\");setInterval(()=>{if(!impWin.location.pathname.includes(\"impersonate\")){impWin.close();window.location.reload();};},500);\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Load List with Query/listquery.html",
    "content": "<A HREF=\"javascript:top.open('now/nav/ui/classic/params/target/sc_request_list.do%3Fsysparm_query%3Du_addressLIKEmain%2520st');\">RITM Addresses</A>\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Load List with Query/readme.md",
    "content": "Sometimes you can't include the condition you want in a graphical list filter, but a (GlideRecord) encoded query is valid.\n \nAn Example:\nOn my sc_request table I have created a field with the type Name-Value Pairs to store address information that will accommodate varying global formats (dynamic content and number of fields).  This field type allows you to add as many pairs as needed to each record.\nThe value of this field is stored in JSON, containing the Name and Value of each pair added:\n{\"Street\":\"123 Main St\",\"City\":\"MyCity\",\"State\":\"MyState\",\"Zip\":\"90210\"}\n \nBusiness Case:\nIf I need to find all of the Request records that contain a certain City name, Zip Code, Street name or address etc., I cannot do so in a list view of the Request table, as the only conditions available are 'is', 'is not', or 'is anything':\nThere are probably other useful conditions missing for various field types.\n\nIn a script, I can get the records I'm looking for via GlideRecord using an encoded query.  As a simple example, let's say I want to retrieve all of the records that contain 'Main St':\n\n'u_addressLIKEmain st'\n \nThe Workaround:\nBy adding a sysparm_query to the URL, the list will be filtered appropriately.  The above encoded query resolves like this when added to the end of a URL:\nhttps://instancename.service-now.com/now/nav/ui/classic/params/target/sc_request_list.do%3Fsysparm_query%3Du_addressLIKEmain%2520st\n\nWhere sc_rquest is the table name,\nu_address is the field name to query on,\nand the rest is the encoded query\n\nWhen doing this, you will see the correct filter reflected in the breadcrumb, but here is the best part - now when you expand the filter via the funnel icon, 'contains' is a valid operator, so you can change the text/value to whatever else you are looking for!\nWhile this is a simple example, passing in a complex encoded query with ANDs and ORs will also work to filter the records and update the filter where building the filter manually is limited.\n\nThis bookmarklet is formatted to open a new tab when logged into an instance. \n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open copied record/README.md",
    "content": "## Open copied record\n\nSomeone sent you the Number of a record that belong to the task table (e.g. Incident, SCTASK, RITM, Story, Problem, Change etc.), but not a link to the record itself?\n\nYou can save yourself a couple of clicks if you use this Bookmarklet while you are on the instance that the record belongs to.\n\n**Usage**:\n- copy the record number,\n- make sure that in your browser you are on the same instance that the record belongs to,\n- click the Bookmarklet, and the record's form will open in a new tab (with a visible navigation pane).\n\nI use this Bookmarklet on a daily basis, hope you'll like it as well.\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open copied record/open copied record bookmarklet.js",
    "content": "javascript: (c=> c.readText().then( text => window.open(\"http://\" + window.location.hostname + \"/nav_to.do?uri=task.do?sysparm_query=number=\" + text, \"_blank\")))(navigator.clipboard);\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open g_form modal/Open modal to use g_form.js",
    "content": "javascript:!function(){if(document.querySelector(\"#gform-modal-overlay\"))return;const e=window.gsft_main?.g_form||this.g_form||\"undefined\"!=typeof querySelectorShadowDom&&querySelectorShadowDom.querySelectorAllDeep(\"sn-form-data-connected\")[0]?.nowRecordFormBlob?.gForm||angular.element(\"sp-variable-layout\").scope().getGlideForm?.()||null;if(!e)return;const t=e.elements?e.elements:(e=>e.getFieldNames().map((e=>({fieldName:e}))))(e);((e,t)=>{const n=document.createElement(\"div\");n.id=\"gform-modal-overlay\",n.style.position=\"fixed\",n.style.top=0,n.style.left=0,n.style.width=\"100vw\",n.style.height=\"100vh\",n.style.backgroundColor=\"rgba(0, 0, 0, 0.6)\",n.style.zIndex=9999,n.style.display=\"flex\",n.style.justifyContent=\"center\",n.style.alignItems=\"center\";const d=document.createElement(\"div\");d.id=\"modal-container\",d.style.background=\"#fff\",d.style.padding=\"20px\",d.style.borderRadius=\"8px\",d.style.boxShadow=\"0 0 10px rgba(0,0,0,0.3)\",d.style.maxWidth=\"1000px\",d.style.textAlign=\"center\",d.style.maxHeight=\"70vh\";const o=document.createElement(\"div\");o.style.overflowY=\"auto\",o.style.overflowX=\"hidden\",o.style.maxHeight=\"60vh\",d.appendChild(o);let l=\"\";t.forEach((t=>{const n=t.fieldName,d=e.isReadOnly(n),o=e.isMandatory(n),a=e.isVisible(n);l+=`\\n      <tr>\\n        <td>${n}</td>\\n        <td><input type=\"checkbox\" ${o?\"disabled\":\"\"} id=\"disabled-${n}\" ${d?\"\":\"checked\"}></td>\\n        <td><input type=\"checkbox\" id=\"mandatory-${n}\" ${o?\"checked\":\"\"}></td>\\n        <td><input type=\"checkbox\" ${o?\"disabled\":\"\"} id=\"visible-${n}\" ${a?\"checked\":\"\"}></td>\\n        <td><input type=\"text\" id=\"value-${n}\" value=\"${e.getValue(n)}\"></td>\\n        <td><button id=\"change-value-${n}\">Set</button></td>   \\n      </tr>\\n    `})),o.innerHTML=`\\n    <h2>g_form modal</h2>\\n    <table>\\n      <thead>\\n        <tr>\\n          <th>Field</th>\\n          <th>ReadOnly</th>\\n          <th>Mandatory</th>\\n          <th>Display</th>\\n          <th>Value</th>\\n          <th></th>\\n        </tr>\\n      </thead>\\n      <tbody>${l}</tbody>\\n    </table>\\n  `;const a=[];n.addEventListener(\"click\",(e=>{d.contains(e.target)||(a.forEach((e=>e())),document.body.removeChild(n))})),((e,t)=>{let n=!1,d=0,o=0;const l=document.createElement(\"div\");l.style.cursor=\"move\",l.style.height=\"20px\",l.style.marginBottom=\"10px\",l.style.background=\"rgba(100, 100, 100, 0.3)\",l.style.borderRadius=\"8px\",e.prepend(l);const a=t=>{n&&(e.style.left=t.clientX-d+\"px\",e.style.top=t.clientY-o+\"px\",e.style.position=\"absolute\")},s=()=>{n=!1,document.body.style.userSelect=\"\"};l.addEventListener(\"mousedown\",(t=>{n=!0,d=t.clientX-e.offsetLeft,o=t.clientY-e.offsetTop,document.body.style.userSelect=\"none\"})),document.addEventListener(\"mousemove\",a),document.addEventListener(\"mouseup\",s),t.push((()=>{document.removeEventListener(\"mousemove\",a),document.removeEventListener(\"mouseup\",s)}))})(d,a),n.appendChild(d),document.body.appendChild(n),t.forEach((t=>{const n=t.fieldName,d=o.querySelector(`#disabled-${n}`),l=o.querySelector(`#mandatory-${n}`),a=o.querySelector(`#visible-${n}`),s=o.querySelector(`#value-${n}`),c=o.querySelector(`#change-value-${n}`);d&&d.addEventListener(\"change\",(()=>{e?.setDisabled(n,d.checked)})),l&&l.addEventListener(\"change\",(()=>{const t=l.checked,o=!e?.getValue(n),s=a.checked;e?.setMandatory(n,t),t&&(!o&&s?a.checked=!0:o&&!s?(a.checked=!0,d.checked=!1):o&&s&&(d.checked=!1)),d&&(d.disabled=t),a&&(a.disabled=t)})),a&&a.addEventListener(\"change\",(()=>{e?.setDisplay(n,a.checked)})),c&&s&&c.addEventListener(\"click\",(()=>{const t=s.value;e.setValue(n,t)}))}))})(e,t)}();\n\n/*\n * full js of minified bookmarklet below inside comment\n */\n\n/*\n(function () {\n    const makeDraggable = (element, cleanupHandlers) => {\n        let isDragging = false;\n        let offsetX = 0;\n        let offsetY = 0;\n\n        const header = document.createElement('div');\n        header.style.cursor = 'move';\n        header.style.height = '20px';\n        header.style.marginBottom = '10px';\n        header.style.background = \"rgba(100, 100, 100, 0.3)\";\n        header.style.borderRadius = \"8px\";\n        element.prepend(header);\n\n        const mouseMoveHandler = (e) => {\n            if (isDragging) {\n                element.style.left = `${e.clientX - offsetX}px`;\n                element.style.top = `${e.clientY - offsetY}px`;\n                element.style.position = 'absolute';\n            }\n        };\n\n        const mouseUpHandler = () => {\n            isDragging = false;\n            document.body.style.userSelect = '';\n        };\n\n        header.addEventListener('mousedown', (e) => {\n            isDragging = true;\n            offsetX = e.clientX - element.offsetLeft;\n            offsetY = e.clientY - element.offsetTop;\n            document.body.style.userSelect = 'none';\n        });\n\n        document.addEventListener('mousemove', mouseMoveHandler);\n        document.addEventListener('mouseup', mouseUpHandler);\n\n        cleanupHandlers.push(() => {\n            document.removeEventListener('mousemove', mouseMoveHandler);\n            document.removeEventListener('mouseup', mouseUpHandler);\n        });\n    };\n\n    const createOverlay = (g_form, fieldArray) => {\n        const overlay = document.createElement('div');\n        overlay.id = \"gform-modal-overlay\";\n        overlay.style.position = 'fixed';\n        overlay.style.top = 0;\n        overlay.style.left = 0;\n        overlay.style.width = '100vw';\n        overlay.style.height = '100vh';\n        overlay.style.backgroundColor = 'rgba(0, 0, 0, 0.6)';\n        overlay.style.zIndex = 9999;\n        overlay.style.display = 'flex';\n        overlay.style.justifyContent = 'center';\n        overlay.style.alignItems = 'center';\n\n\n        const container = document.createElement('div');\n        container.id = \"modal-container\";\n        container.style.background = '#fff';\n        container.style.padding = '20px';\n        container.style.borderRadius = '8px';\n        container.style.boxShadow = '0 0 10px rgba(0,0,0,0.3)';\n        container.style.maxWidth = '1000px';\n        container.style.textAlign = 'center';\n        container.style.maxHeight = '70vh';\n\n        const modal = document.createElement('div');\n        modal.style.overflowY = 'auto';\n        modal.style.overflowX = 'hidden';\n        modal.style.maxHeight = '60vh';\n        container.appendChild(modal)\n\n        let listItems = '';\n        fieldArray.forEach(element => {\n            const fieldName = element.fieldName;\n            const isReadOnly = g_form.isReadOnly(fieldName);\n            const isMandatory = g_form.isMandatory(fieldName);\n            const isVisible = g_form.isVisible(fieldName);\n\n            listItems += `\n      <tr>\n        <td>${fieldName}</td>\n        <td><input type=\"checkbox\" ${isMandatory ? 'disabled' : ''} id=\"disabled-${fieldName}\" ${!isReadOnly ? 'checked' : ''}></td>\n        <td><input type=\"checkbox\" id=\"mandatory-${fieldName}\" ${isMandatory ? 'checked' : ''}></td>\n        <td><input type=\"checkbox\" ${isMandatory ? 'disabled' : ''} id=\"visible-${fieldName}\" ${isVisible ? 'checked' : ''}></td>\n        <td><input type=\"text\" id=\"value-${fieldName}\" value=\"${g_form.getValue(fieldName)}\"></td>\n        <td><button id=\"change-value-${fieldName}\">Set</button></td>   \n      </tr>\n    `;\n        });\n\n        modal.innerHTML = `\n    <h2>g_form modal</h2>\n    <table>\n      <thead>\n        <tr>\n          <th>Field</th>\n          <th>ReadOnly</th>\n          <th>Mandatory</th>\n          <th>Display</th>\n          <th>Value</th>\n          <th></th>\n        </tr>\n      </thead>\n      <tbody>${listItems}</tbody>\n    </table>\n  `;\n\n        const cleanupHandlers = [];\n\n        overlay.addEventListener('click', (event) => {\n            if (!container.contains(event.target)) {\n                cleanupHandlers.forEach(fn => fn());\n                document.body.removeChild(overlay);\n            }\n        });\n\n        makeDraggable(container, cleanupHandlers);\n        overlay.appendChild(container);\n        document.body.appendChild(overlay);\n\n        fieldArray.forEach(element => {\n            const fieldName = element.fieldName;\n            const disabledCheckbox = modal.querySelector(`#disabled-${fieldName}`);\n            const mandatoryCheckbox = modal.querySelector(`#mandatory-${fieldName}`);\n            const visibleCheckbox = modal.querySelector(`#visible-${fieldName}`);\n            const valueInput = modal.querySelector(`#value-${fieldName}`);\n            const changeButton = modal.querySelector(`#change-value-${fieldName}`);\n\n            if (disabledCheckbox) {\n                disabledCheckbox.addEventListener('change', () => {\n                    g_form?.setDisabled(fieldName, disabledCheckbox.checked);\n                });\n            }\n\n            if (mandatoryCheckbox) {\n                mandatoryCheckbox.addEventListener('change', () => {\n                    const setToMandatory = mandatoryCheckbox.checked;\n                    const isEmpty = !g_form?.getValue(fieldName);\n                    const isDisplayed = visibleCheckbox.checked;\n                    g_form?.setMandatory(fieldName, setToMandatory);\n\n                    if (setToMandatory) {\n                        if (!isEmpty && isDisplayed) {\n                            visibleCheckbox.checked = true;\n                        } else if (isEmpty && !isDisplayed) {\n                            visibleCheckbox.checked = true;\n                            disabledCheckbox.checked = false;\n                        } else if (isEmpty && isDisplayed) {\n                            disabledCheckbox.checked = false;\n                        }\n                    }\n                    if (disabledCheckbox) disabledCheckbox.disabled = setToMandatory;\n                    if (visibleCheckbox) visibleCheckbox.disabled = setToMandatory;\n                });\n            }\n\n            if (visibleCheckbox) {\n                visibleCheckbox.addEventListener('change', () => {\n                    g_form?.setDisplay(fieldName, visibleCheckbox.checked);\n                });\n            }\n\n            if (changeButton && valueInput) {\n                changeButton.addEventListener('click', () => {\n                    const newValue = valueInput.value;\n                    g_form.setValue(fieldName, newValue);\n                });\n            }\n        });\n    };\n\n    if (document.querySelector(\"#gform-modal-overlay\")) return;\n\n    const getFieldNames = (g_form) => {\n        return g_form.getFieldNames().map(name => ({ fieldName: name }));\n    }\n\n    const g_form =\n        window.gsft_main?.g_form ||\n        this.g_form ||\n        (typeof querySelectorShadowDom !== \"undefined\" &&\n            querySelectorShadowDom.querySelectorAllDeep('sn-form-data-connected')[0]?.nowRecordFormBlob?.gForm) ||\n        (angular.element(\"sp-variable-layout\").scope().getGlideForm?.()) ||\n        null;\n    if (!g_form) {\n        return;\n    }\n    const fieldArray = g_form.elements ? g_form.elements : getFieldNames(g_form);\n    createOverlay(g_form, fieldArray);\n})();\n*/\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open g_form modal/README.md",
    "content": "# g_form Bookmarklet Modal\n\nThis script provides a bookmarklet that injects a draggable modal into ServiceNow forms that allows the user to interact with fields on forms. Tested on next exp classic forms with or without top navigation, workspace(needs snutils installed) and portal.\n---\n\n## 📸 Screenshots\n\n### Modal Overview\n![Modal Overview](image.png)\n\n---\n\n## 🔧 How to Use\n\n1. **Copy the minified script** on the first line of the *Open modal to use g_form.js* file\n2. **Create a bookmark** in your browser.\n3. Paste the script into the bookmark's URL field.\n4. Navigate to a ServiceNow form and click the bookmarklet.\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open record in another instance/Open record in another instance.js",
    "content": "javascript:(function(){try{const u=location.href,m=u.match(/https:\\/\\/([a-zA-Z0-9-]+)\\.service-now\\.com/);if(!m){alert(\"Not a valid ServiceNow URL.\");return;}const c=m[1],i=[\"dev12345\",\"test12345\",\"uat12345\",\"prod12345\"];const d=document.createElement(\"div\");Object.assign(d.style,{position:\"fixed\",top:\"20px\",right:\"20px\",zIndex:999999,background:\"#fff\",padding:\"10px\",border:\"2px solid #333\",borderRadius:\"8px\",boxShadow:\"0 2px 10px rgba(0,0,0,0.3)\"});d.innerHTML=%60<b>Current:</b> ${c}<br><small>Select target instance:</small><br>%60+i.map(x=>%60<button style=\"margin:3px;padding:5px 10px;border-radius:4px;cursor:pointer;\">${x}</button>%60).join(\"\")+%60<br><a href=\"#\" style=\"font-size:12px;color:#888;\">Close</a>%60;document.body.appendChild(d);d.querySelectorAll(\"button\").forEach(b=>b.onclick=()=>{const t=b.textContent.trim();window.open(u.replace(c+\".service-now.com\",t+\".service-now.com\"),\"_blank\");d.remove();});d.querySelector(\"a\").onclick=e=>{e.preventDefault();d.remove();};}catch(e){alert(\"Error: \"+e.message);}})();\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open record in another instance/README.md",
    "content": "# Open Same Record in Another Instance (Browser Bookmarklet)\n\n## Description\nThis bookmarklet allows ServiceNow developers, admins, or QA testers to **quickly open the same record in another instance** (e.g., DEV → TEST → UAT → PROD) without manually searching for the record or typing the URL.  \n\nIt is especially useful for:\n- Comparing records across environments\n- Testing configuration changes\n- Debugging workflows or UI policies across instances\n\nInstead of typing the target instance every time, it provides **clickable buttons** for your predefined instances.\n\n---\n\n## Features\n- Detects current instance automatically\n- Opens the same record in a **new tab**\n- Pop-up overlay with buttons for predefined target instances\n- Toggleable: overlay disappears after selecting or closing\n- Fully client-side — works on any ServiceNow record page\n\n---\n\n## How to use\n\n1. Create a new bookmark in your browser.  \n2. Name it: `Open in Another Instance`  \n3. In the URL field, paste the bookmarklet code present in [Open Record in Another Instance](./Open%20record%20in%20another%20instance/Open%20record%20in%20another%20instance.js)\n4. **Important** In the script, update the instance names with your instances.\n   For example purpose [\"dev12345\",\"test12345\",\"uat12345\",\"prod12345\"] are added in the code.\n5. Now open any record in any ServiceNow instance and click on the bookmark and select the instance to open.\n\nNote : Please be noted that this will work for all the ServiceNow instances with URL like https://xxxx.service-now.com\nFor custom URLs, you need to tweak the code slightly as required.\n\nSCreenshots:\n<img width=\"1900\" height=\"534\" alt=\"image\" src=\"https://github.com/user-attachments/assets/03d0ae55-c2fb-48bc-bffc-a6061be79b16\" />\n\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open tinymce for journal/Open tinymce editor in modal for journal fields.js",
    "content": "!function(){const e={attachmentCanPopup:\"true\",attachmentCanView:\"true\",canReadDbImage:!0,canReadDbVideo:!0,allowedExtensions:\"\",videoExtensions:\"webm|mov|swf|avi|mp4\",convert_urls:!1,custom_elements:\"sn-toc,~sn-mention\",link_default_target:\"\",enable_media_sites:\"youtube.com,player.vimeo.com,vimeo.com,players.brightcove.net,brightcove.net\",extended_valid_elements:\"now-illustration-custom[token-id|src|alt|width|height],sn-toc,sn-mention[class|table|sysid]\",font_family_formats:\"Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats;\",language:\"en\",plugins:\"powerpaste table link image media codemirror lists advlist fullscreen charmap directionality emoticons insertdatetime nonbreaking pagebreak searchreplace wordcount anchor tableofcontents codesample visualblocks visualchars autolink align_listitems accordion editimage a11ychecker readonlynoborder\",powerpaste_html_import:\"clean\",powerpaste_word_import:\"prompt\",powerpaste_clean_filtered_inline_elements:\"strong, b\",relative_urls:!0,remove_script_host:!0,contextmenu:\"link image table\",toolbar:[\"fontfamily fontsize | bold italic underline strikethrough forecolor backcolor pastetext removeformat | blocks searchreplace undo redo | bullist numlist outdent indent alignleft aligncenter alignright | tableofcontents table link unlink a11ycheck anchor accordion insertdatetime | image media codesample | code fullscreen\"],menubar:!1,menu:{file:{title:\"File\",items:\"newdocument restoredraft | preview | export print | deleteallconversations\"},edit:{title:\"Edit\",items:\"undo redo | cut copy paste pastetext | selectall | searchreplace\"},view:{title:\"View\",items:\"code | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments\"},insert:{title:\"Insert\",items:\"image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents | insertdatetime\"},format:{title:\"Format\",items:\"bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat\"},tools:{title:\"Tools\",items:\"spellchecker spellcheckerlanguage | a11ycheck code wordcount\"},table:{title:\"Table\",items:\"inserttable | cell row column | advtablesort | tableprops deletetable\"},help:{title:\"Help\",items:\"help\"}},style_formats:[{title:\"Headings\",items:[{title:\"Heading 1\",format:\"h1\"},{title:\"Heading 2\",format:\"h2\"},{title:\"Heading 3\",format:\"h3\"},{title:\"Heading 4\",format:\"h4\"},{title:\"Heading 5\",format:\"h5\"},{title:\"Heading 6\",format:\"h6\"}]},{title:\"Inline\",items:[{title:\"Bold\",format:\"bold\"},{title:\"Italic\",format:\"italic\"},{title:\"Underline\",format:\"underline\"},{title:\"Strikethrough\",format:\"strikethrough\"},{title:\"Superscript\",format:\"superscript\"},{title:\"Subscript\",format:\"subscript\"},{title:\"Code\",format:\"code\"}]},{title:\"Blocks\",items:[{title:\"Paragraph\",format:\"p\"},{title:\"Blockquote\",format:\"blockquote\"},{title:\"Div\",format:\"div\"},{title:\"Pre\",format:\"pre\"}]},{title:\"Align\",items:[{title:\"Left\",format:\"alignleft\"},{title:\"Center\",format:\"aligncenter\"},{title:\"Right\",format:\"alignright\"},{title:\"Justify\",format:\"alignjustify\"}]}],text_patterns:!1,promotion:!1,help_tabs:[\"shortcuts\",\"keyboardnav\",\"versions\"],height:300,automatic_uploads:!1,allow_script_urls:!1},t=top?.window?.gsft_main||this,i=t.document;if(i.getElementById(\"drc-overlay\"))return;(a=>{const o=()=>{setTimeout((()=>{t.setupTinymceField(\"modal-editor\",e)}))},l=i.createElement(\"div\");l.style.overflowY=\"auto\",l.style.overflowX=\"hidden\",l.style.maxHeight=\"60vh\";const n=i.createElement(\"textarea\");n.style.visibility=\"hidden\",n.id=\"modal-editor\",l.appendChild(n);const s=t.g_form;if(void 0!==s){const e=s.elements.filter((e=>\"journal_input\"===e.type)),a=i.createElement(\"div\");e.forEach((e=>{const o=i.createElement(\"button\");o.style.margin=\"0.8rem\",o.textContent=e.fieldName,a.appendChild(o),o.onclick=()=>{const i=t.tinymce.get(\"modal-editor\").getContent();s.setValue(e.fieldName,`[code]${i}[/code]`)}})),l.appendChild(a)}if(a.appendChild(l),void 0===t.tinymce){const e=[\"/scripts/tinymce_default/js_includes_tinymce.jsx?sysparm_substitute=false\"];t.ScriptLoader.getScripts(e,o)}else o()})((()=>{const e=i.createElement(\"div\");e.id=\"drc-overlay\",e.style.position=\"fixed\",e.style.top=0,e.style.left=0,e.style.width=\"100vw\",e.style.height=\"100vh\",e.style.backgroundColor=\"rgba(0, 0, 0, 0.6)\",e.style.zIndex=3,e.style.display=\"flex\",e.style.justifyContent=\"center\",e.style.alignItems=\"center\";const a=i.createElement(\"div\");a.id=\"modal-container\",a.style.background=\"#fff\",a.style.padding=\"20px\",a.style.borderRadius=\"8px\",a.style.boxShadow=\"0 0 10px rgba(0,0,0,0.3)\",a.style.maxWidth=\"1000px\",a.style.textAlign=\"center\",a.style.maxHeight=\"70vh\",a.style.position=\"absolute\";const o=i.createElement(\"div\");o.style.cursor=\"move\",o.style.height=\"20px\",o.style.marginBottom=\"10px\",o.style.background=\"rgba(100, 100, 100, 0.3)\",o.style.borderRadius=\"8px\";const l=((e,t)=>{let a=!1,o=0,l=0;const n=e=>{a&&(t.style.left=e.clientX-o+\"px\",t.style.top=e.clientY-l+\"px\")},s=()=>{a=!1,i.body.style.userSelect=\"\"};e.addEventListener(\"mousedown\",(e=>{a=!0,o=e.clientX-t.offsetLeft,l=e.clientY-t.offsetTop,i.body.style.userSelect=\"none\"})),i.addEventListener(\"mousemove\",n),i.addEventListener(\"mouseup\",s);const r=[];return r.push((()=>{i.removeEventListener(\"mousemove\",n),i.removeEventListener(\"mouseup\",s)})),r})(o,a);a.append(o),e.appendChild(a),i.body.appendChild(e);return e.addEventListener(\"click\",(o=>{if(!a.contains(o.target)){const a=t.tinymce?.get(\"modal-editor\");a&&a.remove(),i.body.removeChild(e),l.forEach((e=>e()))}})),a})())}();\n\n/*\n(function () {\n    const tinyMceConfig = {\n        \"attachmentCanPopup\": \"true\",\n        \"attachmentCanView\": \"true\",\n        \"canReadDbImage\": true,\n        \"canReadDbVideo\": true,\n        \"allowedExtensions\": \"\",\n        \"videoExtensions\": \"webm|mov|swf|avi|mp4\",\n        \"convert_urls\": false,\n        \"custom_elements\": \"sn-toc,~sn-mention\",\n        \"link_default_target\": \"\",\n        \"enable_media_sites\": \"youtube.com,player.vimeo.com,vimeo.com,players.brightcove.net,brightcove.net\",\n        \"extended_valid_elements\": \"now-illustration-custom[token-id|src|alt|width|height],sn-toc,sn-mention[class|table|sysid]\",\n        \"font_family_formats\": \"Andale Mono=andale mono,times;Arial=arial,helvetica,sans-serif;Arial Black=arial black,avant garde;Book Antiqua=book antiqua,palatino;Comic Sans MS=comic sans ms,sans-serif;Courier New=courier new,courier;Georgia=georgia,palatino;Helvetica=helvetica;Impact=impact,chicago;Symbol=symbol;Tahoma=tahoma,arial,helvetica,sans-serif;Terminal=terminal,monaco;Times New Roman=times new roman,times;Trebuchet MS=trebuchet ms,geneva;Verdana=verdana,geneva;Webdings=webdings;Wingdings=wingdings,zapf dingbats;\",\n        \"language\": \"en\",\n        \"plugins\": \"powerpaste table link image media codemirror lists advlist fullscreen charmap directionality emoticons insertdatetime nonbreaking pagebreak searchreplace wordcount anchor tableofcontents codesample visualblocks visualchars autolink align_listitems accordion editimage a11ychecker readonlynoborder\",\n        \"powerpaste_html_import\": \"clean\",\n        \"powerpaste_word_import\": \"prompt\",\n        \"powerpaste_clean_filtered_inline_elements\": \"strong, b\",\n        \"relative_urls\": true,\n        \"remove_script_host\": true,\n        \"contextmenu\": \"link image table\",\n        \"toolbar\": [\n            \"fontfamily fontsize | bold italic underline strikethrough forecolor backcolor pastetext removeformat | blocks searchreplace undo redo | bullist numlist outdent indent alignleft aligncenter alignright | tableofcontents table link unlink a11ycheck anchor accordion insertdatetime | image media codesample | code fullscreen\"\n        ],\n        \"menubar\": false,\n        \"menu\": {\n            \"file\": {\n                \"title\": \"File\",\n                \"items\": \"newdocument restoredraft | preview | export print | deleteallconversations\"\n            },\n            \"edit\": {\n                \"title\": \"Edit\",\n                \"items\": \"undo redo | cut copy paste pastetext | selectall | searchreplace\"\n            },\n            \"view\": {\n                \"title\": \"View\",\n                \"items\": \"code | visualaid visualchars visualblocks | spellchecker | preview fullscreen | showcomments\"\n            },\n            \"insert\": {\n                \"title\": \"Insert\",\n                \"items\": \"image link media addcomment pageembed template codesample inserttable | charmap emoticons hr | pagebreak nonbreaking anchor tableofcontents | insertdatetime\"\n            },\n            \"format\": {\n                \"title\": \"Format\",\n                \"items\": \"bold italic underline strikethrough superscript subscript codeformat | styles blocks fontfamily fontsize align lineheight | forecolor backcolor | language | removeformat\"\n            },\n            \"tools\": {\n                \"title\": \"Tools\",\n                \"items\": \"spellchecker spellcheckerlanguage | a11ycheck code wordcount\"\n            },\n            \"table\": {\n                \"title\": \"Table\",\n                \"items\": \"inserttable | cell row column | advtablesort | tableprops deletetable\"\n            },\n            \"help\": {\n                \"title\": \"Help\",\n                \"items\": \"help\"\n            }\n        },\n        \"style_formats\": [\n            {\n                \"title\": \"Headings\",\n                \"items\": [\n                    { \"title\": \"Heading 1\", \"format\": \"h1\" },\n                    { \"title\": \"Heading 2\", \"format\": \"h2\" },\n                    { \"title\": \"Heading 3\", \"format\": \"h3\" },\n                    { \"title\": \"Heading 4\", \"format\": \"h4\" },\n                    { \"title\": \"Heading 5\", \"format\": \"h5\" },\n                    { \"title\": \"Heading 6\", \"format\": \"h6\" }\n                ]\n            },\n            {\n                \"title\": \"Inline\",\n                \"items\": [\n                    { \"title\": \"Bold\", \"format\": \"bold\" },\n                    { \"title\": \"Italic\", \"format\": \"italic\" },\n                    { \"title\": \"Underline\", \"format\": \"underline\" },\n                    { \"title\": \"Strikethrough\", \"format\": \"strikethrough\" },\n                    { \"title\": \"Superscript\", \"format\": \"superscript\" },\n                    { \"title\": \"Subscript\", \"format\": \"subscript\" },\n                    { \"title\": \"Code\", \"format\": \"code\" }\n                ]\n            },\n            {\n                \"title\": \"Blocks\",\n                \"items\": [\n                    { \"title\": \"Paragraph\", \"format\": \"p\" },\n                    { \"title\": \"Blockquote\", \"format\": \"blockquote\" },\n                    { \"title\": \"Div\", \"format\": \"div\" },\n                    { \"title\": \"Pre\", \"format\": \"pre\" }\n                ]\n            },\n            {\n                \"title\": \"Align\",\n                \"items\": [\n                    { \"title\": \"Left\", \"format\": \"alignleft\" },\n                    { \"title\": \"Center\", \"format\": \"aligncenter\" },\n                    { \"title\": \"Right\", \"format\": \"alignright\" },\n                    { \"title\": \"Justify\", \"format\": \"alignjustify\" }\n                ]\n            }\n        ],\n        \"text_patterns\": false,\n        \"promotion\": false,\n        \"help_tabs\": [\"shortcuts\", \"keyboardnav\", \"versions\"],\n        \"height\": 300,\n        \"automatic_uploads\": false,\n        \"allow_script_urls\": false\n    };\n\n    const makeModalMovable = (headerElement, modalElement) => {\n        let isDragging = false,\n            offsetX = 0,\n            offsetY = 0;\n\n        const mouseMoveHandler = (event) => {\n            if (isDragging) {\n                modalElement.style.left = `${event.clientX - offsetX}px`\n                modalElement.style.top = `${event.clientY - offsetY}px`\n            }\n        }\n        const mouseUpHandler = () => {\n            isDragging = false;\n            document.body.style.userSelect = \"\";\n        };\n        const mouseDownHandler = (e) => {\n            isDragging = true;\n            offsetX = e.clientX - modalElement.offsetLeft;\n            offsetY = e.clientY - modalElement.offsetTop;\n            document.body.style.userSelect = \"none\";\n        };\n\n        headerElement.addEventListener(\"mousedown\", mouseDownHandler);\n        document.addEventListener(\"mousemove\", mouseMoveHandler);\n        document.addEventListener(\"mouseup\", mouseUpHandler);\n\n        const cleanupHandlers = [];\n        cleanupHandlers.push(() => {\n            document.removeEventListener('mousemove', mouseMoveHandler);\n            document.removeEventListener('mouseup', mouseUpHandler);\n        });\n\n        return cleanupHandlers;\n    }\n\n    const createOverlay = () => {\n        const overlay = document.createElement(\"div\")\n        overlay.id = \"drc-overlay\";\n        overlay.style.position = \"fixed\";\n        overlay.style.top = 0;\n        overlay.style.left = 0;\n        overlay.style.width = \"100vw\";\n        overlay.style.height = \"100vh\";\n        overlay.style.backgroundColor = \"rgba(0, 0, 0, 0.6)\";\n        overlay.style.zIndex = 3;\n        overlay.style.display = \"flex\";\n        overlay.style.justifyContent = \"center\";\n        overlay.style.alignItems = \"center\";\n\n        const modalContainer = document.createElement(\"div\");\n        modalContainer.id = \"modal-container\";\n        modalContainer.style.background = \"#fff\";\n        modalContainer.style.padding = \"20px\";\n        modalContainer.style.borderRadius = \"8px\";\n        modalContainer.style.boxShadow = \"0 0 10px rgba(0,0,0,0.3)\";\n        modalContainer.style.maxWidth = \"1000px\";\n        modalContainer.style.textAlign = \"center\";\n        modalContainer.style.maxHeight = \"70vh\";\n        modalContainer.style.position = 'absolute';\n\n        const modalHeader = document.createElement(\"div\");\n        modalHeader.style.cursor = \"move\";\n        modalHeader.style.height = \"20px\";\n        modalHeader.style.marginBottom = \"10px\";\n        modalHeader.style.background = \"rgba(100, 100, 100, 0.3)\";\n        modalHeader.style.borderRadius = \"8px\";\n\n        const cleanupHandlers = makeModalMovable(modalHeader, modalContainer);\n        modalContainer.append(modalHeader)\n        overlay.appendChild(modalContainer);\n        document.body.appendChild(overlay);\n\n        const onOverlayClick = (event) => {\n            if (!modalContainer.contains(event.target)) {\n                const editor = frame.tinymce?.get(\"modal-editor\");\n                if (editor) editor.remove();\n                document.body.removeChild(overlay);\n                cleanupHandlers.forEach(fn => fn());\n            }\n        }\n        overlay.addEventListener(\"click\", onOverlayClick);\n\n        return modalContainer;\n    }\n\n    const addModalContent = (modalContainer) => {\n        const createEditor = () => {\n            setTimeout(() => {\n                frame.setupTinymceField(\"modal-editor\", tinyMceConfig);\n            })\n\n        }\n        const modalContent = document.createElement(\"div\");\n        modalContent.style.overflowY = \"auto\";\n        modalContent.style.overflowX = \"hidden\";\n        modalContent.style.maxHeight = \"60vh\";\n        const textArea = document.createElement(\"textarea\");\n        textArea.style.visibility = \"hidden\";\n        textArea.id = \"modal-editor\";\n        modalContent.appendChild(textArea);\n\n        const g_form = frame.g_form;\n        if (typeof g_form !== \"undefined\") {\n            const journalInputs = g_form.elements.filter(e => e.type === \"journal_input\");\n\n            const buttonContainer = document.createElement(\"div\");\n            journalInputs.forEach(journalInput => {\n                const button = document.createElement(\"button\");\n                button.style.margin = \"0.8rem\";\n                button.textContent = journalInput.fieldName;\n                buttonContainer.appendChild(button)\n                button.onclick = () => {\n                    const content = frame.tinymce.get('modal-editor').getContent();\n                    g_form.setValue(journalInput.fieldName, `[code]${content}[/code]`)\n                }\n            });\n            modalContent.appendChild(buttonContainer)\n\n        }\n        modalContainer.appendChild(modalContent)\n\n        if (typeof frame.tinymce === 'undefined') {\n            const scriptFiles = ['/scripts/tinymce_default/js_includes_tinymce.jsx?sysparm_substitute=false']\n            frame.ScriptLoader.getScripts(scriptFiles, createEditor);\n        } else {\n            createEditor();\n        }\n    }\n    const frame = top?.window?.gsft_main || this;\n    const document = frame.document;\n\n    if (document.getElementById(\"drc-overlay\")) return;\n\n    const modalContainer = createOverlay();\n    addModalContent(modalContainer);\n})();\n/*\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Open tinymce for journal/README.md",
    "content": "# tinymce journal editor Bookmarklet Modal\n\nThis script is a bookmarklet that injects a draggable modal into a form (or ui page with access to ScriptLoader api in the content frame) with a tiny mce editor and buttons to insert the html to the journal fields on the form.\n\nThe dependency for tinymce is loaded from the instance itself and a html field does not need to be present on the form. \n\n---\n\n## 📸 Screenshots\n\n### Modal Overview\n![Modal Overview](tinymce.png)\n\n---\n\n## 🔧 How to Use\n\n1. **Copy the minified script** on the first line of the *Open tinymce editor in modal for journal fields.js* file\n2. **Create a bookmark** in your browser.\n3. Paste the script into the bookmark's URL field.\n4. Navigate to a ServiceNow form and click the bookmarklet.\n5. Input formatted text into editor and click a button from the bottom to set the html to the corresponding journal field with the [code] tags\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Quick Notes/README.md",
    "content": "## Quick Note\n\nA bookmarklet that opens a new browser tab with a blank editable page for quick notes. Copy and paste work with or without (right-click) formatting. Includes javascript string methods `'variable_name'.get()` to get the contents of the page into a variable and `variable_name.set()` to set the variable contents into another page. This allows for quick processing and output of the results.\n\n```html\ndata:text/html, <title>Quick Note</title><script>String.prototype.get=function(){window[this]=document.body.innerText};String.prototype.set=function(){w=window.open();w.document.body.innerHTML='<pre style=\"font: 1rem/1.5 monospace;margin:0 auto;padding:2rem;\">'+this.toString()+'</pre>'};</script><body contenteditable style=\"font: 1rem/1.5 monospace;margin:0 auto;padding:2rem;\">\n```\n\n## Example\nEnter some text onto the page.  \n> `This is some text`  \n\nOpen the Browser Console.  \nType a string literal with the `.get()` method.   \n> `'t'.get()`  \n\nThe string literal becomes a variable name containing the contents of the page.  \nYou can now manipulate the string however needed.  \nTo see the results, you can simply log the info to the console or you can post it to a new page. To post the content to a new page, call the `.set()` method.  \n> `variable_name.set()`\n\nThe command below will open a new tab containing \"THIS IS SOME TEXT\"  \n> `t.toUpperCase().set();` \n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Quick Notes/quick_note.html",
    "content": "data:text/html, <title>Quick Note</title><script>String.prototype.get=function(){window[this]=document.body.innerText};String.prototype.set=function(){w=window.open();w.document.body.innerHTML='<pre style=\"font: 1rem/1.5 monospace;margin:0 auto;padding:2rem;\">'+this.toString()+'</pre>'};</script><body contenteditable style=\"font: 1rem/1.5 monospace;margin:0 auto;padding:2rem;\">\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Quick login to current instance/Quick login.js",
    "content": "javascript:\nlet params = `scrollbars=no,resizable=no,status=no,location=no,toolbar=no,menubar=no,width=300,height=300,left=100,top=100`;\nlet loginWin = window.open(\"http://\" + window.location.hostname + \"/login\", \"login\", params);\nsetInterval(()=>{\n  if(!loginWin.location.pathname.includes(\"login\")){\n    loginWin.close();\n    window.location.reload();\n  };},500);\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Quick login to current instance/README.md",
    "content": "## Quick login to current instance\n\nIf you frequently have to login to the currently used instance with a different user (e.g. with your admin account), this Bookmarklet will be a huge timesaver for you!\n\nAll you have to do is to click the Bookmarklet, and a popup window will appear with the current Servicenow instance's login page. After you enter your credentials (or your password manager enters it), the popup will close, and the page will reload to log you in.\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/ServiceNow Instance Collection/README.md",
    "content": "# Bookmarklets\n\nBookmarklets are simply browser bookmarks. They are created using javascript to make them more robust than simple links. They can execute within the context of the current active window and tab of your browser, which makes them very powerful and convenient.\n\n## Why use bookmarklets?\n- Do you work on multiple instances (dev, test, prod, customers, partners, etc.)?\n- Do you have favorites that you use frequently?\n- Do you get delayed when you work on a new or different instance and haven't setup your favorites yet?\n- Have you had your favorites wiped out by a clone?\n\n## Answer\n- Keep your favorites in your browser rather than in the instance!\n\n## How do I use bookmarklets?\nThe HTML file contains a set of bookmarklets that are instance agnostic. As long as you have an instance tab currently active, the bookmarklets will use the same instance. \n\n- Some are simple navigation while others are actions (i.e. show the contents of the scratchpad in an info message box or make all hidden fields visible). \n- The navigation bookmarklets will open in new tabs so you will not lose your place.\n- Some are meant to be used within a specific page context. For example, showing the scratchpad contents in an info message box only works on the Platform UI Forms.\n\n## How do I add these bookmarklets to my browser?\n- Open the HTML file and simply drag a bookmarklets to your bookmarks bar to create the bookmarklet.\n- Sort and group them as desired. They work in subfolders too.\n- Open an instance and login.\n- From any page on the instance, select the bookmarklet to activate it."
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/ServiceNow Instance Collection/bookmarklets.html",
    "content": "<H1>Bookmarks</H1>\n<DL><p>\n    <DT><H3 ADD_DATE=\"1650720615\" LAST_MODIFIED=\"1664546260\" PERSONAL_TOOLBAR_FOLDER=\"true\">Bookmarks Bar</H3>\n    <DL><p>\n            <DT><A HREF=\"javascript:top.location.pathname=&quot;&quot;\" ADD_DATE=\"1662054942\">Home</A>\n            <DT><A HREF=\"javascript:top.open(&quot;now/nav/ui/home&quot;);\" ADD_DATE=\"1662054942\">Admin Workspace</A>\n            <DT><A HREF=\"javascript:top.open(&quot;/now/appenginestudio&quot;);\" ADD_DATE=\"1662054942\">App Engine Studio</A>\n            <DT><A HREF=\"javascript:top.open(&quot;sysapproval_approver_list.do&quot;);\" ADD_DATE=\"1662054942\">Approvals</A>\n            <DT><A HREF=\"javascript:top.open(&quot;sys_script_list.do&quot;);\" ADD_DATE=\"1662054942\">Business Rules</A>\n            <DT><A HREF=\"javascript:top.open(&quot;/sys_audit_delete_list.do&quot;)\" ADD_DATE=\"1662054942\">Deleted Audit Records</A>\n            <DT><A HREF=\"javascript:top.open(&quot;$flow-designer.do&quot;);\" ADD_DATE=\"1662054942\">Flow Designer</A>\n            <DT><A HREF=\"javascript:top.open(&quot;$knowledge.do&quot;);\" ADD_DATE=\"1662054942\">Knowledge</A>\n            <DT><A HREF=\"javascript:top.open(&quot;login.do&quot;);\" ADD_DATE=\"1662054942\">Login</A>\n            <DT><A HREF=\"javascript:top.open(&quot;sys_update_set.do?sys_id=-1%22);\" ADD_DATE=\"1662054942\">New Update Set</A>\n            <DT><A HREF=\"javascript:top.open(&quot;$allappsmgmt.do&quot;);\" ADD_DATE=\"1662054942\">Plugins</A>\n            <DT><A HREF=\"javascript:top.open(&quot;report_home.do&quot;);\" ADD_DATE=\"1662054942\">Reports</A>\n            <DT><A HREF=\"javascript:top.open('/sys_trigger_list.do');\" ADD_DATE=\"1663250178\">Scheduled Jobs</A>\n            <DT><A HREF=\"javascript:top.open('/sysauto_list.do');\" ADD_DATE=\"1663250293\">Scheduled Job Definitions</A>\n            <DT><A HREF=\"javascript:top.open(&quot;sys.scripts.do&quot;);\" ADD_DATE=\"1662054942\">Scripts - Background</A>\n            <DT><A HREF=\"javascript:top.open('sys_script_include_list.do');\" ADD_DATE=\"1663696463\">Script - Includes</A>\n            <DT><A HREF=\"javascript:top.open('sys_ui_script_list.do');\" ADD_DATE=\"1663696379\">Scripts - UI</A>\n            <DT><A HREF=\"javascript:top.open(&quot;/sp_config&quot;);\" ADD_DATE=\"1662054942\">Service Portal Config</A>\n            <DT><A HREF=\"javascript:top.open(prompt(&quot;What portal URL do you want to open?%22)||%22sp%22);\" ADD_DATE=\"1662054942\">Service Portals</A>\n            <DT><A HREF=\"javascript:top.open(&quot;side_door.do&quot;);\" ADD_DATE=\"1662054942\">Side Door</A>\n            <DT><A HREF=\"javascript:top.open(&quot;stats.do&quot;);\" ADD_DATE=\"1662054942\">Stats.do</A>\n            <DT><A HREF=\"javascript:top.open(&quot;$studio.do?sysparm_transaction_scope=global&sysparm_nostack=true%22);\" ADD_DATE=\"1662054942\">Studio</A>\n            <DT><A HREF=\"javascript:top.open(&quot;syslog_list.do?sysparm_query=sys_created_onONLast%2015%20minutes%40javascript%3Ags.beginningOfLast15Minutes()%40javascript%3Ags.endOfLast15Minutes()&sysparm_view=%22);\" ADD_DATE=\"1662054942\">System Logs - 15 minutes</A>\n            <DT><A HREF=\"javascript:top.open(&quot;sys_db_object_list.do&quot;)\" ADD_DATE=\"1662054942\">Tables</A>\n            <DT><A HREF=\"javascript:top.open(&quot;workflow_ide.do&quot;);\" ADD_DATE=\"1662054942\">Workflow Editor</A>\n            <DT><A HREF=\"javascript:top.open(&quot;snd_xplore.do&quot;);\" ADD_DATE=\"1662054942\">Xplore</A>\n                <DT><H3 ADD_DATE=\"1662655990\" LAST_MODIFIED=\"1663266697\">CMDB</H3>\n                    <DL><p>\n                        <DT><A HREF=\"javascript:top.open(&quot;$ciModel.do&quot;);\" ADD_DATE=\"1662651163\">CI Class Manager</A>\n                        <DT><A HREF=\"javascript:top.open(&quot;$pa_dashboard.do%3Fsysparm_dashboard%3D0b35c80ceb10220078e44d3df106fe61&quot;);\" ADD_DATE=\"1662750601\">CMDB View Dashboard</A>\n                        <DT><A HREF=\"javascript:top.open(&quot;$pa_dashboard.do%3Fsysparm_dashboard%3D5b080828c7843200a210c1452e976394&quot;);\" ADD_DATE=\"1662753976\">CMDB Group VIew</A>\n                        <DT><A HREF=\"javascript:top.open(&quot;$pa_dashboard.do%3Fsysparm_dashboard%3D48b1c6beebb3120078e44d3df106fe29&quot;);\" ADD_DATE=\"1662754005\">CMDB Service View</A>\n                        <DT><A HREF=\"javascript:top.open('now/cmdb/home');\" ADD_DATE=\"1663266692\">CMDB Workspace</A>\n                    </DL><p>\n                <DT><H3 ADD_DATE=\"1662054942\" LAST_MODIFIED=\"1664229363\">Utils</H3>\n                    <DL><p>\n                    <DT><A HREF=\"javascript:var match=window.location.href.match(/(http.*\\/)(\\w+.do)\\?(.*)/);window.location.href=match[1]+%22nav_to.do?uri=%22+match[2]+%22&%22+match[4];\" ADD_DATE=\"1663957007\">Add Navigation Pane</A>\n                    <DT><A HREF=\"javascript:top.open(&quot;cancel_my_transaction.do&quot;);\" ADD_DATE=\"1662054942\">Cancel My Transactions</A>\n                    <DT><A HREF=\"javascript:top.open(&quot;cache.do&quot;);\" ADD_DATE=\"1662054942\">Clear Server Cache</A>\n                    <DT><A HREF=\"javascript:var f=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22].g_form:g_form;f.elements.forEach(field=%3Ef.setReadOnly(field.fieldName,false));\" ADD_DATE=\"1662054942\">Make Fields Editable</A>\n                    <DT><A HREF=\"javascript:var f=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22].g_form:g_form;f.elements.forEach(field=%3Ef.setMandatory(field.fieldName,false));\" ADD_DATE=\"1662054942\">Make Fields Optional</A>\n                    <DT><A HREF=\"javascript:var f=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22].g_form:g_form;f.elements.forEach(field=%3Ef.setVisible(field.fieldName,true));\" ADD_DATE=\"1662054942\">Make Fields Visible</A>\n                    <DT><A HREF=\"javascript:top.open(prompt(&quot;Where do you want to go today:&quot;));\" ADD_DATE=\"1662054942\">Navigate To</A>\n                    <DT><A HREF=\"javascript:var f=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22].g_form:g_form;f.save();\" ADD_DATE=\"1662054942\">Save Record</A>\n                    <DT><A HREF=\"javascript:var f=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22].g_form:g_form;f.setValue(prompt(%22Enter%20the%20field%20name:%22),prompt(%22Enter%20the%20value:%22));\" ADD_DATE=\"1662054942\">Set Field Value</A>\n                    <DT><A HREF=\"javascript:top.location+=&quot;&sysparm_userpref_rowcount=&quot;+prompt(&quot;Set row count to: &quot;);\" ADD_DATE=\"1662054942\">Set Row Count</A>\n                    <DT><A HREF=\"javascript:var f=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22].g_form:g_form;f.addInfoMessage(%22%3Ctable%20class=%27table%27%3E%3Ctr%3E%3Cth%3EName%3C/th%3E%3Cth%3EType%3C/th%3E%3Cth%3EReference%3C/th%3E%3C/tr%3E%22+f.elements.map(el=%3E%27%3Ctr%3E%27+Object.keys(el).filter(k=%3E[%27fieldName%27,%27type%27,%27reference%27].includes(k)).map(k=%3E%27%3Ctd%3E%27+el[k]+%27%3C/td%3E%27)+%27%3C/tr%3E%27)+%22%3C/table%3E%22)\" ADD_DATE=\"1663616727\">Show Field Info</A>\n                    <DT><A HREF=\"javascript:var w=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22]:window;w.g_form.addWarningMessage(%22Scratchpad:%3Cbr/%3E%3Cpre%3E%22+JSON.stringify(w.g_scratchpad,%20null,%202%20)%20+%20%22%3C/pre%3E%22);\" ADD_DATE=\"1662489547\">Show Scratchpad</A>\n                    <DT><A HREF=\"javascript:var w=window.frames[&quot;gsft_main&quot;]!==undefined?window.frames[%22gsft_main%22]:window;w.g_scratchpad=w.g_scratchpad||{%22scratchpad%22:%22empty%22};w.alert(%22Scratchpad:\\n%22+JSON.stringify(w.g_scratchpad,null,2));\" ADD_DATE=\"1662489536\">Show Scratchpad (Alert)</A>\n                    <DT><A HREF=\"javascript:top.open(&quot;channel.do?sysparm_channel=logtail%22);\" ADD_DATE=\"1664229346\">Tail Node Log</A>\n                </DL><p>\n            </DL><p>\n    </DL><p>\n</DL><p>\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Show Logged-in User roles/readme.md",
    "content": "# ServiceNow User Info Bookmarklet\n\nA simple, client-side bookmarklet to display the current ServiceNow user's details in a pop-up modal. It's designed for developers, testers, and administrators who need to quickly check a user's name, sys_id, and full list of assigned roles\n\n## Features\n\n* **Displays Key User Info**: Shows Full Name, User Name, and User ID (sys_id)\n* **Shows All Roles**: Lists the complete, sorted array of roles, including inherited roles\n\n\n## How to Use\n\n1.  Create a new bookmark in your browser\n2.  For the name, enter something memorable like **SN User Info**\n3.  Copy the code from user_info_bookmarklet.js\n4.  Paste the code into the **URL** or **Address** field of the bookmark\n5.  Save the bookmark\n6.  While on any ServiceNow page, click the bookmark to activate the modal\n\n<img width=\"1899\" height=\"761\" alt=\"image\" src=\"https://github.com/user-attachments/assets/6dd8e92a-f905-4516-b1be-61e32a4de35c\" />\n\n"
  },
  {
    "path": "Specialized Areas/Browser Bookmarklets/Show Logged-in User roles/user_info_bookmarklet.js",
    "content": "javascript:(function(){var e=null;if(\"undefined\"!=typeof g_user&&g_user.userName)e=g_user;else if(\"undefined\"!=typeof gsft_main&&gsft_main.g_user&&gsft_main.g_user.userName)e=gsft_main.g_user;else if(\"undefined\"!=typeof window.NOW&&window.NOW.user&&\"undefined\"!=typeof window.ux_globals&&window.ux_globals.session)try{e={fullName:window.ux_globals.session.output.user.fullName,userName:window.ux_globals.session.output.user.userName,userID:window.NOW.user.userID,allRoles:window.ux_globals.session.output.user.roles}}catch(o){e=null}if(!e)return void alert(\"Could not find a ServiceNow user object (g_user, gsft_main.g_user, or Workspace objects). Are you on a ServiceNow page and logged in?\");var n=document.getElementById(\"sn-user-modal\");n&&n.parentNode.removeChild(n);var o=e.allRoles.filter(function(e){return e}).sort().join(\"<br>\"),t='<div style=\"font-family:sans-serif;color:#333;\"><h2>User Details</h2><p><strong>Full Name:</strong> '+e.fullName+'</p><p><strong>User Name:</strong> '+e.userName+'</p><p><strong>User ID (sys_id):</strong> <span style=\"font-family:monospace;background:#eee;padding:2px 4px;border-radius:3px;\">'+e.userID+'</span></p><h3>All Roles ('+e.allRoles.filter(function(e){return e}).length+')</h3><div style=\"height:250px;overflow-y:auto;border:1px solid #ccc;padding:10px;background-color:#f9f9f9;border-radius:4px;font-size:12px;\">'+o+\"</div></div>\",l=document.createElement(\"div\");l.id=\"sn-user-modal\";var d=\"#sn-user-modal-backdrop{position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.6);z-index:9999;}#sn-user-modal-content{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);background-color:#fff;padding:25px;border-radius:8px;box-shadow:0 5px 15px rgba(0,0,0,0.3);width:550px;max-width:90%;max-height:90vh;z-index:10000;box-sizing:border-box;}#sn-user-modal-body{max-height:calc(90vh - 100px);overflow-y:auto;}#sn-user-modal-close{position:absolute;top:10px;right:15px;font-size:28px;font-weight:bold;cursor:pointer;color:#888;line-height:1;}#sn-user-modal-close:hover{color:#000;}\";function i(){var e=document.getElementById(\"sn-user-modal\");e&&e.parentNode.removeChild(e)}l.innerHTML=\"<style>\"+d+'</style><div id=\"sn-user-modal-backdrop\"></div><div id=\"sn-user-modal-content\"><span id=\"sn-user-modal-close\">&times;</span><div id=\"sn-user-modal-body\">'+t+\"</div></div>\",document.body.appendChild(l),document.getElementById(\"sn-user-modal-close\").addEventListener(\"click\",i),document.getElementById(\"sn-user-modal-backdrop\").addEventListener(\"click\",i)})();\n"
  },
  {
    "path": "Specialized Areas/Browser Utilities/Custom Search Engines/README.md",
    "content": "# Custom Browser Site Searches\n\nCustom searches in your browser allow you to quickly do a search on specific sites directly from the browser address bar.\n\nFor example you can type the following in your browser address bar to search for Flow Designer on the ServiceNow Docs site: sndocs flow designer\n\n\n## Custom Search Engines\n\n### ServiceNow Docs\nSearch the ServiceNow Docs site directly.\n\n- **Search engine**: ServiceNow Docs (Vancouver)\n- **Shortcut**: sndocs\n- **URL**: https://docs.servicenow.com/search?q=%s&labelkey=vancouver\n\n### ServiceNow Community\nSearch the ServiceNow Community site directly\n\n- **Search engine**: ServiceNow Community\n- **Shortcut**: sncomm\n- **URL**: https://www.servicenow.com/community/forums/searchpage/tab/message?advanced=false&allow_punctuation=false&q=%s\n\n### My Instance Global Search\nDo a Global search on my instance. Remember to replace \"instance\" with your actual instance name.\n\n- **Search engine**: My Instance Global Search\n- **Shortcut**: instance\n- **URL**: https://instance.service-now.com/nav_to.do?uri=text_search_exact_match.do%3Fsysparm_search=%s\n\n\n## How add site search to browser\n\n### Chrome\n1. Open Settings\n1. Select \"Search engine\"\n1. Under \"Site search\", click \"Add\"\n\n### Edge\n1. Open Settings\n1. Select \"Privacy, search, and services\"\n1. Open \"Address bar and search\"\n1. Open \"Manage search engines\"\n1. Click \"Add\"\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB CI Deduplication Task Generator/CI Deduplication Tasks Generation with UI Action.js",
    "content": "//UI Action for Create De-duplicate Tasks \n//Onclick showConfirmationDialog\n\nfunction showConfirmationDialog() {\nvar entries = g_list.getChecked();\nvar sysIDs = entries.split(',');\n\nvar con1 = confirm('Total number of Selected CIs ' + sysIDs.length + '. Click OK to create De-duplicate task');\n\nif (con1) {\nalert(sysIDs);\nvar ga = new GlideAjax('createDuplicateCITask');\nga.addParam('sysparm_name', 'createDeDupTask');\nga.addParam('sysparm_entry_ids', entries);\nga.getXML(getDupTasks);\n}\n\nfunction getDupTasks(response) {\n\nvar answer = response.responseXML.documentElement.getAttribute(\"answer\");\nif (answer == null) {\nalert('Failed to create Remediate Duplicate Task. Selected CIs are already part of an open Remediate Duplicate Task');\n} else {\nvar url1 = 'reconcile_duplicate_task.do?sys_id=' + answer;\nvar con = confirm('The De-duplicate task is created. Click OK to redirect to De-duplicate task record');\nif (con) {\nlocation.href = url1;\n}\n}\n}\n}\n\n//Script Include\n\nvar createDuplicateCITask = Class.create();\ncreateDuplicateCITask.prototype = Object.extendsObject(AbstractAjaxProcessor, {\ncreateDeDupTask: function() {\nvar entries = this.getParameter('sysparm_entry_ids');\n\nvar dupTaskUtil = new CMDBDuplicateTaskUtils();\nvar deDupTaskID = dupTaskUtil.createDuplicateTask(entries);\n\nreturn deDupTaskID;\n\n},\n\ntype: 'createDuplicateCITask'\n});\n\n \n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB CI Deduplication Task Generator/README.md",
    "content": "# CI Deduplication Task Generator\n\nThis repository contains a ServiceNow customization that enables users to create De-Duplicate Tasks for selected Configuration Items (CIs) directly from a list view using a UI Action.\n\nWhen executed, the UI Action confirms the number of selected CIs, calls a Script Include via GlideAjax, and creates a Remediate Duplicate Task using CMDBDuplicateTaskUtils\n\n### How It Works\n\n* Allows users to select multiple CIs and trigger de-duplication in one click\n* Automatically creates a De-Duplicate Task record using backend logic\n* Displays confirmation dialogs for task creation and redirection\n* Prevents duplicate task creation for CIs already linked to an open task\n* Redirects to the created De-Duplicate Task record for quick review\n\n\n### Dependencies\n\nThis script requires the `global.CMDBDuplicateTaskUtils` Script Include to be active in your instance.\n\n### Configuration & Use\n\nCreation of UI Action and asking confirmation of selected Records from List View by using GlideAjax\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Dynamic Status Update Function/README.md",
    "content": "Overview:\n\nThis function is designed to keep Configuration Items (CIs) within the CMDB (Configuration Management Database) up-to-date by ensuring that CIs whose discovery dates exceed a certain threshold are automatically updated or deleted. It dynamically updates the operational status of CIs or removes them from the database based on the number of days since they were last discovered.\n\nThe script is flexible and can work with any CMDB table, offering administrators the ability to control and maintain data hygiene across various CMDB records by using parameters like table name, discovery source, and date thresholds.\n\nUse Case:\n\nIn many organizations, the CMDB grows with records over time, some of which may become outdated or irrelevant. This function helps keep the CMDB clean and operational by:\n1.\tMarking CIs as Non-operational if they haven't been discovered in a given period (e.g., more than 14 days)\n2.\tDeleting (retiring) CIs if they haven't been discovered for an even longer period (e.g., more than 30 days)\nThis process ensures that stale or outdated records are either updated or removed, improving data quality and operational efficiency.\n\nKey Features:\n\n1.\tDynamic Table Input: The function accepts any CMDB table name, making it versatile for use across various CI types (applications, servers, hardware, etc.)\n2.\tCustomizable Queries: Allows administrators to specify encoded queries to target specific records (e.g., CIs discovered by specific discovery sources)\n3.\tOperational Status Update: Automatically marks CIs as \"Non-operational\" after a defined period of inactivity\n4.\tCI Deletion (Retirement): Deletes CIs that have not been discovered for a prolonged period, reducing clutter in the CMDB\n\nBenefits:\n1.\tData Hygiene: Helps maintain clean, relevant, and up-to-date records in the CMDB by removing or marking inactive CIs\n2.\tAutomation: Automates the process of identifying and managing stale records without manual intervention\n3.\tFlexibility: Works across multiple CMDB tables, making it adaptable to different types of CIs (servers, applications, etc.)\n4.\tConfigurable: Parameters like the table name, discovery source, and days thresholds can be easily adjusted to meet the organization’s specific needs\n5.\tImproved Performance: Reducing stale records can enhance the performance of queries and operations related to CMDB data\n6.\tGovernance & Compliance: Ensures the CMDB complies with internal policies by regularly updating or removing inactive CIs\n\nHow It Works:\n1.\tTable Selection: You specify the CMDB table name you want to target (e.g., cmdb_ci_appl for applications, cmdb_ci_server for servers)\n2.\tQuery Application: The function queries the specified table using an encoded query that filters the records (e.g., CIs discovered by ServiceNow Discovery)\n3.\tDate Comparison: It compares the last_discovered date of each record with the current date to determine how long it has been since the CI was last discovered\n4.\tOperational Status Update: If the CI has not been discovered for more than a specified number of days (e.g., 14 days), the function automatically updates the CI’s operational status to Non-operational\n5.\tDeletion of Old CIs: If the CI has not been discovered for an extended period (e.g., 30 days), the function deletes the record, removing outdated entries from the CMDB\n\nParameters:\n1.\tTable Name: The name of the CMDB table to target (e.g., cmdb_ci_appl, cmdb_ci_server)\n2.\tEncoded Query: A query string used to filter the records (e.g., only CIs discovered via ServiceNow Discovery)\n3.\tNon-operational Days: Number of days after which the record will be marked as Non-operational\n4.\tDeletion Days: Number of days after which the record will be deleted (retired)\n\nExample:\nFor instance, you might use this function to clean up outdated application CIs. The function will first mark all applications that haven't been discovered for 14 days as Non-operational. After 30 days without discovery, the function will then delete those records, keeping your CMDB clean and up-to-date.\n\nConclusion:\nThis dynamic function provides a simple, yet powerful solution to maintaining the integrity and cleanliness of your CMDB, ensuring that CIs are regularly checked for updates and outdated records are properly handled. By automating the process of marking CIs as Non-operational and retiring them, this function helps improve operational efficiency, data accuracy, and compliance with governance policies.\n\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Dynamic Status Update Function/updateCMDBOperationalStatus.js",
    "content": "function updateOperationalStatus(tableName, encodedQuery, nonOperDays, deletionDays) {\n    // Get the current date and time\n    var todayDate = new GlideDateTime();\n    gs.info(\"Today's date: \" + todayDate);\n\n    // Validate if the necessary fields exist in the table (last_discovered and operational_status)\n    var grCheck = new GlideRecord(tableName);\n    if (!grCheck.isValidField('last_discovered') || !grCheck.isValidField('operational_status')) {\n        gs.error(\"Table \" + tableName + \" does not have the required fields (last_discovered, operational_status).\");\n        return;\n    }\n\n    // Query the specified CMDB table with the provided encoded query\n    var gr = new GlideRecord(tableName);\n    gr.addEncodedQuery(encodedQuery);\n    gr.query();\n\n    while (gr.next()) {\n        var recentDiscovery = new GlideDateTime(gr.last_discovered);\n        gs.info(\"Last discovered date: \" + recentDiscovery);\n\n        // Calculate the difference between today and last_discovered\n        var differenceInMs = todayDate.getNumericValue() - recentDiscovery.getNumericValue();\n        var differenceInDays = Math.floor(differenceInMs / (1000 * 60 * 60 * 24)); // Convert milliseconds to days\n        gs.info(\"Difference in days: \" + differenceInDays);\n\n        // If last_discovered is more than nonOperDays old, mark as Non-operational\n        if (differenceInDays > nonOperDays) {\n            gr.operational_status = '2'; // 2 is typically used for 'Non-operational' or similar status\n            gr.update(); // Update the record to mark it as non-operational\n            gs.info(\"Record \" + gr.sys_id + \" marked as Non-operational.\");\n        }\n\n        // If last_discovered is more than deletionDays old, delete the record\n        if (differenceInDays > deletionDays) {\n            gs.info(\"Record \" + gr.sys_id + \" is more than \" + deletionDays + \" days old and will be deleted.\");\n            gr.deleteRecord(); // Retire (delete) the record\n        }\n    }\n}\n\n\n// Example: Update records in 'cmdb_ci_appl' where discovery_source is 'ServiceNow'\n// Mark as Non-operational after 14 days, delete after 30 days\nupdateOperationalStatus('cmdb_ci_appl', 'discovery_source=ServiceNow^sys_id=5f8af237c0a8010e01a932999468b83a', 14, 30);\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Get CI Relationships/README.md",
    "content": "# Retrieve CI Relationships present for particular CMDB CI record \nThis functionality allows users to obtain all configuration item (CI) relationships associated with a specific CMDB CI record. It provides detailed insights into how the selected CI is related to other CIs within the CMDB, enabling better management and understanding of infrastructure dependencies.\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Get CI Relationships/getCIRelationships.js",
    "content": "var cmdb_class_name = \"<cmdb_ci_class_name>\"; // Replace with your cmdb_table_name\nvar record_sys_id = \"<cmdb_ci_record_sys_id>\"; // Replace with your cmdb_ci record sys_id\n\nvar request = new sn_ws.RESTMessageV2();\nrequest.setHttpMethod('get');\nrequest.setEndpoint('https://<instance_name>.service-now.com/api/now/cmdb/instance/' + cmdb_class_name + \"/\" + record_sys_id);\nrequest.setBasicAuth('<instance_username>', '<instance_password>'); // Replace with your instance username and password\nvar response = request.executeAsync();\nresponse.waitForResponse(60);\n\nif (response.getStatusCode() == 200) {\n    var body = JSON.parse(response.getBody());\n    gs.info(\"Inbbound Relationships: \" + JSON.stringify(body.result.inbound_relations));\n    gs.info(\"Outbound Relationships: \" + JSON.stringify(body.result.outbound_relations));\n} else {\n    gs.error(\"Error in API Response with statusCode: \"+ response.getStatusCode())\n}\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Health Check/README.md",
    "content": "# CMDB Health Check – Non-Operational Installed Applications\n\n## Purpose\nThis script checks for Application Configuration Items (CIs) that are currently:\n- Installed (`install_status = 1`)\n- Non-operational (`operational_status = 2`)\n\n## Why Run This Check?\nSuch records can signal potential CMDB data quality issues, as an application marked \"Installed\" should generally be in an active/operational state. Spotting these mismatches early helps:\n- Prevent inaccurate reports and dashboards\n- Improve incident/change assignment accuracy\n- Maintain overall CMDB integrity\n\n## Output\nThe script logs the count of Application CIs that fit the criteria:\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Health Check/health_check.js",
    "content": "var ga = new GlideAggregate('cmdb_ci_appl');\nga.addQuery('operational_status', 2); // Non-operational\nga.addQuery('install_status', 1); // Installed\nga.addAggregate('COUNT');\nga.query();\nvar total = 0;\nif (ga.next()) {\n    total = ga.getAggregate('COUNT');\n}\ngs.info('Application CIs installed but not in operational count: ' + total);\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/README.md",
    "content": "The CMDVB Utility Scripts:\n1. unused CIs: This script is used to identify unused Configuration Items (CIs) in the CMDB that have not been updated within a specified number of days. The primary goal is to flag CIs that may be outdated or not maintained. In the script one can provide for the number of days to check for the \nCIs that haven't been be used for 'd' days.\n\n2. detectDuplicates.js: This script is designed to detect duplicate Configuration Items (CIs) in the CMDB (Configuration Management Database) in ServiceNow. Duplicate CIs can cause data quality issues and interfere with processes such as asset management, incident management, and change management. By identifying CIs with the same values in specific fields (like name, serial number, or asset tag), the script helps maintain the integrity of the CMDB.\n\n3. populateMissingManufacturers.js: This script is used in ServiceNow to automatically populate the manufacturer field for CIs (Configuration Items) in the CMDB (Configuration Management Database) when the manufacturer field is missing (null). It uses a predefined mapping between models and their respective manufacturers, and updates the CIs accordingly.\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/ReadME.md",
    "content": "This script is used in ServiceNow to automatically fill in the missing manufacturer information for Configuration Items (CIs) in the CMDB (Configuration Management Database).\n\n1. Predefined Mapping:\nThe script starts with a list of known model names and their corresponding manufacturer names.For example, a model called ThinkPad T14 is made by Lenovo, and MacBook Pro 16 is made by Apple\n2. Look Up Manufacturer:\n    * It defines a function that looks up the manufacturer’s record in the core_company table (based on the name) and gets its unique ID (sys_id).\n3.  Find CIs Missing a Manufacturer:\n    * The script goes through all CIs in the cmdb_ci table where the manufacturer field is empty.\n4.  Update Missing Manufacturer:\n    * For each of those CIs:\n        * It checks the model name.\n        * If the model is in the predefined mapping:\n            * It looks up the correct manufacturer in the core_company table.\n            * It updates the CI record by setting the manufacturer field with the correct sys_id.\n            * It also logs that the update was successful.\n        * If the manufacturer is not found in the system, it logs a warning.\n5.  Final Log:\n    * After going through all matching CIs, it logs how many records were successfully updated.\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/TechTrekwithAJ-AutoPopulateManufacturer.js",
    "content": "// Predefined mapping of model names to manufacturer sys_ids or names\nvar modelManufacturerMap = {\n    'ThinkPad T14': 'Lenovo',\n    'EliteBook 840': 'HP',\n    'Latitude 7420': 'Dell',\n    'MacBook Pro 16': 'Apple',\n    'Surface Pro 7': 'Microsoft'\n};\n\n// Function to get the manufacturer sys_id from the manufacturer name\nfunction getManufacturerSysId(name) {\n    var mfgGR = new GlideRecord('core_company');\n    mfgGR.addQuery('name', name);\n    mfgGR.query();\n    if (mfgGR.next()) {\n        return mfgGR.getUniqueValue();\n    }\n    return null;\n}\n\n// GlideRecord to loop through Configuration Items where manufacturer is empty\nvar ciGR = new GlideRecord('cmdb_ci');\nciGR.addNullQuery('manufacturer'); // Manufacturer is empty\nciGR.query();\n\nvar updatedCount = 0;\n\nwhile (ciGR.next()) {\n    var model = ciGR.model.name.toString(); // Get model name\n\n    if (model && modelManufacturerMap.hasOwnProperty(model)) {\n        var manufacturerName = modelManufacturerMap[model];\n        var manufacturerSysId = getManufacturerSysId(manufacturerName);\n\n        if (manufacturerSysId) {\n            ciGR.manufacturer = manufacturerSysId;\n            ciGR.update();\n            updatedCount++;\n            gs.info('Updated CI: ' + ciGR.name + ' | Model: ' + model + ' | Manufacturer: ' + manufacturerName);\n        } else {\n            gs.warn('Manufacturer \"' + manufacturerName + '\" not found in core_company table.');\n        }\n    }\n}\n\ngs.info('✅ Auto-populate complete. Total CIs updated: ' + updatedCount);\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/detectDuplicateCIs.js",
    "content": "var gr = new GlideAggregate('cmdb_ci');\ngr.addAggregate('COUNT', 'name');\ngr.groupBy('name'); // You can change this to 'serial_number' or 'asset_tag'\ngr.query();\n\nwhile (gr.next()) {\n    if (gr.getAggregate('COUNT', 'name') > 1) {\n        gs.info('Duplicate CI found for: ' + gr.name + ', Count: ' + gr.getAggregate('COUNT', 'name'));\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/populateMissingManufacturers.js",
    "content": "var modelToManufacturer = {\n    'HP Model X': 'HP',\n    'Dell Model Y': 'Dell'\n    // Add more model to manufacturer mappings\n};\n\nvar gr = new GlideRecord('cmdb_ci');\n//I am finding CIs where Manufacturer field is null\ngr.addNullQuery('manufacturer');\n//You can modify the query to further filter the CIs, for example, to target only active CIs or those belonging \n//to a specific CI class (e.g., servers or routers):\n//gr.addQuery('install_status', '=', '1'); // Only query active CIs\n//gr.addQuery('sys_class_name', '=', 'cmdb_ci_server'); // Only query server CIs\ngr.query();\n\nwhile (gr.next()) {\n    var model = gr.model_id.name;\n    if (modelToManufacturer[model]) {\n        gr.manufacturer = modelToManufacturer[model];\n        gr.update();\n        gs.info('Updated manufacturer for CI: ' + gr.name);\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/softwareCreation.js",
    "content": "var SoftwareUtils = Class.create();\nSoftwareUtils.prototype = {\n    initialize: function() {},\n\n    softwareUtil: function(deviceId, softwareId) {\n        var softwareInstance = new GlideRecord('cmdb_software_instance');\n        gs.info(\"Transformation\" + deviceId + \" \" + softwareId);\n        softwareInstance.addQuery('installed_on', deviceId);\n        softwareInstance.addQuery('software', softwareId);\n        softwareInstance.query();\n\n        if (!softwareInstance.next()) {\n            gs.info(\"Transformation inside the loop\");\n            var newSoftwareInstance = new GlideRecord('cmdb_software_instance');\n            newSoftwareInstance.initialize();\n            newSoftwareInstance.setValue('installed_on', deviceId);\n            newSoftwareInstance.setValue('software', softwareId);\n            newSoftwareInstance.insert();\n        }\n    },\n\n    type: 'SoftwareUtils'\n};\n\n\nTransform Script Example\n\nUsed within a Transform Map Script to ensure the relationship between the imported software record and its device is maintained.\n\n(function runTransformScript(source, map, log, target /*undefined onStart*/ ) {\n    var ninjaSoftware = new global.SoftwareUtils();\n    ninjaSoftware.softwareUtil(source.u_device_sys_id, target.getUniqueValue());\n})(source, map, log, target);\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/softwareCreationREADME.md",
    "content": "This script automates the creation of Software Instance relationships in the CMDB during data import or transformation.\nIt ensures that each discovered or imported software record is correctly linked to the device on which it is installed, without creating duplicates.\n\n\n\nThe SoftwareUtils Script Include provides a utility function that checks whether a specific software (cmdb_software_product) is already related to a device (cmdb_ci_computer, cmdb_ci_server, etc.) through a Software Instance (cmdb_software_instance) record.\n\nIf the relationship does not exist, it creates a new software instance automatically.\n\nThis logic can be used directly within a Transform Map Script (onAfter) during software data import.\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB Utility Scripts/unUsedCIs.js",
    "content": "var grCI = new GlideRecord('cmdb_ci');\n//Specify the threshold days\nvar daysThreshold = 30;\nvar dateThreshold = gs.daysAgo(daysThreshold);\n\ngrCI.addQuery('sys_updated_on', '<', dateThreshold);\ngrCI.query();\n\nwhile (grCI.next()) {\n    gs.info('Unused CI: ' + grCI.name + ', Last Updated: ' + grCI.sys_updated_on);\n}\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB record count/README.md",
    "content": "# CMDB Record count\n\nSimple code snippet to count the number of records in each CMDB table\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB record count/TechTrekwithAJ-CMDBCIcountonclass.js",
    "content": "(function() {\n    // Array of CMDB tables to count records from\n    var cmdbTables = [\n        'cmdb_ci',         // Configuration Items\n        'cmdb_ci_service', // Services\n        'cmdb_ci_database', // Databases\n        'cmdb_ci_server',   // Servers\n        'cmdb_ci_netgear',  // Network Gear,\n        'cmdb_ci_win_server',  // windows Servers\n        'cmdb_ci_linux_server',  // linux Servers\n        'cmdb_ci_appl',   // Applications\n        'cmdb_ci_computer',  // Computers\n        'cmdb_ci_business_app',  // Business Applications\n        'cmdb_ci_printer',   // Printers\n        'cmdb_ci_hardware',  // Hardware\n        'cmdb_ci_storage_device',  // Storage devices\n        'cmdb_ci_vm_object'  // Virtual Machine Objects\n       \n        // we can add more CMDB tables as we needed\n     \n    ];\n    \n    // Iterate over each table and count records\n    for (var i = 0; i < cmdbTables.length; i++) {\n        var tableName = cmdbTables[i];\n        var gr = new GlideRecord(tableName);\n        gr.query();\n        var count = gr.getRowCount();\n        \n        // Log the count for the current table\n        gs.info('Table: ' + tableName + ' - Record Count: ' + count);\n    }\n})();\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB record count/TechTrekwithAJ-readme.md",
    "content": "1. Function Declaration:This starts an anonymous function that is executed immediately. It's used for encapsulation, preventing global namespace pollution.\n2. Array Definition :This array (`cmdbTables`) lists the names of various CMDB tables that the script will query to count the number of records. Each entry is a string representing a table name, with comments describing each table's purpose.\n3. Iteration Over the Array: A `for` loop iterates through each table name in the `cmdbTables` array. The variable `i` serves as the loop index, and `tableName` holds the name of the current table being processed.\n4. GlideRecord Query: A new `GlideRecord` instance (`gr`) is created for the current table (`tableName`) and The `query()` method is called to retrieve all records from that table.\n5. Count Records:The `getRowCount()` method is called on the `GlideRecord` instance to get the total number of records retrieved by the query. This count is stored in the variable `count`.\n6. Logging the Results: The result is logged to the ServiceNow system log using `gs.info()`, displaying the table name and the corresponding record count.\n7. End of the Function: This closes the function and executes it immediately.\n"
  },
  {
    "path": "Specialized Areas/CMDB/CMDB record count/cmdb-record-count.js",
    "content": "var out = '\\n# of records,Table,Label\\n'\n\nvar getForm = function () {\n    var gq = new GlideRecord('sys_db_object');\n    gq.addEncodedQuery('nameSTARTSWITHcmdb_ci_');\n    gq.query();\n    while (gq.next()) {\n        var id = gq.getValue('sys_id');\n        var tableName = gq.getValue('name');\n        var label = gq.getDisplayValue('label');\n        getCount(tableName, label)\n    }\n}\n\nvar getCount = function(tableName, label) {\n\tvar agg = new GlideAggregate(tableName);\n    agg.addAggregate('COUNT');\n\tagg.query();\n\tif (agg.next()) {\n\t\tvar count = agg.getAggregate('COUNT');\n        if (count > 0){\n            out += count + ',' + tableName + ',' + label +'\\n';\n        }\n\t}\n};\n\ngetForm();\ngs.info(out);"
  },
  {
    "path": "Specialized Areas/CMDB/CSDM Maturity Report/README.md",
    "content": "# CSDM Maturity Report\n\nThis code snippet generates a report that helps with assessing an organization's Common Services Data Model (CSDM) maturity\n\nIt counts the number of records in the tables that are part of the CSDM by category\n"
  },
  {
    "path": "Specialized Areas/CMDB/CSDM Maturity Report/csdm-maturity-report.js",
    "content": "var csdm = {\n    Core: {\n        \"task\": \"Task\",\n        \"cmdb_ci\": \"CMDB\",\n        \"cmdb_rel_ci\": \"CI Relationship\",\n        \"alm_asset\": \"Asset\"\n    },\n    Foundation: {\n        \"cmdb_group\": \"CMDB Group\",\n        \"cmdb_model\": \"Product Model\",\n        \"ast_contract\": \"Contract\",\n        \"core_company\": \"Company\",\n        \"business_unit\": \"Business Unit\",\n        \"cmn_department\": \"Department\",\n        \"cmn_location\": \"Location\",\n        \"sys_user_group\": \"Group\",\n        \"sys_user\": \"User\",\n        \"sys_user.active=true\": \"User\"\n    },\n    Design: {\n        \"cmdb_ci_business_capability\": \"Business Capability\",\n        \"cmdb_ci_information_object\": \"Information Object\",\n        \"cmdb_ci_business_app\": \"Business Application\"\n    },\n    Technical_Services: {\n        \"cmdb_ci_service_technical\": \"Technical Service\",\n        \"service_offering.service_classification=Technical Service\": \"Technical Service Offering\",\n        \"cmdb_ci_service\": \"Service\",\n        \"cmdb_ci_service.service_classification=Technical Service\": \"Techinical Service\",\n        \"cmdb_ci_service.service_classification=Application Service\": \"Application Service\",\n        \"cmdb_ci_service.service_classification=infrastructure Service\": \"infrastructure Service\",\n        \"cmdb_ci_service.service_classification=End User Service\": \"End User Service\",\n        \"cmdb_ci_service_auto\": \"Application Service\",\n        \"cmdb_ci_service_discovered\": \"Mapped Application Service\",\n        \"cmdb_ci_service_calculated\": \"Calculated Application Service\",\n        \"cmdb_ci_service_by_tags\": \"Tag Based\",\n        \"cmdb_ci_query_based_service\": \"Dynamic CI Groups\",\n        \"svc_ci_assoc\": \"Service CI Associations\"\n    },\n    Sell_Consume: {\n        \"cmdb_ci_service.service_classification=Business Service\": \"Business Service\",\n        \"service_offering.service_classification=Business Service\": \"Business Service Offering\",\t\n        \"sc_catalog\": \"Catalogs\",\n        \"sc_cat_item_producer\": \"Catalog Items Producer\",\n        \"sc_cat_item.active=true^sys_class_name=sc_cat_item\": \"Catalog Items\"\n    }\n};\n\n\nvar getCount = function(tblQuery) {\n    var count = 0;\n    var tblQueryArr = tblQuery.split('.');\n    var tbl = tblQueryArr[0]\n    var query = tblQueryArr[1] || '';\n    gs.info(tbl);\n    var agg = new GlideAggregate(tbl);\n    if (query != '') {\n        agg.addEncodedQuery(query);\n    }\n    agg.addAggregate('COUNT');\n    agg.query();\n    if (agg.next()) {\n        count = agg.getAggregate('COUNT');\n    }\n    return count;\n};\nvar out = '';\nObject.keys(csdm).forEach(function (domain) {\n    Object.keys(csdm[domain]).forEach(function (tblQuery) {\n        var count = getCount(tblQuery);\n        out += domain + ',' + csdm[domain][tblQuery] + ',' + tblQuery + ',' + count + '\\n';\n    });\n});\n\ngs.info(out);\n"
  },
  {
    "path": "Specialized Areas/CMDB/IRE Errors/IRE Errors.js",
    "content": "var ireErrorMessagesObj = {\n    \"IDENTIFICATION_RULE_MISSING\": { count: 0, desc: \"Identity Rule Missing for table [xyz]\" },\n    \"MISSING_MATCHING_ATTRIBUTES\": { count: 0, desc: \"In payload missing minimum set of input values for criterion (matching) attributes from identify rule for table [xyz].\" },\n    \"NO_CLASS_NAME_FOR_INDEPENDENT_CI\": { count: 0, desc: \"Cannot have 'sys_class_name' as a key field in an Independent Identity Rule on 'xyz'\" },\n    \"IDENTIFICATION_RULE_FOR_LOOKUP_MISSING\": { count: 0, desc: \"Identity Rule for table [xyz] missing Lookup Rule for class [abc]\" },\n    \"IDENTIFICATION_RULE_FOR_RELATED_ITEM_MISSING\": { count: 0, desc: \"Identity Rule for table [xyz] missing Related Rule for class [abc]\" },\n    \"NO_LOOKUP_RULES_FOR_DEPENDENT_CI\": { count: 0, desc: \"Cannot have Lookup Rule for a Dependent Identity Rule on 'xyz'\" },\n    \"INVALID_INPUT_DATA\": { count: 0, desc: \"Invalid payload: invalid sys_id or invalid data_source or invalid relationship in cmdb_rel_ci etc..\" },\n    \"DUPLICATE_RELATIONSHIP_TYPES\": { count: 0, desc: \"Duplicate relationship type records exists with name [xyz] in table [cmdb_rel_type] having sys_ids: [abc]\" },\n    \"DUPLICATE_PAYLOAD_RECORDS\": { count: 0, desc: \"Found duplicate items in the payload (index 0 and 1), using className [xyz] and fields [abc].\" },\n    \"LOCK_TIMEOUT\": { count: 0, desc: \"Failed to acquire synchronization lock for xyz\" },\n    \"MULTIPLE_DUPLICATE_RECORDS\": { count: 0, desc: \"Found duplicate records in table [xyz] using fields [abc]\" },\n    \"REQUIRED_ATTRIBUTE_EMPTY\": { count: 0, desc: \"Missing mandatory field [xyz] in table [abc]. Add input value for mandatory field in payload\" },\n    \"MISSING_DEPENDENCY\": { count: 0, desc: \"In payload no relations defined for dependent class [xyz] that matches any containment/hosting rules: [abc].\" },\n    \"METADATA_RULE_MISSING\": { count: 0, desc: \"No containment or hosting rules defined for dependent class [xyz].\" },\n    \"MULTIPLE_DEPENDENCIES\": { count: 0, desc: \"Found multiple dependent relation items [xyz] and [abc] in payload OR Multiple paths leading to the same destination: xyz -> abc\" },\n    \"ABANDONED\": { count: 0, desc: \"Abandoning processing payload item 'xyz', since its depends on payload item 'abc' has errors. See Docs site for details.\" },\n    \"MULTI_MATCH\": { count: 0, desc: \"Duplicate dependent records found having relationship [xyz] with same CI (className:[abc], sysId:[def]). See Docs site for details.\" },\n    \"QUALIFICATION_LOOP\": { count: 0, desc: \"Qualification chain has loop that contains relation 'xyz'.\" },\n    \"TYPE_CONFLICT_IN_QUALIFICATION\": { count: 0, desc: \"Invalid payload, qualification chain has multiple possible paths for payload items: 'xyz' and 'abc'\" },\n    \"RECLASSIFICATION_NOT_ALLOWED\": { count: 0, desc: \"CI Reclassification not allowed from class: [xyz] to [abc].\" },\n    \"DUPLICATE_RELATED_PAYLOAD\": { count: 0, desc: \"Found duplicate Related items (0 and 1) in the payload index 1 using fields xyz.\" },\n    \"DUPLICATE_LOOKUP_PAYLOAD\": { count: 0, desc: \"Found duplicate Lookup items (0 and 1) in the payload index 1 using fields xyz\" },\n    \"INSERT_NOT_ALLOWED_FOR_SOURCE\": { count: 0, desc: \"Insert into [xyz] is blocked for data source [abc] by IRE data source rule.\" }\n};\n\nvar sysLogQuery = 'sourceSTARTSWITHiden^level=2^source=identification_engine';\nvar sysLog = new GlideRecord('syslog');\n// change me: set the limit or query by date range\nsysLog.setLimit(10);\nsysLog.addEncodedQuery(sysLogQuery);\nsysLog.query();\n\nwhile (sysLog.next()) {\n    Object.keys(ireErrorMessagesObj).forEach(function (key) {\n        if (sysLog.message.indexOf(key) > -1) {\n            ireErrorMessagesObj[key].count++;\n        }\n    });\n}\n\nout = 'count,error,description\\n';\nObject.keys(ireErrorMessagesObj).forEach(function (key) {\n    if (ireErrorMessagesObj[key].count > 0) {\n        out += ireErrorMessagesObj[key].count + ',' + key + ',' + ireErrorMessagesObj[key].desc + '\\n';\n    }\n});\n\ngs.info(out)\n"
  },
  {
    "path": "Specialized Areas/CMDB/IRE Errors/README.md",
    "content": "# IRE Errors\n\nThis code snippet displays a count of IRE errors grouped by category \nThis background script will help you undertand the most common source of IRE errors\nYou will need to adjust the record limit in line 30, or change the query time frame in line 27\n\n"
  },
  {
    "path": "Specialized Areas/CMDB/IRE Queridentify/Identify and query with IRE.js",
    "content": "function identifyAndQueryCI(className, identificationPayload, lookupIdentificationPayload) {\n\n    var grCI;\n\n    // Build the payload to identify this object \n\n    var payload = {\n        items: [{\n            'className': className,\n            'values': identificationPayload,\n            'lookup': lookupIdentificationPayload\n        }]\n    };\n\n    // Using IRE, identify and get the CI GlideRecord\n\n    var output = JSON.parse(SNC.IdentificationEngineScriptableApi.identifyCI(JSON.stringify(payload)));\n\n    // Using the IRE operation output, determine whether the CI exist\n\n    if (JSUtil.notNil(output)) {\n        if (output.items[0].operation !== 'INSERT') {\n            grCI = new GlideRecordUtil().getCIGR(output.items[0].sysId);\n        }\n    }\n\n    return grCI;\n\n}\n\n// Example of identication of a server using one of its MAC addresses and its name\n// this uses one of the out fo the box identification rules \n\nvar grServer = identifyAndQueryCI(\n\n    // Class to identify the CI from\n    'cmdb_ci_server',\n    \n    // Attributes to use to identify (at the same level than the CI class we're searching)\n    {\n        //'name': '*JEMPLOYEE-IBM'\n        'name': 'THIS NAME DOES NOT EXIST!'\n    },\n\n    // Attributes to use from classes referencing the one we search in\n    [\n        {\n            'className': 'cmdb_ci_network_adapter',\n            'values': {\n                //'name': 'eth0',\n                'mac_address': '00:13:CE:C9:16:5D'\n            }\n        },\n        {\n            'className': 'cmdb_ci_network_adapter',\n            'values': {\n                //'name': 'eth1',\n                'mac_address': '00:14:A4:DE:A6:E2'\n            }\n        }\n    ]\n\n);\n\n// If the CI has been identified we get a valid GlideRecord, otherwise we get undefined\n\nif (JSUtil.notNil(grServer)) {\n\n    gs.debug('Successfully identified CI ' + grServer.getDisplayValue() + ' of class ' + grServer.getValue('sys_class_name'));\n\n} else {\n\n    gs.debug('This CI does not exist');\n}\n"
  },
  {
    "path": "Specialized Areas/CMDB/IRE Queridentify/README.md",
    "content": "# Identifying and fetching a CI using IRE\n\nThis code snippet leverages `identifyCI()` and `GlideRecordUtil()`to identify and fetch a CI using the Identification and Reconciliation Engine. In this example we use lookup identification to make it a bit more sophisticated.\n\nThe code as-is will use the network adapters to identify the server. Replace *THIS NAME DOES NOT EXIST!* with **JEMPLOYEE-IBM* to use the name of the CI instead. The CI named **JEMPLOYEE-IBM* is a demo CI that comes with the standard PDI.\n\n*Note*: this example should work with an zBoot PDI. However, for some reasons it seems like the values of the `install_status` field of the demo data network adapter are corrupted or something. In order to fix this, type `cmdb_ci_networt_adapter.list` in the menu filter, and change the *Status* field (`install_status`) of all 3 records to `Installed`.\n"
  },
  {
    "path": "Specialized Areas/CMDB/Mandatory Field Analysis/README.md",
    "content": "# Mandatory Field Analysis\n\nMandatory fields may cause discovery not to work as expected, especially if the mandatory field is also a custom field. \n\nThis code snippet helps identify mandatory fields and can assist with remediating problematic configuration\n"
  },
  {
    "path": "Specialized Areas/CMDB/Mandatory Field Analysis/TechTrekwithAJ-CMDBMandatoryfieldanalysis.js",
    "content": "(function() {\n    // Define the CMDB table you want to analyze\n    var cmdbTableName = 'cmdb_ci_computer'; // Replace with your desired CMDB class name\n    var mandatoryFields = ['name', 'sys_class_name', 'location', 'install_status']; // Replace with your mandatory fields\n    var missingFieldsCount = 0;\n\n    // GlideRecord to query the specified CMDB table\n    var gr = new GlideRecord(cmdbTableName);\n    gr.query();\n\n    // Iterate through all records in the specified CMDB table\n    while (gr.next()) {\n        var missingFields = [];\n\n        // Check each mandatory field for a value\n        mandatoryFields.forEach(function(field) {\n            if (!gr.getValue(field)) {\n                missingFields.push(field);\n            }\n        });\n\n        // Log the record if any mandatory fields are missing\n        if (missingFields.length > 0) {\n            gs.info('Record missing mandatory fields: ' + gr.sys_id + ' (' + gr.name + '). Missing fields: ' + missingFields.join(', '));\n            missingFieldsCount++;\n        }\n    }\n\n    // Log the total number of records missing mandatory fields\n    gs.info('Total records missing mandatory fields: ' + missingFieldsCount);\n})();\n"
  },
  {
    "path": "Specialized Areas/CMDB/Mandatory Field Analysis/TechTrekwithAJ-cmdbmandatoryfielddescription_readme.md",
    "content": " CMDB Table Definition:\n        cmdbTableName specifies the CMDB class you want to analyze (e.g., cmdb_ci_computer).\n        Modify this to the desired table name.\nMandatory Fields Array:\n        mandatoryFields is an array that holds the names of the fields you want to check for mandatory values. Customize this list as per your requirements.\nGlideRecord Query:\n        A GlideRecord object is created to query the specified CMDB table.\nIteration and Check:\n        The script iterates through all records in the CMDB table and checks each mandatory field to see if it is populated.\n        If a mandatory field is missing, it adds the field name to the missingFields array.\nLogging:\n        If any mandatory fields are missing for a record, it logs the record's sys_id and name, along with the missing fields.\n        It also counts and logs the total number of records with missing mandatory fields at the end.\n"
  },
  {
    "path": "Specialized Areas/CMDB/Mandatory Field Analysis/mandatory-field-analysis.js",
    "content": "// sys_dictionary\n// need to account for sys_dictionary_override\nvar out_file = '\\nTable,Field name,Label,Mandatory,Updated By,Update Date,Update Name\\n';\nvar eQuery = 'mandatory!=false^nameSTARTSWITHcmdb_ci_';\nvar dict = new GlideRecord('sys_dictionary');\ndict.addEncodedQuery(eQuery);\ndict.query();\nwhile (dict.next()) {\n    var key2find = dict.name + ',' + dict.column_label + ',' + dict.element + ',' + dict.mandatory + ',' + dict.sys_updated_by + ',' + dict.sys_updated_on + ',' + dict.sys_update_name;\n    out_file += key2find +'\\n'\n}\ngs.info(out_file);"
  },
  {
    "path": "Specialized Areas/CMDB/UnsyncCI from Asset/readme.md",
    "content": "Fix script to unsync a Asset from a CI . OOB the field is read only, this allows admin to unsync when required.\n"
  },
  {
    "path": "Specialized Areas/CMDB/UnsyncCI from Asset/script.js",
    "content": "unsyncCI();\n\nfunction unsyncCI() {\n\n    var grAsset = new GlideRecord(\"alm_hardware\");\n    grAsset.addEncodedQuery('sys_id='); //replace query with rrequired list\n    grAsset.query();\n    if (grAsset.next()) {\n        var grCI = new GlideRecord(\"cmdb_ci\");\n        grCI.addQuery(\"sys_id\", grAsset.ci);\n        grCI.setLimit(1);\n        grCI.query();\n        if (grCI.next()) {\n            grCI.asset = '';\n            grCI.setWorkFlow(false);\n            grCI.update();\n        }\n        grAsset.ci = '';\n        grAsset.setWorkFlow(false);\n        grAsset.update();\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Cost Optimization/Instance Cost Analyzer/README.md",
    "content": "# ServiceNow Instance Cost Optimization Analyzer\n\n## Description\nAnalyzes ServiceNow instance usage to identify cost optimization opportunities including unused licenses, redundant integrations, and oversized tables.\n\n## Use Case\n- Identify unused user licenses for cost savings\n- Find redundant or duplicate integrations\n- Locate oversized tables affecting performance and storage costs\n- Generate cost optimization reports for management\n\n## Features\n- **License Analysis**: Finds inactive users with expensive licenses\n- **Integration Audit**: Identifies duplicate or unused REST/SOAP integrations\n- **Storage Analysis**: Locates tables consuming excessive storage\n- **Cost Reporting**: Generates actionable cost-saving recommendations\n\n## Implementation\n\n### 1. Script Include (cost_analyzer.js)\nCreate a Script Include named `CostOptimizationAnalyzer`\n\n### 2. Scheduled Job (scheduled_job.js)\nRun weekly analysis and generate reports\n\n### 3. System Properties\n- `cost.analyzer.license.threshold` = 90 (days of inactivity)\n- `cost.analyzer.table.size.threshold` = 1000000 (records)\n- `cost.analyzer.integration.threshold` = 30 (days unused)\n\n## Output\nReturns JSON object with:\n```json\n{\n  \"unusedLicenses\": [{\"user\": \"john.doe\", \"license\": \"itil\", \"lastLogin\": \"2024-01-15\"}],\n  \"redundantIntegrations\": [{\"name\": \"Duplicate LDAP\", \"type\": \"REST\", \"lastUsed\": \"2024-02-01\"}],\n  \"oversizedTables\": [{\"table\": \"sys_audit\", \"recordCount\": 5000000, \"sizeGB\": 12.5}],\n  \"totalPotentialSavings\": \"$15,000/month\"\n}\n"
  },
  {
    "path": "Specialized Areas/Cost Optimization/Instance Cost Analyzer/cost_analyzer.js",
    "content": "\nvar CostOptimizationAnalyzer = Class.create();\nCostOptimizationAnalyzer.prototype = {\n    \n    analyze: function() {\n        try {\n            var results = {\n                unusedLicenses: this.findUnusedLicenses(),\n                redundantIntegrations: this.findRedundantIntegrations(),\n                oversizedTables: this.findOversizedTables(),\n                analysisDate: new GlideDateTime().getDisplayValue(),\n                totalPotentialSavings: 0\n            };\n            \n            results.totalPotentialSavings = this.calculatePotentialSavings(results);\n            this.logResults(results);\n            return results;\n            \n        } catch (e) {\n            gs.error('Cost Analyzer Error: ' + e.message);\n            return null;\n        }\n    },\n    \n    findUnusedLicenses: function() {\n        var unusedLicenses = [];\n        var threshold = gs.getProperty('cost.analyzer.license.threshold', '90');\n        var cutoffDate = gs.daysAgoStart(parseInt(threshold));\n        \n        var userGr = new GlideRecord('sys_user');\n        userGr.addQuery('active', true);\n        userGr.addQuery('last_login_time', '<', cutoffDate);\n        userGr.addNotNullQuery('last_login_time');\n        userGr.query();\n        \n        while (userGr.next()) {\n            var roles = this.getExpensiveRoles(userGr.sys_id.toString());\n            if (roles.length > 0) {\n                unusedLicenses.push({\n                    user: userGr.user_name.toString(),\n                    name: userGr.name.toString(),\n                    lastLogin: userGr.last_login_time.getDisplayValue(),\n                    expensiveRoles: roles,\n                    estimatedMonthlyCost: roles.length * 100 // Estimate $100 per role\n                });\n            }\n        }\n        \n        return unusedLicenses;\n    },\n    \n    getExpensiveRoles: function(userId) {\n        var expensiveRoles = ['itil', 'itil_admin', 'admin', 'security_admin', 'asset'];\n        var userRoles = [];\n        \n        var roleGr = new GlideRecord('sys_user_has_role');\n        roleGr.addQuery('user', userId);\n        roleGr.query();\n        \n        while (roleGr.next()) {\n            var roleName = roleGr.role.name.toString();\n            if (expensiveRoles.indexOf(roleName) !== -1) {\n                userRoles.push(roleName);\n            }\n        }\n        \n        return userRoles;\n    },\n    \n    findRedundantIntegrations: function() {\n        var redundantIntegrations = [];\n        var threshold = gs.getProperty('cost.analyzer.integration.threshold', '30');\n        var cutoffDate = gs.daysAgoStart(parseInt(threshold));\n        \n        // Check IntegrationHub Spoke usage\n        var unusedSpokes = this.findUnusedSpokes(cutoffDate);\n        redundantIntegrations = redundantIntegrations.concat(unusedSpokes);\n        \n        // Check SOAP Web Services\n        var unusedSoap = this.findUnusedSoapServices(cutoffDate);\n        redundantIntegrations = redundantIntegrations.concat(unusedSoap);\n        \n        // Check for duplicate endpoints (still valuable)\n        var duplicates = this.findDuplicateEndpoints();\n        redundantIntegrations = redundantIntegrations.concat(duplicates);\n        \n        return redundantIntegrations;\n    },\n    \n    findUnusedSpokes: function(cutoffDate) {\n        var unusedSpokes = [];\n        \n        // Get all installed spokes\n        var spokeGr = new GlideRecord('sys_app');\n        spokeGr.addQuery('source', 'sn_app_store');\n        spokeGr.addQuery('name', 'CONTAINS', 'spoke');\n        spokeGr.query();\n        \n        while (spokeGr.next()) {\n            var spokeId = spokeGr.sys_id.toString();\n            var spokeName = spokeGr.name.toString();\n            \n            // Check usage in ua_ih_usage table\n            var usageGr = new GlideRecord('ua_ih_usage');\n            usageGr.addQuery('spoke', spokeId);\n            usageGr.addQuery('sys_created_on', '>=', cutoffDate);\n            usageGr.setLimit(1);\n            usageGr.query();\n            \n            if (!usageGr.hasNext()) {\n                // No recent usage found\n                var lastUsage = this.getLastSpokeUsage(spokeId);\n                unusedSpokes.push({\n                    name: spokeName,\n                    type: 'IntegrationHub Spoke',\n                    spokeId: spokeId,\n                    lastUsed: lastUsage,\n                    status: 'Potentially Unused'\n                });\n            }\n        }\n        \n        return unusedSpokes;\n    },\n    \n    getLastSpokeUsage: function(spokeId) {\n        var usageGr = new GlideRecord('ua_ih_usage');\n        usageGr.addQuery('spoke', spokeId);\n        usageGr.orderByDesc('sys_created_on');\n        usageGr.setLimit(1);\n        usageGr.query();\n        \n        if (usageGr.next()) {\n            return usageGr.sys_created_on.getDisplayValue();\n        }\n        return 'Never used';\n    },\n    \n    findUnusedSoapServices: function(cutoffDate) {\n        var unusedSoap = [];\n        \n        var soapGr = new GlideRecord('sys_web_service');\n        soapGr.query();\n        \n        while (soapGr.next()) {\n            var soapId = soapGr.sys_id.toString();\n            var soapName = soapGr.name.toString();\n            \n            // Check if SOAP service has been used recently\n            var usageCount = this.getSoapUsageCount(soapId, cutoffDate);\n            if (usageCount === 0) {\n                unusedSoap.push({\n                    name: soapName,\n                    type: 'SOAP Web Service',\n                    endpoint: soapGr.endpoint.toString(),\n                    lastUsed: this.getLastSoapUsage(soapId),\n                    status: 'Potentially Unused'\n                });\n            }\n        }\n        \n        return unusedSoap;\n    },\n    \n    getSoapUsageCount: function(soapId, cutoffDate) {\n        var usageGr = new GlideAggregate('sys_soap_log');\n        usageGr.addQuery('web_service', soapId);\n        usageGr.addQuery('sys_created_on', '>=', cutoffDate);\n        usageGr.addAggregate('COUNT');\n        usageGr.query();\n        \n        if (usageGr.next()) {\n            return parseInt(usageGr.getAggregate('COUNT'));\n        }\n        return 0;\n    },\n    \n    getLastSoapUsage: function(soapId) {\n        var logGr = new GlideRecord('sys_soap_log');\n        logGr.addQuery('web_service', soapId);\n        logGr.orderByDesc('sys_created_on');\n        logGr.setLimit(1);\n        logGr.query();\n        \n        if (logGr.next()) {\n            return logGr.sys_created_on.getDisplayValue();\n        }\n        return 'Never used';\n    },\n    \n    findDuplicateEndpoints: function() {\n        var duplicates = [];\n        var endpoints = {};\n        \n        var restGr = new GlideRecord('sys_rest_message');\n        restGr.query();\n        \n        while (restGr.next()) {\n            var endpoint = restGr.endpoint.toString();\n            if (endpoints[endpoint]) {\n                duplicates.push({\n                    name: restGr.name.toString(),\n                    type: 'REST',\n                    endpoint: endpoint,\n                    status: 'Duplicate Endpoint',\n                    duplicateOf: endpoints[endpoint]\n                });\n            } else {\n                endpoints[endpoint] = restGr.name.toString();\n            }\n        }\n        \n        return duplicates;\n    },\n    \n    findOversizedTables: function() {\n        var oversizedTables = [];\n        var threshold = gs.getProperty('cost.analyzer.table.size.threshold', '1000000');\n        \n        var tableGr = new GlideRecord('sys_db_object');\n        tableGr.addQuery('name', 'STARTSWITH', 'u_'); // Custom tables\n        tableGr.query();\n        \n        while (tableGr.next()) {\n            var tableName = tableGr.name.toString();\n            var recordCount = this.getTableRecordCount(tableName);\n            \n            if (recordCount > parseInt(threshold)) {\n                var sizeInfo = this.getActualTableSize(tableName, recordCount);\n                oversizedTables.push({\n                    table: tableName,\n                    recordCount: recordCount,\n                    estimatedSizeGB: sizeInfo.sizeGB,\n                    recommendation: sizeInfo.recommendation\n                });\n            }\n        }\n        \n        // Check system tables that commonly grow large\n        var systemTables = ['sys_audit', 'sys_email', 'syslog', 'sys_attachment'];\n        systemTables.forEach(function(tableName) {\n            var recordCount = this.getTableRecordCount(tableName);\n            if (recordCount > parseInt(threshold)) {\n                var sizeInfo = this.getActualTableSize(tableName, recordCount);\n                oversizedTables.push({\n                    table: tableName,\n                    recordCount: recordCount,\n                    estimatedSizeGB: sizeInfo.sizeGB,\n                    recommendation: sizeInfo.recommendation\n                });\n            }\n        }.bind(this));\n        \n        return oversizedTables;\n    },\n    \n    getTableRecordCount: function(tableName) {\n        try {\n            var countGr = new GlideAggregate(tableName);\n            countGr.addAggregate('COUNT');\n            countGr.query();\n            \n            if (countGr.next()) {\n                return parseInt(countGr.getAggregate('COUNT'));\n            }\n        } catch (e) {\n            gs.debug('Cannot count records for table: ' + tableName);\n        }\n        return 0;\n    },\n    \n    getActualTableSize: function(tableName, recordCount) {\n        var sizeGB = 0;\n        \n        // Get actual table size from sys_physical_table_stats\n        var statsGr = new GlideRecord('sys_physical_table_stats');\n        statsGr.addQuery('table_name', tableName);\n        statsGr.orderByDesc('sys_created_on');\n        statsGr.setLimit(1);\n        statsGr.query();\n        \n        if (statsGr.next()) {\n            // Convert bytes to GB\n            var sizeBytes = parseInt(statsGr.size_bytes || 0);\n            sizeGB = sizeBytes / (1024 * 1024 * 1024);\n        } else {\n            // Fallback to estimate if stats not available\n            var avgRecordSize = 2; // KB per record (estimate)\n            sizeGB = (recordCount * avgRecordSize) / (1024 * 1024);\n        }\n        \n        var recommendation = 'Consider archiving old records';\n        if (tableName === 'sys_audit') {\n            recommendation = 'Configure audit retention policy';\n        } else if (tableName === 'sys_email') {\n            recommendation = 'Clean up old email records';\n        } else if (tableName === 'syslog') {\n            recommendation = 'Reduce log retention period';\n        }\n        \n        return {\n            sizeGB: Math.round(sizeGB * 100) / 100,\n            recommendation: recommendation\n        };\n    },\n    \n    calculatePotentialSavings: function(results) {\n        var totalSavings = 0;\n        \n        // License savings\n        results.unusedLicenses.forEach(function(license) {\n            totalSavings += license.estimatedMonthlyCost || 0;\n        });\n        \n        // Storage savings (estimate $10 per GB per month)\n        results.oversizedTables.forEach(function(table) {\n            totalSavings += (table.estimatedSizeGB * 10);\n        });\n        \n        return '$' + totalSavings.toLocaleString() + '/month';\n    },\n    \n    logResults: function(results) {\n        gs.info('=== Cost Optimization Analysis Results ===');\n        gs.info('Unused Licenses Found: ' + results.unusedLicenses.length);\n        gs.info('Redundant Integrations: ' + results.redundantIntegrations.length);\n        gs.info('Oversized Tables: ' + results.oversizedTables.length);\n        gs.info('Potential Monthly Savings: ' + results.totalPotentialSavings);\n        gs.info('========================================');\n    },\n    \n    type: 'CostOptimizationAnalyzer'\n}\n"
  },
  {
    "path": "Specialized Areas/Cost Optimization/Instance Cost Analyzer/scheduled_job.js",
    "content": "// Scheduled Job Script - Run Weekly\n// Name: Weekly Cost Optimization Analysis\n\ntry {\n    var analyzer = new CostOptimizationAnalyzer();\n    var results = analyzer.analyze();\n    \n    if (results) {\n        // Store results in a custom table or send email report\n        gs.info('Cost optimization analysis completed successfully');\n\n        var emailBody = 'Cost Optimization Report:\\n\\n';\n        emailBody += 'Unused Licenses: ' + results.unusedLicenses.length + '\\n';\n        emailBody += 'Redundant Integrations: ' + results.redundantIntegrations.length + '\\n';\n        emailBody += 'Oversized Tables: ' + results.oversizedTables.length + '\\n';\n        emailBody += 'Potential Savings: ' + results.totalPotentialSavings + '\\n';\n        \n        // below line will send to email\n        gs.eventQueue('cost.optimization.report', null, emailBody);\n    }\n    \n} catch (e) {\n    gs.error('Scheduled cost analysis failed: ' + e.message);\n}\n"
  },
  {
    "path": "Specialized Areas/Data Quality/Similarity Calculator/Change Table/README.md",
    "content": "# Similarity Calculator for ServiceNow Change Requests\n\n## Overview\nThis utility provides manual similarity scoring between ServiceNow change request records using text analysis, without requiring machine learning. It helps developers and admins find similar changes by comparing descriptions and calculating similarity scores programmatically.\n\n## How It Works\n1. Extracts keywords from change request descriptions\n2. Compares keyword overlap between change requests\n3. Calculates a similarity score (0-100%)\n4. Finds and ranks similar change requests based on score\n\n## Features\n- Compare change request descriptions using keyword matching\n- Calculate similarity scores between change requests\n- Find and rank similar changes programmatically\n- No ML or Predictive Intelligence required\n\n## Use Cases\n- Manual clustering of change requests\n- Identifying duplicate or related changes\n- Data quality analysis before ML model training\n- Change impact analysis and triage\n\n## Setup Requirements\n- ServiceNow instance with access to the `change_request` table\n- Script execution permissions (Background Script or Script Include)\n- No external dependencies\n\n## Customization\n- Adjust keyword extraction logic for your environment\n- Change scoring algorithm to use TF-IDF, cosine similarity, etc.\n- Filter by assignment group, category, or other fields\n"
  },
  {
    "path": "Specialized Areas/Data Quality/Similarity Calculator/Change Table/similarity_change_calculator.js",
    "content": "// ========================================\n// Similarity Calculator for ServiceNow Changes\n// ========================================\n// Purpose: Manually score similarity between change requests using text analysis\n// No ML required\n// ========================================\n\n(function similarityChangeCalculator() {\n    // --- CONFIG ---\n    var config = {\n        table: 'change_request',\n        baseChangeSysId: 'YOU ID HERE', // Set to the sys_id of the change to compare\n        fields: ['short_description', 'description'],\n        maxResults: 50,\n        minSimilarity: 0 // Minimum similarity % to report\n    };\n\n    // --- Helper: Extract keywords from text ---\n    function extractKeywords(text) {\n        if (!text) return [];\n        // Simple keyword extraction: split, lowercase, remove stopwords\n        var stopwords = ['the','and','a','an','to','of','in','for','on','with','at','by','from','is','it','this','that','as','are','was','were','be','has','have','had','but','or','not','can','will','do','does','did','if','so','then','than','too','very','just','also','into','out','up','down','over','under','again','more','less','most','least','such','no','yes','you','your','our','their','my','me','i'];\n        var words = text.toLowerCase().replace(/[^a-z0-9 ]/g, ' ').split(/\\s+/);\n        var keywords = [];\n        for (var i = 0; i < words.length; i++) {\n            var word = words[i];\n            if (word && stopwords.indexOf(word) === -1 && word.length > 2) {\n                keywords.push(word);\n            }\n        }\n        return keywords;\n    }\n\n    // --- Helper: Calculate similarity score ---\n    function calcSimilarity(keywordsA, keywordsB) {\n        if (!keywordsA.length || !keywordsB.length) return 0;\n        var mapA = {};\n        var mapB = {};\n        for (var i = 0; i < keywordsA.length; i++) {\n            mapA[keywordsA[i]] = true;\n        }\n        for (var j = 0; j < keywordsB.length; j++) {\n            mapB[keywordsB[j]] = true;\n        }\n        var intersection = 0;\n        var unionMap = {};\n        for (var k in mapA) {\n            unionMap[k] = true;\n            if (mapB[k]) intersection++;\n        }\n        for (var l in mapB) {\n            unionMap[l] = true;\n        }\n        var union = Object.keys(unionMap).length;\n        return union ? (intersection / union * 100) : 0;\n    }\n\n    // --- Get base change request ---\n    var baseGr = new GlideRecord(config.table);\n    if (!baseGr.get(config.baseChangeSysId)) {\n        gs.error('Base change request not found: ' + config.baseChangeSysId);\n        return;\n    }\n    var baseText = config.fields.map(function(f) { return baseGr.getValue(f); }).join(' ');\n    var baseKeywords = extractKeywords(baseText);\n\n    // --- Find candidate change requests ---\n    var gr = new GlideRecord(config.table);\n    gr.addQuery('active', true);\n    gr.addQuery('sys_id', '!=', config.baseChangeSysId);\n    gr.setLimit(config.maxResults);\n    gr.query();\n\n    var results = [];\n    while (gr.next()) {\n        var compareText = config.fields.map(function(f) { return gr.getValue(f); }).join(' ');\n        var compareKeywords = extractKeywords(compareText);\n        var score = calcSimilarity(baseKeywords, compareKeywords);\n        results.push({\n            sys_id: gr.getUniqueValue(),\n            number: gr.getValue('number'),\n            short_description: gr.getValue('short_description'),\n            similarity: score\n        });\n    }\n\n    // --- Sort and print results ---\n    results.sort(function(a, b) { return b.similarity - a.similarity; });\n    gs.info('=== Similarity Results ===');\n    for (var i = 0; i < results.length; i++) {\n        var r = results[i];\n        gs.info((i+1) + '. ' + r.number + ' [' + r.sys_id + '] (' + r.similarity.toFixed(1) + '%) - ' + r.short_description);\n    }\n    if (results.length === 0) {\n        gs.info('No similar change requests found above threshold.');\n    }\n})();\n"
  },
  {
    "path": "Specialized Areas/Data Quality/Similarity Calculator/Incident Table/README.md",
    "content": "# Similarity Calculator for ServiceNow Incidents\n\n## Overview\nThis utility provides manual similarity scoring between ServiceNow incident records using text analysis, without requiring machine learning. It helps developers and admins find similar incidents by comparing descriptions and calculating similarity scores programmatically.\n\n## How It Works\n1. Extracts keywords from incident descriptions\n2. Compares keyword overlap between incidents\n3. Calculates a similarity score (0-100%)\n4. Finds and ranks similar incidents based on score\n\n## Features\n- Compare incident descriptions using keyword matching\n- Calculate similarity scores between incidents\n- Find and rank similar incidents programmatically\n- No ML or Predictive Intelligence required\n\n## Use Cases\n- Manual clustering of incidents\n- Identifying duplicate or related tickets\n- Data quality analysis before ML model training\n- Root cause analysis and incident triage\n\n## Setup Requirements\n- ServiceNow instance with access to the `incident` table\n- Script execution permissions (Background Script or Script Include)\n- No external dependencies\n\n## Customization\n- Adjust keyword extraction logic for your environment\n- Change scoring algorithm to use TF-IDF, cosine similarity, etc.\n- Filter by assignment group, category, or other fields\n"
  },
  {
    "path": "Specialized Areas/Data Quality/Similarity Calculator/Incident Table/similarity_incident_calculator.js",
    "content": "// ========================================\n// Similarity Calculator for ServiceNow Incidents\n// ========================================\n// Purpose: Manually score similarity between incidents using text analysis\n// No ML required\n// ========================================\n\n(function similarityCalculator() {\n    // --- CONFIG ---\n    var config = {\n        table: 'incident',\n        baseIncidentSysId: '89b325155370f610de0038e0a0490ec5', // Set to the sys_id of the incident to compare\n        fields: ['short_description', 'description'],\n        maxResults: 50,\n        minSimilarity: 0 // Minimum similarity % to report\n    };\n\n    // --- Helper: Extract keywords from text ---\n    function extractKeywords(text) {\n        if (!text) return [];\n        // Simple keyword extraction: split, lowercase, remove stopwords\n        var stopwords = ['the','and','a','an','to','of','in','for','on','with','at','by','from','is','it','this','that','as','are','was','were','be','has','have','had','but','or','not','can','will','do','does','did','if','so','then','than','too','very','just','also','into','out','up','down','over','under','again','more','less','most','least','such','no','yes','you','your','our','their','my','me','i'];\n        var words = text.toLowerCase().replace(/[^a-z0-9 ]/g, ' ').split(/\\s+/);\n        var keywords = [];\n        for (var i = 0; i < words.length; i++) {\n            var word = words[i];\n            if (word && stopwords.indexOf(word) === -1 && word.length > 2) {\n                keywords.push(word);\n            }\n        }\n        return keywords;\n    }\n\n    // --- Helper: Calculate similarity score ---\n    function calcSimilarity(keywordsA, keywordsB) {\n        if (!keywordsA.length || !keywordsB.length) return 0;\n        var mapA = {};\n        var mapB = {};\n        for (var i = 0; i < keywordsA.length; i++) {\n            mapA[keywordsA[i]] = true;\n        }\n        for (var j = 0; j < keywordsB.length; j++) {\n            mapB[keywordsB[j]] = true;\n        }\n        var intersection = 0;\n        var unionMap = {};\n        for (var k in mapA) {\n            unionMap[k] = true;\n            if (mapB[k]) intersection++;\n        }\n        for (var l in mapB) {\n            unionMap[l] = true;\n        }\n        var union = Object.keys(unionMap).length;\n        return union ? (intersection / union * 100) : 0;\n    }\n\n    // --- Get base incident ---\n    var baseGr = new GlideRecord(config.table);\n    if (!baseGr.get(config.baseIncidentSysId)) {\n        gs.error('Base incident not found: ' + config.baseIncidentSysId);\n        return;\n    }\n    var baseText = config.fields.map(function(f) { return baseGr.getValue(f); }).join(' ');\n    var baseKeywords = extractKeywords(baseText);\n\n    // --- Find candidate incidents ---\n    var gr = new GlideRecord(config.table);\n    gr.addQuery('active', true);\n    gr.addQuery('sys_id', '!=', config.baseIncidentSysId);\n    gr.setLimit(config.maxResults);\n    gr.query();\n\n    var results = [];\n    while (gr.next()) {\n        var compareText = config.fields.map(function(f) { return gr.getValue(f); }).join(' ');\n        var compareKeywords = extractKeywords(compareText);\n        var score = calcSimilarity(baseKeywords, compareKeywords);\n        results.push({\n            sys_id: gr.getUniqueValue(),\n            number: gr.getValue('number'),\n            short_description: gr.getValue('short_description'),\n            similarity: score\n        });\n    }\n\n    // --- Sort and print results ---\n    results.sort(function(a, b) { return b.similarity - a.similarity; });\n    gs.info('=== Similarity Results ===');\n    for (var i = 0; i < results.length; i++) {\n        var r = results[i];\n        gs.info((i+1) + '. ' + r.number + ' (' + r.similarity.toFixed(1) + '%) - ' + r.short_description);\n    }\n    if (results.length === 0) {\n        gs.info('No similar incidents found above threshold.');\n    }\n})();\n"
  },
  {
    "path": "Specialized Areas/Dynamic Filters/getMyDirectReports/README.md",
    "content": "# Summary #\nThis looping script traverses the User table from a certain point to get either one level of employees or all employees in the hierarchy underneath the logged-on user. There are two functions:\n1. **One of My Direct Reports**: gets only users directly reporting to the logged on user\n1. **One of My Reports**: gets all users reporting to the logged on user\n\n> [!WARNING]\n> There is some recursion protection, but the use of this script could have performance impacts for very large organizations. Use at your discretion.\n\n**How to Use**\n* Admins can use the script as a Reference Qualifier\n* Users (with platform access) can select the predefined filter in lists and reports (like with \"One of My Assignments\").\n\n# Solution #\nThis solution has three main components:\n* 1 Client Callable Script Include\n* 2 Dynamic Filters\n\n> [!NOTE]\n> You will also need to create an ACL for your client callable script include.  Think through other security rules before using this filter broadly for users without roles. OOTB Users without roles are often restricted to seeing only their items, for example, a User without itil or snc_incident_read typically cannot see other user's Incidents unless they are the Opened by or are on the Watch list. Make sure you test as the users you hope to publish this item to.\n\n## Script Include (sys_script_include) ##\n\n| Field | Value |\n|---|---|\n| Name | getMyReportsUtil |\n| Client Callable | true |\n| Script | <em>see [getMyReportsUtil.js](getMyReportsUtil.js)</em> |\n\n## Dynamic Filter Option (sys_filter_option_dynamic) ##\n\n### One of My Direct Reports ###\n\n| Field | Value |\n|---|---|\n| Label | One of My Direct Reports |\n| Script | new global.getMyReportsUtil().getMyDirectReports() |\n| Field type | Reference |\n| Reference Table | User [sys_user] |\n| Order | 500 |\n| Roles | Choose the same role(s) you added to the Script Include ACL |\n| Reference script | Script Include: getMyReportsUtil |\n| Available for filter | true |\n| Available for ref qual | true |\n\n**Example Incident list filtered by Caller is One of My Direct Reports**\n![Example Incident list filtered by Caller is One of My Direct Reports](one_of_my_direct_reports.png)\n\n### One of My Reports ###\n\n| Field | Value |\n|---|---|\n| Label | One of My Reports |\n| Script | new global.getMyReportsUtil().getMyReports() |\n| Field type | Reference |\n| Reference Table | User [sys_user] |\n| Order | 600 |\n| Roles | Choose the same role(s) you added to the Script Include ACL |\n| Reference script | Script Include: getMyReportsUtil |\n| Available for filter | true |\n| Available for ref qual | true |\n\n**Example Incident list filtered by Caller is One of My Reports**\n![Example Incident list filtered by Caller is One of My Reports](one_of_my_reports.png)\n\n ### Example Manager Data for the screenshots above ###\n\n![Example Manager Data](example_manager_data.png)\n"
  },
  {
    "path": "Specialized Areas/Dynamic Filters/getMyDirectReports/getMyReportsUtil.js",
    "content": "var getMyReportsUtil = Class.create();\ngetMyReportsUtil.prototype = {\n\n    initialize: function() {\n        this.myReports = []; //array for direct reports sys_ids\n\t\tthis.managerID = gs.getUserID(); //set Manager ID to the logged on User\n    },\n\n    getMyDirectReports: function() {\n        //query the user table for users that report to the specifed Manager\n        var userGr = new GlideRecord(\"sys_user\");\n        userGr.addQuery(\"manager\", this.managerID);\n        userGr.addQuery(\"sys_id\", \"!=\", this.managerID); //recursion protection, exclude the manager\n        userGr.addQuery(\"active\", true); //only active employees\n        userGr.query();\n        while (userGr.next()) {\n\t\t\t//add the User to the Array\n            this.myReports.push(userGr.getUniqueValue());\n        }\n\n        return this.myReports;\n    },\n\n    getMyReports: function() {\n        this._getReportsRecursive();\n\n        return this.myReports;\n    },\n\n    _getReportsRecursive: function() {\n        //query the user table for users that report to the specifed Manager\n        var userGr = new GlideRecord(\"sys_user\");\n        userGr.addQuery(\"manager\", this.managerID);\n        userGr.addQuery(\"sys_id\", \"!=\", this.managerID); //recursion protection 1, exclude the manager\n        userGr.addQuery(\"sys_id\", \"NOT IN\", this.myReports); //recursion protection 2, exclude previously found users\n        userGr.addQuery(\"active\", true); //only active employees\n        userGr.query();\n        while (userGr.next()) {\n\t\t\t//add the User to the Array\n            this.myReports.push(userGr.getUniqueValue());\n            //keep following the org hierarchy until we don't find any more reports\n\t\t\tthis.managerID = userGr.getUniqueValue();\n            this._getReportsRecursive();\n        }\n    },\n\n\n\n    type: 'getMyReportsUtil'\n};\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Add Fields On All List Views/AddFieldsToLists.js",
    "content": "var fieldsToAdd = ['sys_updated_on', 'sys_updated_by', 'sys_created_on', 'sys_created_by'];\nvar userID = gs.getUserID();\nvar defaultLists = getDefaultLists();\n\nfor(var i = 0; i < defaultLists.length; i++){\n    var personalList = getPersonalList(defaultLists[i].name);\n    if(personalList){\n        deleteElements(personalList.getUniqueValue(), fieldsToAdd);\n        var position = getPosition(personalList.getUniqueValue());\n        addElements(personalList.getUniqueValue(), position, fieldsToAdd);   \n    }else{\n        personalList = createPersonalList(defaultLists[i].name);\n        addElements(personalList, 0, defaultLists[i].elements);\n        addElements(personalList, defaultLists[i].elements.length + 1, fieldsToAdd);\n    }\n}\n\nfunction getDefaultLists(){\n    var lists = [];\n    var listGR = new GlideRecord('sys_ui_list');\n    listGR.addQuery('view', 'Default view');\n    listGR.addNullQuery('parent');\n    listGR.query();\n    while(listGR.next()){\n        var list = {};\n        list.sys_id = listGR.getUniqueValue();\n        list.name = listGR.getValue('name');\n        list.elements = getElements(listGR.getUniqueValue(), fieldsToAdd);\n        lists.push(list);\n    }\n    return lists;\n}\n\nfunction getElements(listID, fields){\n    var elements = [];\n    var elementGR = new GlideRecord('sys_ui_list_element');\n    elementGR.addQuery('list_id', listID);\n\telementGR.addQuery('element', 'NOT IN', fields);\n    elementGR.orderBy('position');\n    elementGR.query();\n    while(elementGR.next()){\n        if(fieldsToAdd.indexOf(elementGR.getValue('element') == -1)){\n            elements.push(elementGR.getValue('element'));\n        }\n    }\n    return elements;\n}\n\nfunction getPersonalList(name){\n    var listGR = new GlideRecord('sys_ui_list');\n    listGR.addQuery('name', name);\n    listGR.addQuery('sys_user', userID);\n    listGR.addQuery('view', 'Default view');\n    listGR.query();\n    if(listGR.next()){\n        return listGR;\n    }\n    else{return null;}\n}\n\nfunction deleteElements(listID, elementsToDelete){\n    var elementGR = new GlideRecord('sys_ui_list_element');\n    elementGR.addQuery('list_id', listID);\n    elementGR.addQuery('element', 'IN', elementsToDelete);\n    elementGR.query();\n    while(elementGR.next()){\n        elementGR.deleteRecord();\n    }\n}\n\nfunction getPosition(listID){\n    var elementGR = new GlideRecord('sys_ui_list_element');\n    elementGR.addQuery('list_id', listID);\n    elementGR.orderByDesc('position');\n    elementGR.query();\n    if(elementGR.next()){\n        return elementGR.getValue('position');\n    }\n}\n\nfunction addElements(listID, position, elements){\n    var elementGR = new GlideRecord('sys_ui_list_element');\n    for(var i = 0; i < elements.length; i++){\n        elementGR.initialize();\n        elementGR.setValue('position', position);\n        elementGR.setValue('list_id', listID);\n        elementGR.setValue('element', elements[i]);\n        elementGR.insert();\n        position++;\n    }\n}\n\nfunction createPersonalList(listName){\n    var listGR = new GlideRecord('sys_ui_list');\n    listGR.initialize();\n    listGR.setValue('name', listName);\n    listGR.setValue('sys_user', userID);\n    listGR.setValue('view', 'Default view');\n    var list = listGR.insert();\n    return list;\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Add Fields On All List Views/README.md",
    "content": "# Add Fields On All List Views\n\nThis will add Updated On, Updated By, Created on, and Created by to every list view for the current user. You can add or remove fields to fieldsToAdd if you want others added as well\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Add Variable set to multiple catalog items/README.md",
    "content": "### Add variable set to multiple catalog items through script\n\nUse this fix script to associate new variable set to few/all catalog items through script.\nUse sys_ids of all catalog items and Variable set in the script.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Add Variable set to multiple catalog items/script.js",
    "content": "var itemIds = ['sys_id1','sys_id2','sys_id3']; // comma separated sys_id's of catalog items\nfor(var i=0; i<itemIds.length; i++){\n  addDefaultVariableSet(itemIds[i],'sys_id of Variable set here');\n}\nfunction addDefaultVariableSet(catItemSysId,VarSetID) {\nvar varset = new sn_sc.CatalogItemVariableSetM2M();\n//prepare object of columns name to Value of table io_set_item\nvar attr = {\n\"variable_set\": VarSetID, //mandatory attribute\n\"sc_cat_item\": catItemSysId, //mandatory attribute\n\"order\": 100 // optional\n};\nvarset.setAttributes(attr); // user setAttributes\nvar m2mRec = varset.create(true);\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Add bulk users to VTB/README.md",
    "content": "For the teams who are using large number of VTBs on a daily basis based on multiple categories, for them adding multiple users to a Visual Task Board (VTB) manually is a tedious task.\nTo solve this, we can use a Fix Script to automate the process. \nVisual Task Boards are stored in the vtb_board table, and board members are linked through the vtb_board_member table. \nThis script will allow us to add multiple users to a specific board by querying and inserting them as board members.\n\nExplanation\n- Set the boardSysId variable to the sys_id of the Visual Task Board to which you want to add users.\n- Add the sys_id values of the users you want to add to the board in the userSysIds array.\n- The script checks if the board with the specified boardSysId exists in the vtb_board table. If it doesn’t, it will display an error message.\n- After adding all users, the script displays a message with the total number of users added to the board.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Add bulk users to VTB/addBulkUsersToVTB.js",
    "content": "// Replace with the sys_id of the Visual Task Board you want to add users to\nvar boardSysId = 'sys_id_of_board';\n\n// Replace with sys_ids of different users to be added to VTB\nvar userSysIds = ['user_1','user_2','user_3'];\n\nvar board = new GlideRecord('vtb_board');\nif (!board.get(boardSysId)) \n{\n    gs.addErrorMessage(\"This Visual Task Board does not exist.\");\n} \n  \nelse \n{\nvar addedCount = 0;\nuserSysIds.forEach(function(userId) \n{\n        var boardMember = new GlideRecord('vtb_board_member');\n        boardMember.initialize();\n        boardMember.board = boardSysId;\n        boardMember.user = userId;\n        boardMember.insert();\n        addedCount++;\n});\ngs.addInfoMessage(addedCount + \" users have been successfully added to your Visual Task Board: \" + board.getValue('name'));\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Adjust Variable Order on Catalog Item/README.md",
    "content": "# Adjust Variable Order on Catalog Item\n\nThis Fix Script helps developers to automatically re-order all variables of a given Catalog Item or Variable Set.  \nWe all know the struggle, that initially we set up variables with fixed order steps such as: 100, 200, 300, 400    \nHowever, over time, additional variables are inserted in between, resulting in a messy looking order, e.g.: 10, 15, 50, 55, 100, 200, 350, 300, 400   \nThis fix script should enable developers to keep their Catalog Items clean and structured, with minimal effort.   \n\n### Instruction\n\nAt the beginning of the script you have to set the Sys ID of the Catalog Item or Variable Set where you want to re-order the variables.\nThe Script is built in a way, that this Sys ID can belong to either a Variable Set or Catalog Item.\nFurthermore, you have to define the step size for the order. My recommendation would be to use 100.\n \n```javascript\nvar sys_id = \"e0ecd03947e29110d3c0c789826d4332\"; //provide a catalog item or variable set sys id\nvar step_size = 100; //provide the step size for the new order\n```\n\n### Benefits\n\n- Keep Catalog Items and Variable Sets clean and structured\n- Reduce efforts maintaining variable orders\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Adjust Variable Order on Catalog Item/script.js",
    "content": "\n//set the following variables before running the script\nvar sys_id = \"e0ecd03947e29110d3c0c789826d4332\"; //provide a catalog item or variable set sys id\nvar step_size = 100; //provide the step size for the new order\n\n\nif (sys_id) { //avoid updating all records with empty fields for catalog item or variable set\n    var variables = new GlideRecord(\"item_option_new\");\n\tvariables.addQuery(\"cat_item\", sys_id).addOrCondition('variable_set', sys_id);\n    variables.orderBy(\"order\");\n    variables.query();\n\n    if (variables.hasNext()) {\n\t\tvar i =0;\n        while (variables.next()) {\n            variables.order.setValue(i*step_size);\n            variables.update();\n\t\t\ti++;\n        }\n\t\tgs.log(\"The order of \" + i + \" variables has been changed, ranging now from 0 to \" + (i-1)*step_size);\n    }\n\telse{\n\t\tgs.log(\"No Variables were found for the provided catalog item or variable set sys id\");\n\t}\n\n} else {\n    gs.log(\"Please provide a catalog item or variable set sys id in line 1\");\n}\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Anonymise Data/README.md",
    "content": "**Script Include** \n\nScript for anonymising data in specified table and chosen fields. You can pass an additional query to limit records which will be cleared. It can be used for example to remove GDPR data from development instances or anonymise old records. It can be used for example in fix script or scheduled job for long-term cleaning. \n\n**How to execute**\n\n```javascript\n//Table name which should be cleared\nvar table = 'sys_user';\n\n//List of fields from table specified before\nvar fieldList = ['first_name', 'last_name'];\n\n//Additional query\nvar query = 'user_nameSTARTSWITHTEST';\n\nvar anonymise = new AnonymiseData();\nanonymise.anonymiseTable(table, fieldList, query, true);\n```\n\nYou need to pass 4 parameters to function anonymiseTable\n\n1. tablename - Name of table to be cleared ex. 'sys_user'\n2. fieldList - Array of fields name, which should be cleared ex. ['first_name', 'last_name']\n3. additionalQuery - Additional encoded query to limit list of records (if you would like to clear whole table just pass empty string)\n4. logging - True/False value to determine if logging should be performed during execution\n\nExample of Fix script execution:\n ![Coniguration](ScreenShot_2.PNG)\n\n**Example configuration of Script Include** \n\n ![Coniguration](ScreenShot_1.PNG)\n \n**Execution logs**\n\n ![Coniguration](ScreenShot_3.PNG)\n\n**Effect of execution**\n\n ![Coniguration](ScreenShot_4.PNG)\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Anonymise Data/script.js",
    "content": "var AnonymiseData = Class.create();\nAnonymiseData.prototype = {\n    initialize: function() {},\n\n    //Generate UUID which will be placed instead of real data\n    generateUUID: function() {\n        return gs.generateGUID();\n    },\n\n    //Function to anonymise data of one record and specified field list\n    //record - Gliderecord object of record to be cleared\n    //fieldList - Array of fields name, which should be cleared ex. ['first_name', 'last_name']\n    anonymiseRecord: function(record, fieldList) {\n        for (index in fieldList) {\n            if (!gs.nil(record.getValue(fieldList[index]))) {\n                record.setValue(fieldList[index], this.generateUUID());\n            }\n        }\n        record.update();\n    },\n\n    //Function to anonymise table with specified additional query and list of fields\n    //tablename - Name of table to be cleared ex. 'sys_user'\n    //fieldList - Array of fields name, which should be cleared ex. ['first_name', 'last_name']\n    //additionalQuery - Additional encoded query to limit list of records (if you would like to clear whole table just pass empty string)\n    //logging - True/False value to determine if logging should be executed during execution\n    anonymiseTable: function(tablename, fieldList, additionalQuery, logging) {\n\n        if (logging)\n            gs.info('[AnonymiseData] - Starting clearing data on table: ' + tablename + ' for fields: ' + fieldList + ' and addiotonal query: ' + additionalQuery);\n\n        var gr = new GlideRecord(tablename);\n        if (!gs.nil(additionalQuery)) {\n            gr.addEncodedQuery(additionalQuery);\n        }\n        gr.query();\n\n        while (gr.next()) {\n            this.anonymiseRecord(gr, fieldList);\n        }\n\n        if (logging)\n            gs.info('[AnonymiseData] - Execution finished, cleared: ' + gr.getRowCount() + ' records.');\n    },\n\n    type: 'AnonymiseData'\n};\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Assign user list to a specific group/README.md",
    "content": "**Fix Script**\n\nScript to *automatically assign a list of users to a specific group*; you can change the user query to prepare a correct list of users according to your needs. To assign a different group, change the value of the GROUP_ID variable to the sys_id of the group which you would like to assign. \n\n**Example configuration of Fix Script**\n\n![Configuration](ScreenShot_1.PNG)\n\n**Example execution logs**\n\n![Logs](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Assign user list to a specific group/script.js",
    "content": "    //Fix script to automatically assign a list of users to a specific group\n\n    //Sys_id value of the selected group to be assigned to users (in this example it is the 'help desk' group)\n    var GROUP_ID = '679434f053231300e321ddeeff7b12d8';\n\n    //Array to store a list of all updated users for logging purposes\n    var updatedUserList = [];\n\n    //Query to get the list of all users getting the new group\n    var grUserList = new GlideRecord('sys_user');\n    grUserList.addQuery('department', '5d7f17f03710200044e0bfc8bcbe5d43'); //Customer support department \n    grUserList.addActiveQuery();\n    grUserList.query();\n\n    while (grUserList.next()) {\n\n        //Assigning group to user\n        var grGroup = new GlideRecord('sys_user_grmember');\n        grGroup.initialize();\n        grGroup.user = grUserList.sys_id;\n        grGroup.group = GROUP_ID;\n        grGroup.insert();\n\n        //Pushing user sys_id to array\n        updatedUserList.push(grUserList.sys_id.toString());\n    }\n\n    //Logging details of Fix Script execution\n    gs.info('[Fix Script] - Assigned group: ' + GROUP_ID + ' to ' + grUserList.getRowCount() + ' users.');\n    gs.info('[Fix Script] - Users list: ' + updatedUserList.join(', '));\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Authenticate using ScriptedRESTAPI/README.md",
    "content": "This fix script is created to make a post call using scripted REST API when header type is 'application/x-www-form-urlencoded' to get the token and authenticate\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Authenticate using ScriptedRESTAPI/script.js",
    "content": "/*\nThis fix script is created to make a post call using scripted REST API when header type is 'application/x-www-form-urlencoded' to get the token and authenticate\n*/\n\n(function execute(inputs, outputs) {\nvar body = \"grant_type=credentials&username=<UserName>-snow&password=<Password>\";\n\ntry { \nvar r = new sn_ws.RESTMessageV2();\nr.setEndpoint(\"http:<API Endpoint Details>/api/jwt/login\"); \n//r.setRequestHeader(\"Accept\", \"application/json\");\nr.setRequestHeader(\"Content-Type\", \"application/x-www-form-urlencoded\");\nr.setRequestBody(body);\nr.setMIDServer('<Mid Server Link>'); // Enter your Mid Server\nr.setHttpMethod('POST');\n\nvar response = r.execute();\nr.setHttpTimeout(30000); // Set Time Out \nvar responseBody = response.getBody();\nvar httpStatus = response.getStatusCode(); // Get Status code to determine success or Faliure\n    gs.info(\"Status >>>>\"+httpStatus);\n    gs.info(\"responseBody >>>>\"+responseBody);\n    outputs.token_value = responseBody;\n  outputs.status = httpStatus;\n}\ncatch(ex) {\nvar message = ex.message;\n}\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/AutoNumberIssueFix/README.md",
    "content": "# The Script is for correcting auto number issues\n# This Script should be run in global scope using scripts background\n# Ensure you update the required tableName per your requirement\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/AutoNumberIssueFix/autonumberingcode.js",
    "content": "// Fix script for autonumbering issues\n// This script will be run in global using scripts Background\n// Update the tableName per requirement\n\nvar nm = new NumberManager(tableName);\n\nvar query = ''; //add query string\n\nvar grTableName = new GlideRecord(tableName); // glideRecord Table Name ex. Incident\n\ngrTableName.addQuery(query);\n\ngrTableName.orderBy('sys_created_on');\n\ngrTableName.query();\n\nwhile (grTableName.next()) {\n\tgrTableName.number = nm.getNextObjNumberPadded();\n\tgrTableName.update();\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Bulk Update Catalog Item Images/README.md",
    "content": "# Bulk Catalog Item Image Change\n\nUpdates the image(s) associated with catalog items or record producers\n\n## Description\n\nThis script can perform bulk updates of the picture and/or icon fields used in catalog items and record producers. The script will update each item that is returned in the query with the images set\nin the iconSysId and pictureSysId variables. A business use for this script would be the need to update the picture and icon fields of all Apple related catalog items with an new Apple logo image.\n\n## Getting Started\n\n### Dependencies\n\n* Must be in the Global scope.\n* The image(s) being used must already exist in the db_image table.\n\n### Execution\n\n1. Copy the script from bulk-update-cat-item-images.js to either a fix script or background script in your ServiceNow instance.\n2. Copy the sys_id(s) from the image that you will using from the sys_attachments table. You can use the same image for both fields or different images, it's up to you. If you have added this image using the db_image table, you can filter the sys_attachments table name column to ZZ_YYdb_image to make it easier to find.\n3. Update the script variables as follows:\n    * **table**: the table you want to change the icons for the catalog item(s) - commonly sc_cat_item or sc_cat_item_producer\n    * **iconSysId**: the sys_id of the image to be used for the icon (NOTE - you can comment this line out if you don't want to update the icon)\n    * **pictureSysId**: the sys_id of the image to be used for the picture (NOTE - you can comment this line out if you don't want to update the picture)\n    * **query**: the encoded query you will use to identify the items to be updated\n4. Run the script and the picture and/or icon fields will be updated\n\n## Authors\n\nBrad Warman\n\nhttps://www.servicenow.com/community/user/viewprofilepage/user-id/80167\n\n## Version History\n\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Bulk Update Catalog Item Images/bulk-cat-item-image-update.js",
    "content": "var table = 'sc_cat_item';\nvar iconSysId = 'f1dd1def1ba9fd1086a098e7b04bcb81'; // sys_id of icon file\nvar pictureSysId = 'f1dd1def1ba9fd1086a098e7b04bcb81'; // sys_id of picture file\nvar query = 'category=6a6ab100dbd5cc10ee115d87f49619fb^active!=true'; // query used to identify items\n\nvar grItem = new GlideRecord(table);\ngrItem.addEncodedQuery(query);\ngrItem.query();\nwhile (grItem.next()) {\n    createImage(pictureSysId, 'picture', table, grItem.sys_id);\n    createImage(iconSysId, 'icon', table, grItem.sys_id);\n}\n\nfunction createImage(attachmentID, fieldName, tableName, tableID) {\n\n    var attachmentGR = new GlideRecord('sys_attachment');\n    attachmentGR.get(attachmentID);\n    var fields = attachmentGR.getFields();\n    var imageGR = new GlideRecord('sys_attachment');\n    imageGR.initialize();\n    imageGR.compressed = attachmentGR.compressed;\n    imageGR.content_type = 'image/png';\n    imageGR.size_bites = attachmentGR.size_bites;\n    imageGR.size_compressed = attachmentGR.size_compressed;\n    imageGR.file_name = fieldName;\n    imageGR.table_name = 'ZZ_YY' + tableName;\n    imageGR.table_sys_id = tableID;\n    imageGR.state = '2';\n    var imageID = imageGR.insert();\n\n    copyAttachmentContent(attachmentID, imageID);\n\n}\n\n/*\n oldID: sys_id of existing attachment\n newID: sys_id of newly created attachment\n */\nfunction copyAttachmentContent(oldID, newID) {\n    var oldGR = new GlideRecord('sys_attachment_doc');\n    oldGR.addQuery('sys_attachment', oldID);\n    oldGR.query();\n    while (oldGR.next()) {\n        var newGR = new GlideRecord('sys_attachment_doc');\n        newGR.initialize();\n        newGR.data = oldGR.data;\n        newGR.length = oldGR.length;\n        newGR.position = oldGR.position;\n        newGR.sys_attachment = newID;\n        newGR.insert();\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Calculate Business Duration/README.md",
    "content": "# Calculate Business Duration\n\nUse this script to update the business duration field for records.\n\n## Description\n\nUpdates the business duration field for tables such as incident and sc_req_item. The business duration can be determined by using a schedule if required.\n\n## Getting Started\n\n### Dependencies\n\n* This script will only work in the Global scope.\n\n### Execution\n\n* Copy the script from calculate-business-duration.js to either a background script or a fix script.\n* If using a schedule, add the sys_id of the required schedule to the selectedSchedule variable.\n* Set the table using the table variable.\n* Set the encoded query to obtain the records you would like to update. The preconfigured query includes records from January 1, 2022 where the state is either closed or cancelled.\n* Run the script.\n\n## Author\n\nBrad Warman\n\nhttps://www.servicenow.com/community/user/viewprofilepage/user-id/80167\n\n## Version History\n\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Calculate Business Duration/calculate-business-duration.js",
    "content": "// This script will retrospectively calculate the business duration of records and update the business_duration field with the correct value.\n\nvar selectedSchedule = ''; // Set the sys_id of the schedule you'd like to use to calculate duration\ntable = 'sc_req_item'; // Change this to set a different table such as incident\n\nvar gr = new GlideRecord(table);\ngr.addEncodedQuery(\"stateIN3,4^sys_created_on>javascript:gs.dateGenerate('2022-01-01','00:00:01')\"); // Set your encoded query to whatever you would like\ngr.query();\nvar count = 0;\nwhile (gr.next()) {\n    var startDate = new GlideDateTime(gr.sys_created_on);\n    var endDate = new GlideDateTime(gr.closed_at);\n    var schedule = new GlideSchedule();\n    schedule.load(selectedSchedule); \n    var duration = schedule.duration(startDate, endDate);\n    gr.setValue('business_duration', duration);\n    var opened = gr.sys_created_on.getDisplayValue();\n    var resolved = gr.closed_at.getDisplayValue();\n    gr.setValue('calendar_duration', gs.dateDiff(opened, resolved, false));\n    gr.setWorkflow('false'); // Set to true if you want workflows to run\n    gr.autoSysFields('false'); // Set to true if you want system fields to be updated\n    gr.update();\n    count = count + 1;\n}\ngs.info(count + \" records updated with new business duration value\");\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Cancel Struck Slack Conversations/README.md",
    "content": "**Use Case**\n1. Sometimes due to network or connecton issue, users are not able to end the conversations using **restart** or **end** command from slack.\n2. This script will cancel the struck slack conversations, allowing the users to interaction with ServiceNow again through slack.\n3. The encoded Query device type specify the source of conversation.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Cancel Struck Slack Conversations/script.js",
    "content": "/*\nThis script will mark the struck slack conversation as canceled.\nUse Case: Sometimes due to n/w  or connecton issue, users are not able to end the conversations using restart or end command from slack.\nDevice Type in Encoded Query specify the source of conversation, slack in this case.\n*/\nvar struckConv = new GlideRecord('sys_cs_conversation'); // conversation table glide record\nstruckConv.addEncodedQuery('stateINopen,faulted,chatInProgress^device_type=slack'); // Query can be enhanced based on user (consumer) or date range.\nstruckConv.query();\nwhile(struckConv.next()){\n\tstruckConv.setValue('state','canceled'); // set the state to cancel for struck conversation.\n\tstruckConv.setWorkflow(false);\n\tstruckConv.update();\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Cancel Workflow/Cancel Workflow Context.js",
    "content": "/**\n* Description : Cancel the workflow context attached to a record\n* @RecordSysId : SysId of the record for which workflow needs to be cancelled\n* @RecordTable : Class of the record for which workflow need to be cancelled\n* @WorkflowVersion : Workflow version\n**/\n\nvar recordSysId = '<RecordSysId>'; // Change request Sys_id\n\nvar grRecord = new GlideRecord('<RecordTable>');\ngrRecord.get(recordSysId);\n\nvar grWorkflowContext = new GlideRecord('wf_context'); //Get the context of the workflow for given change request\ngrWorkflowContext.addQuery('id',grRecord.getUniqueValue());\ngrWorkflowContext.addQuery('workflow_version','<WorkflowVersion>'); //Query with the active workflow version\ngrWorkflowContext.query();\nif (grWorkflowContext.next()) {\n\n\tvar workflow = new workflow(); // Initiate the Workflow API\n\tworkflow.cancelContext(grWorkflowContext); // Call function to cancel the current workflow context\n\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Cancel Workflow/README.md",
    "content": "Script to Cancel the workflow context attached to a record\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Cancel in progress flow executions using flow name/README.md",
    "content": "The script in Fix scripts > Cancel in progress flow executions using flow name > script.js can be used to cancel the dlow executions using the name of the flow you have provided.\n\nThis can be used to cancel huge amount of flow executions created for testing.\n\nThis can also be used to cancel all the defective flow executions in one go.\n\nReplace \"NAME OF FLOW TO CANCEL HERE\" with the name of the flow which you need to cancel\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Cancel in progress flow executions using flow name/script.js",
    "content": "var gr = new GlideRecord(\"sys_flow_context\"); \ngr.addQuery(\"name\", \"NAME OF FLOW TO CANCEL HERE\"); //Replace \"NAME OF FLOW TO CANCEL HERE\" with the name of the flow which you need to cancel\ngr.query(); \nwhile (gr.next()) { \nsn_fd.FlowAPI.cancel(gr.getUniqueValue(), 'Canceling In progress Flows'); \n} \n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Check if Fiscal Year is safe for deletion/Check if Fiscal Year is safe for deletion",
    "content": "// Enter in the display name of the Fiscal Year such as \"FY26\" or \"FY32\" to validate.\nvar fiscalYear = \"FY24\";\n\nvar fiscalGr = new GlideRecord(\"fiscal_period\")\nfiscalGr.addEncodedQuery(\"name=\" + fiscalYear);\nfiscalGr.query();\n\nif (fiscalGr.next()) {\n\n  var fyId = fiscalGr.getUniqueValue();\n  var isValidDeletion = true;\n  var tableDetections = [];\n\n  var grDictionary = new GlideRecord(\"sys_dictionary\");\n  grDictionary.addEncodedQuery(\"reference=fiscal_period\");\n  grDictionary.query();\n  while(grDictionary.next()) {\n\n    if (grDictionary.getValue(\"name\") == \"fiscal_period\")\n      continue;\n\n    var tableGr = new GlideRecord(grDictionary.getValue(\"name\"));\n    tableGr.addEncodedQuery(grDictionary.getValue(\"element\") + \".fiscal_year=\" + fyId);\n    tableGr.setLimit(1);\n    tableGr.query();\n    if (tableGr.next()) {\n      isValidDeletion = false;\n      if (tableDetections.indexOf(grDictionary.getValue(\"name\")) == -1) {\n        tableDetections.push(grDictionary.getValue(\"name\"));\n      }\n    }\n\n  }\n\n  if (isValidDeletion) {\n    gs.info(\"No Fiscal Period data found for this year.\");\n    gs.info(\"High chance it should be safe unless data exists and is blocked from a query BR or other access controls.\");\n    gs.info(\"Confirm with customer before proceeding with any deletions for testing.\");\n  } else {\n    gs.info(\"DO NOT DELETE FISCAL PERIODS FOR \" + fiscalYear + \"!\");\n    gs.info(\"Deletion will cause broken references and corruption.\");\n    gs.info(\"Fiscal data is used in the following tables: \" + tableDetections.join(\" | \"));\n  }\n\n} else {\n  gs.info(\"Fiscal Period does not exist, make sure the Name entered is a valid Fiscal Period\")\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Check if Fiscal Year is safe for deletion/README.md",
    "content": "You can do a lot of damage related to data corruption and impacted financials if you delete your Fiscal Periods/Fiscal Year when it's already in use with live data like Cost Plans and Benefit Plans. \n\nThis will validate a single Fiscal Year to validate it's not used yet and safe for deletion if your Fiscal Periods have Validation issues.\n\nUse this in the [System Definition > Scripts - Background] module. Enter in the Fiscal Year by display name on line 2.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Clean update set/README.md",
    "content": "**Fix script**\n\nFix Script for cleaning update set from customer updates made by a selected developer. Script can be adjusted to match different query for cleaning which fits your needs.\n\nCleaning customer updates from update set is not removing updates made in system on direct records! It is just removing customer updates from update set to not move it to forward environments. \n\n*******\nEnhancement - 8th october 2025\nThis scrip is an enhancement to existing script and will look for the default update set(same application) and move the customer update to default update set.\nDeletion is not recommended way so moving to default is a better option.\n*******\n**Example configuration of Fix Script**\n\n![Coniguration](ScreenShot_3.PNG)\n\n**Example execution logs**\n\n![Logs](ScreenShot_4.PNG)\n\n**Example effect of execution**\n\nBefore execution:\n![Before](ScreenShot_1.PNG)\n\nAfter execution:\n![After](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Clean update set/script.js",
    "content": "//Fix Script for cleaning an update set of customer updates made by a selected developer\n\n//Sys_id value of the selected update set\nvar UPDATE_SET_ID = 'fa6ec9475367e6104834d2a0a0490e63';\n\n//Value of the selected developer (sys_user)\nvar DEVELOPER = 'admin';\n\n//Query to get list of all updates in the selected update set made by the selected developer\nvar grCustomerUpdates = new GlideRecord('sys_update_xml');\ngrCustomerUpdates.addQuery('update_set', UPDATE_SET_ID);\ngrCustomerUpdates.addQuery('sys_created_by', DEVELOPER);\ngrCustomerUpdates.query();\nwhile (grCustomerUpdates.next()) {\n    // get scope default update set\n    grCustomerUpdates.setValue('update_set', getDefaultUpdateSet(grCustomerUpdates.getValue('application')).sys_id); // Move the customer update to default update set\n    grCustomerUpdates.update();\n    gs.info('[Fix Script] - Moving: ' + grCustomerUpdates.getRowCount() + ' customer updates made by: ' + DEVELOPER + ' in update set: ' + UPDATE_SET_ID + ' to ' + getDefaultUpdateSet(grCustomerUpdates.getValue('application')).name);\n}\n\n/*\nFunction to get Default update set (application based)\ninput : application , type = glideRecord Object\noutput : Default update set, type = glideRecord Object\n*/\n\nfunction getDefaultUpdateSet(application) {\n    var updateSet = new GlideRecord('sys_update_set');\n    updateSet.addEncodedQuery('application=' + application + '^name=Default');\n    updateSet.query();\n    if (updateSet.next())\n        return updateSet;\n    else\n        gs.info(\"Default update set not found\");\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Copy favourite to other users/README.md",
    "content": "**Enhancement**\n1. This code will create the sp favorites of the selected users along with the sys_ui_bookmarks.\n2. The entry will be made in \"sp_favorite\" through new **createPortalFav** function.\n\nYou can use this script to take an existing favaourite from the sys_ui_bookmark table and create a copy of it for any number of users.\nCan be useful when onboarding new staff, doing testing, etc.\n\nYou will need two things to get started:\n* the sys_id of the original favourite you want to copy - take this from sys_ui_bookmark table\n* an ecoded query string of a filtered list of users that you want to copy the favourite to\n\nRun the script wherever you like, background script, Xplore, or as fix script.\nHave fun!\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Copy favourite to other users/favCopy.js",
    "content": "var srcFav = \"\"; // Add the sys_id of the favourite to copy, from sys_ui_bookmark table\nvar portalFav = \"\"; // Add the sys_id of the favourite to copy, from sp_favorite table.\nvar userCriteria = \"\"; // Add an encoded query of users from sys_user table\nvar fav = new GlideRecord(\"sys_ui_bookmark\");\nfav.get(srcFav);\n\nvar portalFavRec = new GlideRecord(\"sp_favorite\");\nportalFavRec.get(portalFav);  // glide record of favorite record.\n\nvar users = new GlideRecord(\"sys_user\");\nusers.addEncodedQuery(userCriteria);\nusers.query();\nwhile(users.next()) {\n  var newFav = new GlideRecord(\"sys_ui_bookmark\");\n  newFav.initialize();\n  newFav.setValue(\"color\",fav.color);\n  newFav.setValue(\"icon\",fav.icon);\n  newFav.setValue(\"order\",999999999); //this might need to be changed per individual requirements, otherwise this value should probably put it to the bottom of the list\n  newFav.setValue(\"pinned\",true);\n  newFav.setValue(\"title\",fav.title);\n  newFav.setValue(\"url\",fav.url);\n  newFav.setValue(\"user\",users.sys_id);\n  newFav.insert();\n\n  createPortalFav(users); // function to create portal favorites\n}\nfunction createPortalFav(userRec){\n  var newPortalFav = new GlideRecord(\"sp_favorite\");\n  newPortalFav.initialize();\n  newPortalFav.setValue('user',userRec.user);\n  newPortalFav.setValue('reference_table',portalFavRec.reference_table);\n  newPortalFav.setValue('reference_document',portalFavRec.reference_document);\n  newPortalFav.setWorkflow(false); // OOB Sscript include take the logged-in user to check the duplicate records so workflow false restricts the SI to run.\n  newPortalFav.insert();\n}\n\n\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/CreateMultipleRecords/Add Manager to Group",
    "content": "/*\nCheck if mamager is part of group.\nAdd Manager to group.\n*/\nvar memCheck = new GlideRecord('sys_user_group'); // Glide group Table\nmemCheck.addEncodedQuery('active=true^managerISNOTEMPTY'); // target only active groups with valid managers\nmemCheck.query();\nwhile (memCheck.next()) {\n    if (!memCheck.getRefRecord('manager').isMemberOf(memCheck.getUniqueValue())) //check if manager is member of group\n        addManagerToGrp(memCheck.getValue('manager'), memCheck.getUniqueValue()); // call function to add mamager to group\n}\n/*\ninput: managerVale type: sys_id, groupVal, type : sys_id\nFunction will add manager as group member.\n*/\nfunction addManagerToGrp(managerVal, groupVal) {\n    var grpInsert = new GlideRecord('sys_user_grmember'); // Glide group member table\n    grpInsert.user = managerVal;\n    grpInsert.group = groupVal;\n    grpInsert.insert(); // add user to group\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/CreateMultipleRecords/README.md",
    "content": "***********\nThis Script Will Check If The Manager Is Member Of The Group. \nIf The Manager Is Not Member, It Will Add The Manager To The Group. \nThis Script Will Only Target Active Groups With Non Empty Manager Value. \n***********\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/CreateMultipleRecords/create_multiple_records.js",
    "content": "var count = 0; \nvar number = NUMBER_OF_RECORDS; //Enter the number of records to be created here\n\n\nwhile (count< number) {\nvar gRec = new GlideRecord('NAME_OF_THE_TABLE'); //Enter the Glide Record Table name here\ngRec.initialize(); //Create an empty glide record or use gRec.newRecord();// create records with default values\n//gRec.<fieldName> = \"\"; //Optinal set Field values \ngRec.insert(); // insert the glide record into the table.\ncount++; // continue till number of record is met.\n}\n\n\ngs.print(number  + \"records inserted\");\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/De-Provision Admin user (configurable)/README.md",
    "content": "Objective:\n\nThe purpose of this standalone fix script is to routinize the common sysadmin task of stripping admin access from an account. The de-provisioning process is going to be different for every organization, but it is often useful to have a repeatable, predictable technical solution for literally stripping an account of its rights in the event of an organizational change or security incident.\n\nNote that fix scripts are typically intended to be associated with things that need to be done after an application is installed or upgraded, and it might not be considered 'best practice' to use a fix script for a one-time administrative task such as this, particularly one that is not really very difficult to perform by way of form action. Again, the goal for storing a script object like this on the instance is to have a repeatable, easily examined technical solution for completing the task, but if that is a desired thing to have it might be better to migrate this code into a UI Action associated with the Users table. Having it as a fix script is simply a convenient, low-impact solution.\n\nUsage:\n\nAs written the script is designed to be updated once for each use. Modify the variables at the top of the script to meet the needs of the current de-provisioning event. Clicking on the 'Proceed' choice when running this has a fix script will provide some informative output.\n\n'target' is the user ID the Users table account record to be de-provisioned\n\n'admin_roles' lists all of the roles to be considered in scope for the script. If the deprovision_level variable is set to 2 (as described below) these will be the only roles stripped from the account.\n\n'deprov_level' sets the depth of the de-provisioning activity, and the comments describe valid value. This variable is included in order to make the script flexible enough to be used for a range of sysadmin tasks, from simply reviewing what level of access the account has to completing stripping it of all roles and groups. Note that this script is not designed to delete an account record.\n\n'deprov_result_msg' includes the text that will be written to the system log (and outputted when running the fix script interactively)\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/De-Provision Admin user (configurable)/deprovisionAdmin.js",
    "content": "var target = 'this_iser_id'; // User ID (not sys_id) of account to be deprovisioned\nvar admin_roles = ['admin', 'security_admin']; //Will be used if deprov_level = 2\nvar deprov_level = 0;\n// 0 = take no action but list admin roles and active status (prints to console)\n// 1 = simply make account not active and locked out \n// 2 = strip all admin roles as defined above and de-activate / lockout\n// 3 = strip all roles and group memberships and de-activate / lockout\n// 4 = do all of the above plus clear PW and email fields\n\nif (this.hasRoleExactly('security_admin') && gs.getUser().getName() != target) {\n    deprovisionAdminAccount(target, admin_roles, deprov_level);\n} else {\n    gs.info('Please ensure that you have the security_admin role prior to using this tool. Also note that you may not run this tool against your own account.');\n}\n\n\n\n\n\nfunction deprovisionAdminAccount() {\n    try {\n        var deprov_result_msg = \"\";\n        switch (deprov_level) {\n            case 0:\n                deprov_result_msg = this.returnAccountProfile(target);\n                break;\n            case 1:\n                deprov_result_msg = this.closeAccount(target);\n                break;\n            case 2:\n                this.closeAccount(target);\n                deprov_result_msg = this.stripAdminRoles(target, admin_roles);\n                break;\n            case 3:\n                deprov_result_msg = this.closeAccount(target);\n                deprov_result_msg += '\\n';\n                deprov_result_msg += this.stripAllRolesAndGroups(target);\n                break;\n            default:\n                deprov_result_msg = this.returnAccountProfile(target);\n                break;\n        }\n        gs.info(deprov_result_msg);\n    } catch (error) {\n        gs.error('Error in Fix Script (function deprovisionAdminAccount()): ' + error);\n    }\n\n} //deprovisionAdminAccount()\n\nfunction returnAccountProfile(target) {\n    //\n    //Uniquely identify the user account specified in the target variable\n    //\n    var grTargetUser = new GlideRecordSecure('sys_user');\n    grTargetUser.addQuery('user_name', target);\n    grTargetUser.query();\n    if (grTargetUser.getRowCount() < 0) {\n        deprov_result_msg = \"No account with user name \" + target + \" was found.\";\n    }\n    var targetSYSID = '';\n    while (grTargetUser.next()) {\n        targetSYSID = grTargetUser.getUniqueValue();\n    }\n\n\n    var returnObj = {\n        roles: [],\n        groups: []\n    };\n\n    var outputMsg = '';\n    var grRoleMembership = new GlideRecordSecure('sys_user_has_role');\n    grRoleMembership.addQuery('user', targetSYSID);\n    grRoleMembership.query();\n    if (grRoleMembership.getRowCount() > 0) {\n        outputMsg += 'Account ' + target + ' has been assigned the following roles: \\n';\n        while (grRoleMembership.next()) {\n            returnObj.roles.push(grRoleMembership.getDisplayValue('role'));\n            outputMsg += grRoleMembership.getDisplayValue('role') + '\\n';\n        }\n    }\n\n    var grGroupMembership = new GlideRecordSecure('sys_user_grmember');\n    grGroupMembership.addQuery('user', targetSYSID);\n    grGroupMembership.query();\n    if (grGroupMembership.getRowCount() > 0) {\n        outputMsg += '\\nAccount ' + target + ' belongs to the following groups: \\n';\n        while (grGroupMembership.next()) {\n            returnObj.groups.push(grGroupMembership.getDisplayValue('group'));\n            outputMsg += grGroupMembership.getDisplayValue('group') + '\\n';\n        }\n    }\n    outputMsg += '\\nNo changes have been made to account ' + target;\n    return outputMsg;\n}\n\nfunction closeAccount(target) {\n    var outputMsg = '';\n    var grTargetUser = new GlideRecordSecure('sys_user');\n    grTargetUser.addQuery('user_name', target);\n    grTargetUser.query();\n    if (grTargetUser.getRowCount() < 0) {\n        outputMsg = \"No account with user name \" + target + \" was found.\";\n    }\n    while (grTargetUser.next()) {\n\t\tgrTargetUser.setWorkflow(false);\n        grTargetUser.setValue('active', false);\n        grTargetUser.setValue('locked_out', true);\n        grTargetUser.update();\n    }\n\n    outputMsg = 'Account ' + target + ' has been made not active and has been locked out.';\n    return outputMsg;\n}\n\nfunction stripAdminRoles(target, admin_roles) {\n    var outputMsg = '';\n    //\n    //Uniquely identify the roles specified in the admin_rolse variable\n    //\n    var admin_roles_SYSID = [];\n    admin_roles.forEach(function(admin_role) {\n        admin_roles_SYSID.push(this.getRoleID(admin_role));\n    });\n    //\n    //Uniquely identify the user\n    //\n    var grTargetUser = new GlideRecordSecure('sys_user');\n    grTargetUser.addQuery('user_name', target);\n    grTargetUser.query();\n    if (grTargetUser.getRowCount() < 0) {\n        outputMsg = \"No account with user name \" + target + \" was found.\";\n    }\n    var targetSYSID = '';\n    while (grTargetUser.next()) {\n        targetSYSID = grTargetUser.getUniqueValue();\n    }\n\n    var grRoleMembershipsToStrip = new GlideRecordSecure('sys_user_has_role');\n    grRoleMembershipsToStrip.addQuery('user', targetSYSID);\n    grRoleMembershipsToStrip.query();\n    if (admin_roles.length == 0) {\n        outputMsg = 'No admin roles to strip from ' + target + '. No action on roles taken.';\n        return;\n    }\n    var delete_count = 0;\n    while (grRoleMembershipsToStrip.next()) {\n        if (admin_roles_SYSID.indexOf(grRoleMembershipsToStrip.getValue('role')) > -1) {\n\t\t\tgrRoleMembershipsToStrip.setWorkflow(false);\n            grRoleMembershipsToStrip.deleteRecord();\n            delete_count++;\n        }\n    }\n    outputMsg = 'Removed ' + delete_count + ' roles from account ' + target;\n    return outputMsg;\n}\n\nfunction stripAllRolesAndGroups(target) {\n    var outputMsg = '';\n    var grTargetUser = new GlideRecordSecure('sys_user');\n    grTargetUser.addQuery('user_name', target);\n    grTargetUser.query();\n    if (grTargetUser.getRowCount() < 0) {\n        outputMsg = \"No account with user name \" + this.target + \" was found.\";\n    }\n    var targetSYSID = '';\n    while (grTargetUser.next()) {\n        targetSYSID = grTargetUser.getUniqueValue();\n    }\n\n    var role_delete_count = 0;\n    var group_delete_count = 0;\n    var grRoleMembership = new GlideRecordSecure('sys_user_has_role');\n    grRoleMembership.addQuery('user', targetSYSID);\n    grRoleMembership.query();\n    if (grRoleMembership.getRowCount() > 0) {\n        while (grRoleMembership.next()) {\n\t\t\tgrRoleMembership.setWorkflow(false);\n            grRoleMembership.deleteRecord();\n            role_delete_count++;\n        }\n    }\n\n    var grGroupMembership = new GlideRecordSecure('sys_user_grmember');\n    grGroupMembership.addQuery('user', targetSYSID);\n    grGroupMembership.query();\n    if (grGroupMembership.getRowCount() > 0) {\n        while (grGroupMembership.next()) {\n\t\t\tgrGroupMembership.setWorkflow(false);\n            grGroupMembership.deleteRecord();\n            group_delete_count++;\n        }\n    }\n    outputMsg = 'Removed ' + role_delete_count + ' roles and ' +\n        group_delete_count + ' groups from account ' + target;\n    return outputMsg;\n}\n\nfunction getRoleID(roleName) {\n    var returnValue = '';\n    var grRole = new GlideRecord('sys_user_role');\n    grRole.addQuery('name', roleName);\n    grRole.query();\n    while (grRole.next()) {\n        returnValue = grRole.getUniqueValue();\n    }\n    return returnValue;\n}\n\nfunction hasRoleExactly(role) {\n    var arrayUtility = new ArrayUtil();\n    var roles = gs.getSession().getRoles() + '';\n    var roleArray = roles.split(\",\");\n    var isAuthorized = arrayUtility.contains(roleArray, role);\n    return isAuthorized;\n}"
  },
  {
    "path": "Specialized Areas/Fix scripts/Delete Change Conflict/README.md",
    "content": "Use this script to delete change Conflicts and set conflict status as 'No Conflict' using fix script.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Delete Change Conflict/script.js",
    "content": "// delete conflicts for any change and update conflict status to 'No Conflict'\nvar changeRec = new GlideRecord('change_request');\nchangeRec.addQuery('conflict_status', 'Conflict');\nchangeRec.addQuery('number', 'CHG0000014');  // replace your change number\n//changeRec.addQuery('sys_id', 'sys_id of change request record');  // can be used if query is based on sys_id of Change\nchangeRec.query();\nif (changeRec.next()) {\n    var chg = new ChangeConflictHandler();\n    // delete conflict records for the change from Conflict table\n    var output = chg.deleteConflictsByChangeId(changeRec.getUniqueValue()); // or pass any change Record sys_id\n    changeRec.conflict_status = 'No Conflict';  //optional \n    changeRec.update(); // optional\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Delete Duplicate Mobile records/README.md",
    "content": "Below script is a Fix script used to delete duplicate mobile assets and Cis in your CMDB. In this script we keep recently created assets and its related CI and delete other duplicate assets and related Cis if any.\n\nNote : In this script I have used u_alm_mobile as the table and u_cmdb_ci_mobile, this are my tables but you can use your own table where you have stored your mobile devices.\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Delete Duplicate Mobile records/script.js",
    "content": "var grMob = new GlideAggregate('u_alm_mobile');\ngrMob.addAggregate('COUNT', 'serial_number');\ngrMob.groupBy('serial_number');\ngrMob.addHaving('COUNT', '>', 1);\ngrMob.query();\nwhile (grMob.next())\n\t{\t\n\tgs.log(\"Numb of Duplicates: \" + grMob.getAggregate('COUNT', 'serial_number') + \" => \" + grMob.serial_number, 'DuplicateSerailNumber');\n\tvar dup = new GlideRecord('u_alm_mobile');\n\tdup.addQuery('serial_number',grMob.serial_number);\n\tdup.orderByDesc('sys_created_on');\n\tdup.query();\n\tif(dup.next())\n\t\t{\n\t\tvar del = new GlideRecord('u_alm_mobile');\n\t\tdel.addQuery('sys_id','!=',dup.sys_id);\n\t\tdel.addQuery('serial_number',dup.serial_number);\n\t\tdel.query();\n\t\twhile(del.next())\n\t\t\t{\n\t\t\tvar ci = new GlideRecord('u_cmdb_ci_mobile');\n\t\t\tci.addQuery('sys_id',del.ci);\n\t\t\tci.query();\n\t\t\tif(ci.next())\n\t\t\t\t{\n\t\t\t\tci.deleteRecord();\t\t\t\t\n\t\t\t}\n\t\t\tdel.deleteRecord();\n\t\t\t\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Employee document management/read.md",
    "content": "# Regenerate the draft document created for e-signature (or) regenerate the draft document if the field values changed after the draft document had created <br />\n\n***Use case:*** The document was generated with the dynamic fields selected in the document template. But if the field values changes after document generation then these changes will not be reflected in the generated document.<br /><br />\n***Solution:*** Leverage EDM utils provided OOB and call these utilities to regenerate the documents. After this script runs, a new record will be created in draft_document table. The previous version of the document in draft_document will be set to inactive\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Employee document management/regenerate_document.js",
    "content": "var gr = new GlideRecord('VALID_TABLE_NAME');\ngr.addEncodedQuery('PASS_VALID_QUERY_To_TARGET_RECORDS_WHICH_HAVE_DOCUMENTS_THAT_NEEDS_TO_BE_REGENERATED');\ngr.query();\nwhile (gr.next()) {\n\n    if (new hr_PdfUtils().isValidPdfTemplateForPreFill(gr.pdf_template.sys_id)) { //Check if it is a pre-filled type of document or editable pdf document\n        var pdfDraftSysId = new hr_PdfUtils().prefillPdfTemplate(gr.pdf_template.sys_id, false, gr.sys_id, gr.sys_class_name, gr.sys_id);\n        if (gs.nil(pdfDraftSysId))\n            gs.info('Error occurred while generating pdf for ' + gr.sys_id);\n    } else {\n        new GeneralHRForm().inactivateRelatedDrafts(gr.sys_class_name, gr.sys_id);\n        var caseAjax = new hr_CaseAjax();\n        var document = caseAjax.documentBody(gr.sys_class_name, gr.sys_id, gr.sys_class_name, gr.sys_id, 'true');\n        caseAjax.setDocumentBody(document.body, gr.sys_class_name, gr.sys_id, gr.sys_class_name, gr.sys_id);\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find Reports Assigned to inactive Groups/README.md",
    "content": "This Fix script will get you the reports assigned to inactive groups so that you can do the cleanup of reports\nRequired role: itil_admin\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find Reports Assigned to inactive Groups/reportAssignedToInactiveGroups.js",
    "content": "var report = new GlideRecord(\"sys_report_users_groups\");\nreport.addEncodedQuery('group_id.active=false');\nreport.query();\nwhile (report.next())\n{\n\tgs.print(\"record found: \"+report.sys_id);\n\treport.group_id = \"\";\t\n\treport.setWorkflow(false);\n\treport.autoSysFields(false);\n\treport.update();\n}\nignore = 'true';\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find duplicate records/Find duplicate records.js",
    "content": "\n// Find duplicate records in a table e.g. cmdb_ci_vmware_instance based on a field e.g. name\n\nvar TABLE_TO_FIND_DUPLICATE_IN = \"cmdb_ci_vmware_instance\"; // Replace with your table\nvar FIELD_TO_GROUP_BY = \"name\"; // replace with your field\n\nvar dupRecords = []; \nvar gaDupCheck1 = new GlideAggregate(TABLE_TO_FIND_DUPLICATE_IN); \ngaDupCheck1.addAggregate('COUNT', FIELD_TO_GROUP_BY); \ngaDupCheck1.groupBy(FIELD_TO_GROUP_BY); \ngaDupCheck1.addHaving('COUNT', '>', 1); \ngaDupCheck1.query(); \n\nwhile (gaDupCheck1.next()) { \n   dupRecords.push(gaDupCheck1.name.toString() + '\\n'); \n} \n\ngs.print(dupRecords);\n\n\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find duplicate records/README.md",
    "content": "# Find duplicate records\n\nRun the script to find duplicate records in a table. It is using GlideAggregate to find duplicate records. Replace the TABLE_TO_FIND_DUPLICATE_IN and FIELD_TO_GROUP_BY constants with your table and field and it will spit the records.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find duplicate records/TechTrekwithAJ_DuplicateCIreadme.md",
    "content": "Function Declaration: The entire code is wrapped in an immediately invoked function expression (IIFE) for encapsulation.\nConstants Definition: Two variables are defined:TABLE_TO_FIND_DUPLICATE_IN,FIELD_TO_GROUP_BY\nGlideAggregate Instance: Creates a new instance of GlideAggregate for the specified table.\nGroup By Field: Adds an aggregate function to count occurrences of the specified field and groups the results by that field.\nHaving Clause: Adds a condition to filter results to only those where the count of occurrences is greater than 1 (indicating duplicates).\nQuery Execution: Executes the query to fetch the results.\nLog Duplicates: A loop iterates through the results, Retrieves the value of the field being checked, Gets the count of duplicates and Logs a message to the system log displaying the duplicate field value and its count.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find duplicate records/TechTrekwithAJ_FindDuplicateonCMDB_CI.JS",
    "content": "(function() {\n\n    var TABLE_TO_FIND_DUPLICATE_IN = 'cmdb_ci';\n    var FIELD_TO_GROUP_BY = 'name';\n\n    // GlideAggregate instance\n    var ga = new GlideAggregate(TABLE_TO_FIND_DUPLICATE_IN);\n    \n    // Group by the specified field\n    ga.addAggregate('COUNT', FIELD_TO_GROUP_BY);\n    ga.groupBy(FIELD_TO_GROUP_BY);\n    \n    // Query for records with duplicates (count > 1)\n    ga.addHaving('COUNT', '>', '1');\n    ga.query();\n    \n    // Log the duplicate records\n    while (ga.next()) {\n        var fieldValue = ga.getValue(FIELD_TO_GROUP_BY);\n        var count = ga.getAggregate('COUNT', FIELD_TO_GROUP_BY);\n        gs.info('Duplicate Record: ' + fieldValue + ' - Count: ' + count);\n    }\n})();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find the reports assigned to Inactive Users/README.md",
    "content": "This Fix script will get you the reports assigned to inactive Users so that you can do the cleanup of reports\nRequired role: itil_admin\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Find the reports assigned to Inactive Users/reportsassignedtoinactiveusers.js",
    "content": "var report = new GlideRecord(\"sys_report_users_groups\");\nreport.addEncodedQuery('user_id.active=false');\nreport.query();\nwhile (report.next()){\n\tgs.print(\"record found: \"+report.sys_id);\n\treport.user_id = \"\";\t\n\treport.setWorkflow(false);\n\treport.autoSysFields(false);\n\treport.update();\n}\nignore = 'true';\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Fiscal period renamer/README.md",
    "content": "OOTB the Fiscal periods are named as such - \"FY21: M01\". This naming convention relies on each and every user knowing what M01 is (i.e. does the fiscal year start in January or does it start in March). This renaming script will append a descriptor to the end of each fiscal period name to increase usability and to preserve numeric sorting. Assuming M01 is January, the renamed fiscal period might be - \"FY21: M01 Jan\".\n\nThe fiscal period generator also assumes a hard-coded convention for the FYXX component of the name. Many customers differ from this and require it in the format FY23-24 if a fiscal year stretches across two calendar years. This script will allow manual selection of the 'from' year component (i.e. if FY is FY23 and stretches from July 2022 to June 2023 the FY will be renamed FY22-23:).\n\nInstructions:\n1. Update the fpQuery variable to be an encoded query that represents the fiscal periods you wish to rename\n2. Update the orderedMonth variable to represent the descriptor to append to each period name. These must be in the order required for the fiscal year created.\n3. Update the startAdditionalYear variable - this will prepend the financial year value with this value (i.e. change FY21 to FY20-21). This will increment for each FY so input the first value required.\n4. Update the fiscalPeriodRecordsPerYear variable - this is the number of fiscal period records you've created per fiscal year - this is typically 17. Increment this value by one and input (i.e. I'd input 18 if I had 17 records)\n5. Run the script and confirm the log statements are correct\n6. Uncomment row 83 and run again to commit output of step 5\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Fiscal period renamer/fiscal period renamer.js",
    "content": "updateFiscalPeriods();\n\t\n\n\tfunction updateFiscalPeriods() {\n\t\n\n\t    try {\n\t\n\n\t        // endoded query that defines the fiscal year/s to update, as generated in each instance sys_id will always be different\n\t        var fpQuery = 'fiscal_year=91dbfea31bdf3010dddda82c274bcbd6';\n\t        \n\t        // the short month names to append to the end of the month period names, should be ordered from first month in fiscal year to last\n\t        var orderedMonth = [\"Jul\", \"Aug\", \"Sep\", \"Oct\", \"Nov\", \"Dec\", \"Jan\", \"Feb\", \"Mar\", \"Apr\", \"May\", \"Jun\"];\n\t        \n\t        // the first end year in the set of fiscal periods being updated (e.g if fy23 created then this should be set to 24) set to 0 if not wanting to augment the year value included in the name\n\t        var startAdditionalYear = 22;\n\t        \n\t        // number of fiscal period records generated per fiscal year plus one - used to increment the year\n\t        var fiscalPeriodRecordsPerYear = 18;\n\t        \n\t        \n\t        var monthIncrementer = 0,\n\t            yearIncrementer  = 0,\n\t            monthPrefix      = \"M0\";\n\t        var formattedMonth, currName, newName;\n\t        \n\n\t        var fpGR = new GlideRecord('fiscal_period');\n\t        fpGR.addEncodedQuery(fpQuery);\n\t        fpGR.orderBy('name');\n\t        fpGR.query();\n\t        while (fpGR.next()) {\n\t            // reset/increment variables for each fiscal period record iterated through\n\t            var newMonth = \"\";\n\t            newName = \"\";\n\t            yearIncrementer++;\n\t\n\n\t            // if there are 18 fiscal periods per year then this resets variables to iterate through new year\n\t            if (yearIncrementer == fiscalPeriodRecordsPerYear) {\n\t                if (startAdditionalYear > 0) {\n\t                    startAdditionalYear++;\n\t                }\n\t                yearIncrementer = 0;\n\t                monthIncrementer = 0;\n\t            }\n            \n\t\n\t            // get the current period name\n\t            currName = fpGR.getValue('name');\n            \n\t\t\t\t\n\t\t          // if we are trying to add a string that already exists in current fiscal period, abort, abort\n\t            if (currName.indexOf(orderedMonth[monthIncrementer]) >= 0 && orderedMonth.length > 0 || currName.indexOf(startAdditionalYear) >= 0 && startAdditionalYear > 0) {\n\t\t\t            gs.info('ERROR - Current name already includes month short name OR additional start year');\n\t\t\t            return;\n\t            }\n\t\n\n\t            // if a month then add the corresponding short month name to newMonth variable and the string being added doesn't already exist in the current name\n\t            if (fpGR.getValue('fiscal_type') == 'month' && orderedMonth.length > 0) {\n\t                newMonth = \" \" + orderedMonth[monthIncrementer];\n\t                monthIncrementer++;\n\t\t          }\n\t\n\n\t\n\n\t            // if adding year values to name then add the year value and additional year doesn't already exist in current value, if not just set the new name to the current name\n\t            if (startAdditionalYear > 0) {\n\t\t\t\t\t        newName = currName.slice(0, 2) + startAdditionalYear + \"-\" + currName.slice(2, 4) + currName.slice(4);\n\t            } else newName = currName;\n\t\n\n\n\t            // if adding month short names AND adding year then just append the month to the already built new name from above\n\t            // if not adding year then add month to the current name\n\t            if (newMonth) {\n\t                if (startAdditionalYear > 0) {\n\t                    newName += newMonth;\n\t                } else newName = currName + newMonth;\n\t            }\n\t\n\n\n\t            // commit and save the new name to the database\n\t            fpGR.setValue('name', newName);\n\t            gs.info('If newName correct uncomment update code - ' + newName);\n\t            //fpGR.update();\n\t        }\n\t\n\n\t    } catch (e) {\n\t        gs.error(e.message);\n\t    }\n\t}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Fix model names after enabling glide.cmdb_model.display_name.shorten/README.md",
    "content": "There is a property that enables de-duplication of Manufacturer/Publisher Names from Model records (property name: [glide.cmdb_model.display_name.shorten](https://docs.servicenow.com/bundle/rome-it-service-management/page/product/asset-management/concept/c_InstalledWithModelManagement.html#r_ModelManagementProperties \"glide.cmdb_model.display_name.shorten\")). If this property is not active and users enter the Manufacturer/Publisher name into the Model Name field the Manufacturer/Publisher name will show twice in the Display Name.\n\n<div style=\"padding-left: 2em;\">\n\nFor example,  \n**Manufacturer/Publisher Name:** Microsoft  \n**Model Name:** Microsoft Word  \n**Display Name with Property FALSE:** <span style=\"color: #ff0000;\">Microsoft Microsoft Word</span>  \n**Display Name with Property TRUE:** <span style=\"color: #008000;\">Microsoft Word</span>\n\n</div>\n\nOnce this property is activated, inserts/updates of Model records will trigger the business rule to recalculate the Display Name when one of the following fields is updated: Manufacturer/Publisher, Name, Version, Edition, Platform, Language. If you activate this property AFTER many models have been loaded, you may need to run a fix script to retroactively clean the existing Model Display Names.\n\nThe script below can be run as a Fix Script or as a Background Script. Normally, I would `setWorkflow(false)` for this kind of cleanup, but there are a few cascade Business Rules that need to run as this fix is implemented.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Fix model names after enabling glide.cmdb_model.display_name.shorten/fixModelNames.js",
    "content": "fixModelNames();\n\nfunction fixModelNames() {\n\tvar modelGR = new GlideRecord(\"cmdb_model\");\n\tmodelGR.orderBy(\"display_name\");\n\tmodelGR.query();\n\twhile (modelGR.next()) {\n\t\tvar modelName = modelGR.name;\n\t\tif (modelGR.sys_class_name == \"cmdb_software_product_model\") {\n\t\t\tvar swModelGR = new GlideRecord(\"cmdb_software_product_model\");\n\t\t\tif (swModelGR.get(modelGR.sys_id)) {\n\t\t\t\tmodelName = swModelGR.product.getDisplayValue();\n\t\t\t}\n\t\t}\n\t\t\n\t\tvar platform = '';\n\t\tif(!gs.nil(modelGR.platform) && modelGR.platform != 'anything'){\n\t\t\tplatform  = modelGR.getDisplayValue(\"platform\");\n\t\t}\n\t\t\n\t\tvar language = '';\n\t\tif(!gs.nil(modelGR.language) && modelGR.language != '832bec5493212200caef14f1b47ffb56'){\n\t\t\tlanguage =  modelGR.getDisplayValue(\"language\");\n\t\t}\n\t\t\t\t\n\t\tvar values = [modelGR.manufacturer.getDisplayValue(), modelName, modelGR.version, modelGR.edition, platform,language];\n\t\tvar displayName = '';\n\t\t\n\t\tif (values[1].toLowerCase().indexOf(values[0].toLowerCase()) != -1 && 'true'.equals(gs.getProperty('glide.cmdb_model.display_name.shorten')))\n\t\t\tvalues[0] = '';\n\t\t\n\t\tfor (var i = 0; i < values.length; i++){\n\t\t\tif (values[i] != undefined && values[i] != '')\n\t\t\t\tdisplayName += ' ' + values[i];\n\t\t}\n\t\t\n\t\tmodelGR.display_name = displayName.trim();\n\t\tmodelGR.update();\n\t}\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Fix teams token/README.md",
    "content": "This script helps to fix the MS teams integration token for a specific user. Once the script has been executed, the user can try to reinitiate the teams from the task to retrieve a new token."
  },
  {
    "path": "Specialized Areas/Fix scripts/Fix teams token/script.js",
    "content": "var userId = ''; // Place the userID of the user to whom you want to restart the teams token\nvar azureGr = new GlideRecord(\"sn_now_azure_user\");\nazureGr.addQuery('user.user_name',userId);\nazureGr.query();\nif(azureGr.next()){\n\t// gs.print(azureGr.upn);\tCheck if the record is valid one or not\n    azureGr.deleteRecord();\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Format JSON in String Fields/README.md",
    "content": "**Details**\n1. This script will format the JSON data in string fields on forms.\n2. There is on OOB attribute \"json_view\" which can be added to field but it always reqires an extra click and has loading time issues.\n\n**How to use**\n1. Run this script as Fix Script.\n2. Replace the table name and encoded query as per your requirement.\n3. Replace the field to be formatted as per the table selected.\n\n**Before Formatting**\n<img width=\"956\" height=\"371\" alt=\"Before1\" src=\"https://github.com/user-attachments/assets/1b35dfaf-d2ad-44c3-bbd2-d3198664073b\" />\n\n**After Formatting**\n <img width=\"950\" height=\"382\" alt=\"After\" src=\"https://github.com/user-attachments/assets/c3a1c3c0-48bd-4d2f-9e27-fea0ed86004d\" />\n\nJSON.stringify() documentation : https://www.geeksforgeeks.org/javascript/javascript-json-stringify-method/\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Format JSON in String Fields/script.js",
    "content": "/*\nThis script will format the JSON data in string fields on forms.\nThere is on OOB attribute \"json_view\" which can be added to field but it always reqires an extra click and has loading time issues.\n*/\nvar chReq = new GlideRecord('change_request'); // Glide table.\nchReqch.get('c83c5e5347c12200e0ef563dbb9a7190'); // sys_id of record, can be replaced with encoded query for multiple records.\n\nchReq.u_json_field = JSON.stringify(JSON.parse(chReq.u_json_field), null, \"\\t\");\nchReq.update();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Get Catalog Items not used in last few months/README.md",
    "content": "Use this fix script to get the list of Catalog items that are not used in few months. \n\nThis information is used by stackholders to identify unused cat items and the can start deactivating it.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Get Catalog Items not used in last few months/getUnusedCatItems.js",
    "content": "/* Below script can be used to find unused and Used catalog items (Including record producers) in last 15 Months\n\ngs.info(\"------Used Catalog Items --------\");\n\nvar usedItems = [];\nvar unusedItems = [];\nvar ritm = new GlideAggregate('sc_req_item');\nritm.addEncodedQuery('sys_created_onRELATIVEGT@month@ago@15');\nritm.groupBy('cat_item');\nritm.query();\nwhile (ritm.next()) {\n    usedItems.push(ritm.getValue('cat_item'));\n    gs.info(ritm.getDisplayValue('cat_item'));\n}\n\nvar recGr = new GlideAggregate('sc_item_produced_record');\nrecGr.addEncodedQuery('sys_created_onRELATIVEGT@month@ago@15');\nrecGr.groupBy('producer');\nrecGr.query();\nwhile (recGr.next()) {\n    usedItems.push(recGr.getValue('producer'));\n    gs.info(recGr.getDisplayValue('producer'));\n}\ngs.info(\"------Unused Catalog Items --------\");\nvar unusedCatalogItems = new GlideRecord('sc_cat_item');\nunusedCatalogItems.addQuery('sys_id', 'NOT IN', usedItems.toString());\nunusedCatalogItems.query();\nwhile (unusedCatalogItems.next()) {\n    gs.info(unusedCatalogItems.name);\n    unusedItems.push(unusedCatalogItems.getUniqueValue());\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Group Sync Script/README.md",
    "content": "ServiceNow Fix Script - Group Role Synchronization\nOverview\n\nThis Fix Script automatically validates and synchronizes user roles with their assigned groups in ServiceNow.\nIt checks if every user in the target groups has all the roles assigned to that group.\nIf any roles are missing, the script re-adds the user to the group, ensuring all inherited roles are correctly applied.\n\nHow It Works\n\nIdentify Groups\nThe script starts by reading the list of sys_ids of the target groups.\n\nFetch Group Roles\nIt retrieves all the roles assigned to each group from the sys_group_has_role table.\n\nCheck Each User\nFor each user in the group (sys_user_grmember), it fetches their assigned roles from sys_user_has_role.\n\nDetect Missing Roles\nCompares the user’s roles with the group’s roles.\nIf any group role is missing for a user:\n\nRemoves the user from the group.\n\nRe-adds the user to the group, triggering ServiceNow’s role inheritance process.\n\nLogs\nThe script logs all actions using gs.info() for easy monitoring in the system logs.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Group Sync Script/fix_script.js",
    "content": "(function executeFixScript() {\n    // List of group sys_ids to process\n    var groupIds = [\n        'a715cd759f2002002920bde8132e7018' // Add more sys_ids if needed\n    ];\n\n    var groupGR = new GlideRecord('sys_user_group');\n    groupGR.addQuery('sys_id', 'IN', groupIds);\n    groupGR.query();\n\n    while (groupGR.next()) {\n        gs.info('Processing Group: ' + groupGR.name);\n\n        // --- Fetch all roles assigned to this group ---\n        var groupRoles = [];\n        var groupRoleGR = new GlideRecord('sys_group_has_role');\n        groupRoleGR.addQuery('group', groupGR.sys_id);\n        groupRoleGR.query();\n\n        while (groupRoleGR.next()) {\n            groupRoles.push(groupRoleGR.role.toString());\n        }\n\n        gs.info('  Group Roles: ' + groupRoles.join(', '));\n\n        // --- Get all users in the group ---\n        var usersInGroup = [];\n        var memberGR = new GlideRecord('sys_user_grmember');\n        memberGR.addQuery('group', groupGR.sys_id);\n        memberGR.query();\n\n        while (memberGR.next()) {\n            var userGR = memberGR.user.getRefRecord();\n            if (userGR.isValidRecord()) {\n                usersInGroup.push({\n                    userRecord: userGR,\n                    memberSysId: memberGR.sys_id\n                });\n            }\n        }\n\n        // --- Validate each user's roles against group roles ---\n        for (var i = 0; i < usersInGroup.length; i++) {\n            var member = usersInGroup[i];\n            var userGR = member.userRecord;\n\n            // Collect all roles assigned to user\n            var userRoles = [];\n            var userRoleGR = new GlideRecord('sys_user_has_role');\n            userRoleGR.addQuery('user', userGR.sys_id);\n            userRoleGR.query();\n\n            while (userRoleGR.next()) {\n                userRoles.push(userRoleGR.role.toString());\n            }\n\n            // Identify missing roles\n            var missingRoles = groupRoles.filter(function(role) {\n                return userRoles.indexOf(role) === -1;\n            });\n\n            if (missingRoles.length > 0) {\n                gs.info('  User ' + userGR.name + ' missing roles: ' + missingRoles.join(', '));\n                gs.info('  Re-adding user to group to refresh roles.');\n\n                // Remove user from the group\n                var deleteGR = new GlideRecord('sys_user_grmember');\n                if (deleteGR.get(member.memberSysId)) {\n                    deleteGR.deleteRecord();\n                }\n\n                // Re-add user to group to trigger role re-evaluation\n                var newMember = new GlideRecord('sys_user_grmember');\n                newMember.initialize();\n                newMember.group = groupGR.sys_id;\n                newMember.user = userGR.sys_id;\n                newMember.insert();\n\n                gs.info('  User ' + userGR.name + ' re-added successfully.');\n            } else {\n                gs.info('  User ' + userGR.name + ' has all required roles.');\n            }\n        }\n\n        gs.info('Completed processing group: ' + groupGR.name);\n    }\n\n    gs.info('Fix Script completed successfully for all specified groups.');\n})();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Ignore outbound emails/README.md",
    "content": "Used to ignore all emails in the system so they are not sent on outbox activation. \nRun this script prior to enabling email sending to ensure no testing emails are sent upon email activation\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Ignore outbound emails/ignoremail.js",
    "content": "var emailGR = new GlideRecord('sys_email');\n\nemailGR.addQuery('type','send-ready');\nemailGR.query();\n\nwhile (emailGR.next()) {\n  emailGR.type = 'send-ignored';\n  emailGR.update();\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Install Base PDI Plugins/README.md",
    "content": "# Install Base PDI Plugins\n\nFix Script to speed up installation of multiple plugins on a fresh PDI (even with demo data).\n\nOptions: (modify script as needed)\n- plugins (array) includes names of the plugins to install\n- include_demo_data (true/false) whether to install demo data or not (default=false)\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Install Base PDI Plugins/install_plugins.js",
    "content": "// Change to \"true\" to install demo data with the plugins\nvar include_demo_data = false;\n\nvar plugins = [];\n\n// Add or remove plugins by adding or removing plugins.push lines to the code below:\n\n// Service Portfolio Management SLA Commitments\n//   Service Portfolio Management Foundation\n//   Service level management\nplugins.push('com.snc.service_portfolio.sla_commitment');\n// Service Catalog Manager\nplugins.push('com.snc.sc_catalog_manager');\n// PPM Standard (Project Portfolio Suite with Financials)\n//   Performance Analytics - Content Pack - PPM Standard\n//   Project Portfolio Suite\n//   Release Management\n//   Ideation with PPM\nplugins.push('com.snc.financial_planning_pmo');\n// Agile Development 2.0\nplugins.push('com.snc.sdlc.agile.2.0');\n// Agile Development - Unified Backlog\nplugins.push('com.snc.sdlc.agile.multi_task');\n// I18N: Internationalization\n//   System Import Sets\n//   I18N: Knowledge Management Internationalization Plugin v2\nplugins.push('com.glide.i18n');\n// Test Management 2.0\nplugins.push('com.snc.test_management.2.0');\n// Incident Management - Major Incident Management\n//   Incident Communications Management\n//   Incident Updates\n//   Task-Outage Relationship\n//   WebKit HTML to PDF\nplugins.push('com.snc.incident.mim');\n// Change Management - Risk Assessment\n//   Assessment Designer\n//   Assessment\n//   Best Practice - Change Risk Calculator\nplugins.push('com.snc.change_management.risk_assessment');\n\nvar main = new GlideMultiPluginManagerWorker();\nmain.setPluginIds(plugins);\nmain.setIncludeDemoData(include_demo_data);\n//main.setLoadDemoDataOnly(true); // Can be used to install demo data after installation of plugins.\nmain.setProgressName(\"Plugin Installer\");\nmain.setBackground(true);\nmain.start();\n\ngs.info(\"Plugin installation has been initiated, please note that installation runs in the background and can take some time.\");\ngs.info(\"Please visit the following URLs to monitor the state of the installed plugins.\");\ngs.info(\"The installation has finished when all the following plugins have reached State=Active.\");\ngs.info(\"https://\" + gs.getProperty(\"instance_name\") + \".service-now.com/nav_to.do?uri=%2Fv_plugin_list.do%3Fsysparm_query%3DidIN\" + plugins.join(\",\"));\ngs.info(\"A more detailed installation progress can also be seen in the Upgrade History log:\");\ngs.info(\"https://\" + gs.getProperty(\"instance_name\") + \".service-now.com/nav_to.do?uri=sys_upgrade_history_list.do\");\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Install Demo Data/InstallDemoData.js",
    "content": "//List of plugins to install\nvar plugins = ['com.snc.sdlc.agile.2.0.atf', 'com.snc.change_management.atf', 'com.snc.cmdb.atf', 'com.snc.incident.atf', 'com.snc.financial_planning_pmo.atf', 'com.snc.problem.atf', 'com.glideapp.servicecatalog.atf.test', ' com.snc.sla.atf', 'com.snc.test_management.2.0.atf'];\n\n//Other ATF plugins that are not installed but here for reference if we want to easily add them in the future\nvar not_installed = ['com.sn_cim_atf', ' com.snc.innovation_management.atf', 'com.snc.investment_planning.atf', 'com.snc.sdlc.scrum_program.atf'];\n\n\nvar now_GR = new GlideRecord('sys_plugins');\nnow_GR.addQuery('source', 'IN', plugins);\nnow_GR.query();\n\n\nvar pMgr = new GlidePluginManager();\n\nwhile (now_GR.next()) {\n    var pName = now_GR.getValue('name');\n    var pID = now_GR.getValue('source');\n    var isActive = pMgr.isActive(pID);\n\n    //ensure the plugin is active before loading Demo Data\n    if (isActive) {\n        gs.info('The plugin ' + pName + ' is active');\n        pMgr.loadDemoData(pID);\n    }\n}\n\n\n\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Install Demo Data/README.md",
    "content": "Add demo data to a list of installed plugins. I use this to add demo data for the OOTB ATF tests to work in sub-prod instances. It is expected that the corresponding ATF Test plugins are installed on the production instance already, and we are only adding the demo data."
  },
  {
    "path": "Specialized Areas/Fix scripts/Log out active User sessions/README.md",
    "content": "# Log out active User sessions across all nodes\n## Usage\nCan be run as a fix or background script.\n\nThe function `applyExcludedUsersFilter` excludes selected users from the session cull. To add users to this exclusion list simply add their username to the array `excluded_users`. The current user is added to the array by default in the example.\n\nDue to the significant impact that logging out all users would have I've included a `live_run` variable. If this is not explicitly set to `true` (the boolean, not the string) then the script will log the actions it would have taken, but not actually affect any user sessions. [(Similar to the PowerShell `WhatIf` concept)][WhatIfArticle]\n\n## Sample Outputs\n### Dry Run\n```\n*** Script: Live run: false: would logout sessions for UserName.With.Session.1\n*** Script: Live run: false: would logout sessions for UserName.With.Session.2\n*** Script: Live run: false: would logout sessions for UserName.With.Session.3\n*** Script: Live run: false: Logged out sessions for the following users:\n[\n    \"UserName.With.Session.1\",\n    \"UserName.With.Session.2\",\n    \"UserName.With.Session.3\"\n]\n```\n### Live Run\n```\n*** Script: Live run: true: Logged out sessions for the following users:\n[\n    \"UserName.With.Session.1\",\n    \"UserName.With.Session.2\",\n    \"UserName.With.Session.3\"\n]\n```\n[WhatIfArticle]: https://techcommunity.microsoft.com/t5/itops-talk-blog/powershell-basics-don-t-fear-hitting-enter-with-whatif/ba-p/353579 \"PowerShell WhatIf\""
  },
  {
    "path": "Specialized Areas/Fix scripts/Log out active User sessions/log_out_active_user_sessions.js",
    "content": "function logOutActiveUserSessions(live_run) {\n    var usernames_to_logout = getUniqueUsernamesWithActiveSessions();\n    logoutSessionsForEachUsername(usernames_to_logout, live_run);\n    gs.info(\n        'Live run: {0}: Logged out sessions for the following users:\\n{1}',\n        live_run,\n        JSON.stringify(usernames_to_logout, null, 2)\n    );\n}\nfunction getUniqueUsernamesWithActiveSessions() {\n    /**\n     * We use an aggregate so we can groupBy username to return\n     * a unique list of usernames. A user could have multiple active \n     * sessions, but the method to end user sessions locks out all \n     * sessions for that user, so there is no need to run it for \n     * each session they have.\n     */\n    var active_sessions_agg = new GlideAggregate('sys_user_session');\n    // Filter to currently valid sessions\n    active_sessions_agg.addQuery('invalidated', 'NULL');\n    // Filter out non-user sessions eg a non-interactive system/guest session\n    active_sessions_agg.addQuery('name', '!=', 'NULL');\n    // Filter out sessions of current user. You could also exlude any \n    // users you wanted to this way.\n    applyExcludedUsersFilter(active_sessions_agg);\n    active_sessions_agg.groupBy('name');\n    active_sessions_agg.query();\n    var unique_usernames = [];\n    while (active_sessions_agg.next()) {\n        unique_usernames.push(active_sessions_agg.name.toString());\n    }\n    return unique_usernames;\n}\nfunction applyExcludedUsersFilter(user_sessions_gr) {\n    var current_user_user_id = gs.getUserName();\n    var excluded_users = [\n        current_user_user_id,\n        'Special.Person.1',\n        'Special.Person.2'\n    ];\n    user_sessions_gr.addQuery(\n        'name',\n        'NOT IN',\n        excluded_users\n    );\n}\nfunction logoutSessionsForEachUsername(usernames, live_run) {\n    for (var i = 0; i < usernames.length; i++) {\n        logoutSessionsForUsername(usernames[i], live_run);\n    }\n}\nfunction logoutSessionsForUsername(username, live_run) {\n    if (live_run === true) {\n        GlideSessions.lockOutSessionsInAllNodes(username);\n        return;\n    }\n    gs.info(\n        'Live run: {0}: would logout sessions for {1}',\n        JSON.stringify(live_run), // Differentiate strings from booleans\n        username\n    );\n}\n\nvar live_run = false;\nlogOutActiveUserSessions(live_run);"
  },
  {
    "path": "Specialized Areas/Fix scripts/Mass Email Domain Update/README.md",
    "content": "# Update User Email Domain in ServiceNow\n\nThis script finds all users in the **`sys_user`** table whose email addresses contain an old domain (e.g. `bad_domain.com`) and replaces it with a new domain (e.g. `new_domain.com`) using **regular expressions**.\n\n---\n\n## Purpose\n\nTo bulk–update user email domains safely and efficiently without manual edits.\n\nExample use case:\n When your organization migrates from `@bad_domain.com` to `@new_domain.com`, this script updates all users automatically.\n\n---\n\n## Example\n| Old Email              | New Email              |\n|-------------------------|------------------------|\n| alice@bad_domain.com    | alice@new_domain.com   |\n| bob@bad_domain.com      | bob@new_domain.com     |\n| carol@bad_domain.com    | carol@new_domain.com   |\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Mass Email Domain Update/script.js",
    "content": "// Use: Fix script to update all email domains in sys_user table\n// Replaces 'bad_domain.com' with 'new_domain.com' in email field\n\nvar usr = new GlideRecord('sys_user');\nusr.addEncodedQuery('emailLIKEbad_domain.com'); // find users with old domain\nusr.query();\n\nwhile (usr.next()) {\n    gs.print('OLD EMAIL: ' + usr.email);\n\n    // Create regex to match old domain\n    var regex = new SNC.Regex('bad_domain\\\\.com'); // uses '\\\\' to escape dot for regex\n    // Use replaceAll method to substitute\n    usr.email = regex.replaceAll(usr.email, 'new_domain.com');\n    usr.update();\n    gs.print('NEW EMAIL: ' + usr.email);\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Mass Update RITM Variable/README.md",
    "content": "Use this Fix Script to update a variable value on one or more RITM records\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Mass Update RITM Variable/Update RITM Variable.js",
    "content": "//use this script to update a variable value on one or more RITM records\nvar ritm = new GlideRecord('sc_req_item');\n//ritm.addQuery... to return one or a subset of records\nritm.query();\nwhile(ritm.next()){\n\tvar mtom = new GlideRecord('sc_item_option_mtom');\n\tmtom.addQuery('request_item', ritm.sys_id.toString());\n\tmtom.addQuery('sc_item_option.item_option_new', '92bdf4be97857d189d0372e11153af08'); //sys_id of a specific variable to update\n\tmtom.query();\n\tif(mtom.next()){\n\t\tvar item = new GlideRecord('sc_item_option');\n\t\titem.addQuery('sys_id', mtom.sc_item_option.toString());\n\t\titem.query();\n\t\tif(item.next()){\t\t\n\t\t\titem.value = '83d36f44974d71909d0372e11153af5f'; //new value to be assigned, depending on variable type (reference type used here)\n\t\t\titem.update();\t\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Measure code time execution/README.md",
    "content": "**Fix Script**\n\nScript which is showing how to *measure time execution of specific part of code*. You can use it to check performance of code or track time execution of part of code in Fix Scripts. You can add any amount of measuring parts, based of your needs.\n\n**Example configuration of Fix Script**\n\n![Coniguration](ScreenShot_1.PNG)\n\n**Example execution logs**\n\n![Logs](ScreenShot_2.PNG)\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Measure code time execution/script.js",
    "content": "//Script which shows how to measure time execution of specific parts of code - for example for performance check\n\n//Start time of Fix Script execution\nvar startTime = new Date().getTime();\n\n//First method to get number of users in sys_user table\nvar counter = 0;\nvar userQuery = new GlideRecord('sys_user');\nuserQuery.query();\nwhile (userQuery.next()) {\n    counter++;\n}\ngs.info('[Fix-Script] - There are ' + counter + ' users in sys_user table.');\n\nvar part1 = new Date().getTime();\n\n//Second method to get number of users in sys_user table\nvar counterSecond = 0;\nvar userQuerySecond = new GlideRecord('sys_user');\nuserQuerySecond.query();\ncounterSecond = userQuerySecond.getRowCount();\ngs.info('[Fix-Script] - There are ' + counterSecond + ' users in sys_user table.');\n\n//End time of Fix Script execution\nvar endTime = new Date().getTime();\n\nvar time1 = part1 - startTime;\nvar time2 = endTime - part1;\n\ngs.info('[Fix-Script] - Execution time of startTime->part1: ' + time1 + 'ms');\ngs.info('[Fix-Script] - Execution time of part1->endTime: ' + time2 + 'ms');\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Merge stages or choice/README.md",
    "content": "# Merge Stages/choices into one choice\n\nSometimes we start with flow/process where we have multiple stages for a particular document.\nAfter the document is in production and we merge multiple stages into one to refine process we may need to modify stale data to keep it in sync with new process.\n\nUse old stages and new stages (or choice values)\nE.g.\nOld Stage: New(new), Open(open), Draft(draft)\nNew Stage: Initiate(initiate)\n\nThis script will help to correct data and will update old stages data with new stage value.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Merge stages or choice/script.js",
    "content": "var old_values = ['new', 'open', 'draft'];  // old values to be merged\nvar new_value = 'initiate';  // new Value of your stage\nvar grObj = new GlideRecord('table_name');  // replace table name here\ngrObj.addEncodedQuery('field_nameIN' + old_values.toString());  // add additional query as per your configuration\ngrObj.query();\nwhile (grObj.next()) {\n    grObj.field_name = new_value;   // field_name is your choice field name eg. stage, state etc\n    grObj.setWorkflow(false);  // optional, can be used if you want to avoid running BRs, notifications etc\n    grObj.update();\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Migrate data from one table to another/README.md",
    "content": "# Migrate data from one table to another\n\nSometimes we have to migrate data from one table to another but there has been a lot of customizations on the existing table including the custom columns.\n\nIn order to ensure there is no data loss while migrating data, we must have one backup table **sn_data_fix_backup** where we push data before starting migration and there will be no data loss even in the case of any failure. Below is the xml deinition of the backup table.\n\n```\n<?xml version=\"1.0\" encoding=\"UTF-8\"?><database>\n    <element label=\"Data Fix Backup\" max_length=\"40\" name=\"sn_data_fix_backup\" sizeclass=\"0\" type=\"collection\" create_access=\"true\" read_access=\"true\" delete_access=\"true\" update_access=\"true\">\n        <element label=\"Record ID\" max_length=\"32\" name=\"record_id\" type=\"GUID\"/>\n        <element choice=\"1\" label=\"State\" max_length=\"40\" name=\"state\" type=\"choice\">\n            <choice>\n                <element label=\"Processed\" sequence=\"10\" value=\"processed\"/>\n                <element label=\"Unprocessed\" sequence=\"20\" value=\"unprocessed\"/>\n            </choice>\n        </element>\n        <element label=\"Table name\" max_length=\"80\" name=\"table_name\" type=\"table_name\"/>\n        <element label=\"Values\" max_length=\"30000\" name=\"values\" type=\"string\"/>\n    </element>\n</database>\n```\n\nOne more thing to note here, By default we are inserting record with `Unprocessed` state and once we successfully migrated the record then only we move its state to `Processed` so Customers can easily identify which records has not been migrated/processed yet.\n\nWe are also handling Custom column customization in `cloneColumn` function of the script where we are creating custom columns on the target table.\n\nIf we want to completely deprecate the previous table then we have to update the fields where the previous table is being refered so we are handling this in `updateDictionaryReferences` function and once migartion is completed, we are deprecating the previosu table in `deprecateTable` function.\n\nWe are using `GlideRecordClassSwitcher`function to migrate records from one table to another but this will one migrate data of the columns which are common in both the tables heirarchy so for other fields we have to again populate them again. One important thing to note here is that `GlideRecordClassSwitcher` automatically remove record from the previous table and this will not impact `cascade_rule` of the other tables and **will create record in the new table with the same sys id** so we don't have to worry about references where the record has being references.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Migrate data from one table to another/script.js",
    "content": "var BACKUP_TABLE_NAME = \"sn_data_fix_backup\";\nvar UNPROCESSED_BACKUP_STATE = 0;\nvar PROCESSED_BACKUP_STATE = 1;\n\nvar sourceTable = \"\";\nvar targetTable = \"\";\nvar alternateMapping = { external_id: \"model_number\" };\nvar excludeColumns = [\"external_id\", \"source\"];\n\n// migrate(sourceTable, targetTable, alternateMapping, excludeColumns);\n\nfunction migrate(sourceTable, targetTable, alternateMapping, excludeColumns) {\n  gs.info(\"Upgrade: Migrating '{0}' data.\", sourceTable);\n\n  if (updateDictionaryReferences(sourceTable, targetTable)) {\n    migrateData(sourceTable, targetTable, alternateMapping, excludeColumns);\n\n    if (validateMigration(sourceTable)) deprecateTable(sourceTable);\n    else gs.error(\"Upgrade: Migration of '{0}' was not successful so not deprecating the table.\", sourceTable);\n  }\n\n  gs.info(\"Upgrade: '{0}' migration completed.\", sourceTable);\n}\n\nfunction validateMigration(sourceTable) {\n  gs.info(\"Upgrade: Validating migration for '{0}'.\", sourceTable);\n\n  try {\n    var sourceGr = new GlideRecord(sourceTable);\n    sourceGr.query();\n\n    if (sourceGr.getRowCount() != 0) {\n      gs.info(\"Upgrade: Found {0} records in the old '{1}' table.\", sourceGr.getRowCount(), sourceTable);\n      return false;\n    }\n\n    gs.info(\"Upgrade: Successfully validated migration of '{0}'.\", sourceTable);\n    return true;\n  } catch (ex) {\n    gs.error(\"Upgrade: Exception occurred while validating migration for '{0}'.\", sourceTable);\n  }\n}\n\nfunction updateDictionaryReferences(sourceTableName, targetTableName) {\n  gs.info(\"Upgrade: Starting Dictionary update where '{0}' is being referenced.\", sourceTableName);\n\n  try {\n    // Creating GlideRecord object to access records from sys_dictionary\n    var dictionaryGr = new GlideRecord(\"sys_dictionary\");\n\n    // Using addEncodedQuery to filter the required records\n    dictionaryGr.addEncodedQuery(\"reference=\" + sourceTableName);\n    dictionaryGr.query();\n\n    // Looping through each record and updating 'reference' field\n    while (dictionaryGr.next()) {\n      dictionaryGr.setValue(\"reference\", targetTableName);\n      dictionaryGr.update();\n    }\n\n    gs.info(\"Upgrade: Completed Dictionary update where '{0}' is being referenced.\", sourceTableName);\n\n    return true;\n  } catch (ex) {\n    gs.error(\n      \"Upgrade: Exception occurred during Dictionary update where '{0}' is being referenced: {1}\",\n      sourceTableName,\n      ex\n    );\n\n    return false;\n  }\n}\n\nfunction deprecateTable(tableName) {\n  gs.info(\"Upgrade: starting deprecating '{0}'.\", tableName);\n\n  var deprecationLabel = \" (deprecated)\";\n  var dictGr = new GlideRecord(\"sys_documentation\");\n  dictGr.addQuery(\"name\", tableName);\n  dictGr.addQuery(\"element\", \"\");\n  dictGr.addEncodedQuery(\"labelNOT LIKE(deprecated)\");\n  dictGr.query();\n\n  if (dictGr.next()) {\n    dictGr.setValue(\"label\", dictGr.label + deprecationLabel);\n    dictGr.setValue(\"plural\", dictGr.plural + deprecationLabel);\n    dictGr.setValue(\"hint\", dictGr.hint + deprecationLabel);\n    dictGr.update();\n    gs.info(\"Upgrade: Successfully deprecated '{0}'\", tableName);\n  } else {\n    gs.info(\"Upgrade: No table '{0}' found to deprecate\", tableName);\n  }\n}\n\nfunction cloneColumn(sourceTableName, targetTableName, columnName) {\n  // Get the source column's record\n  var sourceColumnGR = new GlideRecord(\"sys_dictionary\");\n  sourceColumnGR.addQuery(\"name\", sourceTableName);\n  sourceColumnGR.addQuery(\"element\", columnName);\n  sourceColumnGR.query();\n\n  if (sourceColumnGR.next()) {\n    // Create a new sys_dictionary record\n    var colLabel = sourceColumnGR.getValue(\"column_label\");\n    var colName = sourceColumnGR.getValue(\"element\");\n    var colType = sourceColumnGR.getValue(\"internal_type\");\n    var colMaxLength = sourceColumnGR.getValue(\"max_length\");\n    var colReference = sourceColumnGR.getValue(\"reference\");\n    var colDefaultValue = sourceColumnGR.getValue(\"default_value\");\n    var colScopeID = sourceColumnGR.getValue(\"sys_scope\");\n    SncTableEditor.createElement(\n      targetTableName,\n      colLabel,\n      colName,\n      colType,\n      colMaxLength,\n      colReference,\n      colDefaultValue,\n      colScopeID\n    );\n\n    var newDictionaryGR = new GlideRecord(\"sys_dictionary\");\n    newDictionaryGR.addQuery(\"element\", colName);\n    newDictionaryGR.addQuery(\"name\", targetTableName);\n    newDictionaryGR.query();\n\n    var excludeFields = [\n      \"name\",\n      \"column_label\",\n      \"element\",\n      \"internal_type\",\n      \"max_length\",\n      \"reference\",\n      \"default_value\",\n      \"sys_scope\",\n      \"sys_update_name\",\n    ];\n\n    if (newDictionaryGR.next()) {\n      newDictionaryGR.setWorkflow(false);\n      // Loop through all attributes of the source column\n      var all_fields = sourceColumnGR.getFields();\n\n      for (var i = 0; i < all_fields.size(); i++) {\n        var fieldName = all_fields.get(i).getName();\n\n        if (excludeFields.indexOf(fieldName) != -1) {\n          continue;\n        }\n\n        var fieldValue = sourceColumnGR.getValue(fieldName);\n        // Set the attribute value in the new sys_dictionary record\n        newDictionaryGR.setValue(fieldName, fieldValue);\n      }\n\n      // Insert the new sys_dictionary record\n      newDictionaryGR.update();\n\n      gs.info(\"Upgrade: sys_dictionary record updated with sys_id '{0}'.\", newDictionaryGR.getUniqueValue());\n    }\n  } else {\n    gs.error(\"Upgrade: Source column not found with name '{0}' on table '{1}'.\", columnName, sourceTableName);\n  }\n}\n\nfunction getListOfColumnNames(table) {\n  var columnGR = new GlideRecord(table);\n  columnGR.query();\n\n  var fields = columnGR.getFields();\n  var columns = [];\n\n  for (var i = 0; i < fields.size(); i++) {\n    var fieldName = fields.get(i).getName();\n    columns.push(fieldName);\n  }\n\n  return columns;\n}\n\n// function to save the backup of the table records as json string.\nfunction backupRecord(sourceGr, sourceColumns) {\n  try {\n    var values = {};\n\n    for (var i = 0; i < sourceColumns.length; i++) {\n      values[sourceColumns[i]] = sourceGr.getValue(sourceColumns[i]);\n    }\n\n    var backupTableGr = new GlideRecord(BACKUP_TABLE_NAME);\n    backupTableGr.initialize();\n    backupTableGr.setValue(\"record_id\", sourceGr.getUniqueValue());\n    backupTableGr.setValue(\"table_name\", sourceGr.getValue(\"sys_class_name\"));\n    backupTableGr.setValue(\"state\", UNPROCESSED_BACKUP_STATE);\n    backupTableGr.setValue(\"values\", JSON.stringify(values));\n    backupTableGr.insert();\n    gs.info(\n      \"Upgrade: Successfully created backup record of '{0}' table with sys_id '{1}'\",\n      sourceGr.getValue(\"sys_class_name\"),\n      sourceGr.getUniqueValue()\n    );\n\n    return backupTableGr;\n  } catch (ex) {\n    gs.error(\"Upgrade: Exception occurred while creating backup record: {0}\", ex);\n  }\n\n  return false;\n}\n\nfunction backupTable(sourceTableName) {\n  gs.info(\"Upgrade: Starting backing up records of '{0}' table\", sourceTableName);\n\n  try {\n    var sourceColumns = getListOfColumnNames(sourceTableName);\n    var backupGr = new GlideRecord(sourceTableName);\n    backupGr.query();\n\n    while (backupGr.next()) {\n      backupRecord(backupGr, sourceColumns);\n    }\n\n    gs.info(\"Upgrade: Successfully backup records of '{0}' table.\", sourceTableName);\n\n    return true;\n  } catch (ex) {\n    gs.error(\"Upgrade: Exception occurred while backing up data for '{0}' table\", sourceTableName);\n    return false;\n  }\n}\n\nfunction migrateData(sourceTableName, targetTableName, alternateMapping, skipColumns) {\n  try {\n    if (!backupTable(sourceTableName)) return;\n\n    var sourceColumns = getListOfColumnNames(sourceTableName);\n    var targetColumns = getListOfColumnNames(targetTableName);\n    var excludeColumns = skipColumns.concat([\"sys_class_name\", \"sys_update_name\"]);\n\n    for (var i = 0; i < sourceColumns.length; i++) {\n      if (excludeColumns.indexOf(sourceColumns[i]) != -1) {\n        continue;\n      }\n\n      if (targetColumns.indexOf(sourceColumns[i]) == -1) {\n        var targetColumnMapping = alternateMapping[sourceColumns[i]];\n        if (targetColumnMapping != undefined && targetColumns.indexOf(targetColumnMapping) != -1) {\n          continue;\n        }\n\n        try {\n          cloneColumn(sourceTableName, targetTableName, sourceColumns[i]);\n        } catch (ex) {\n          gs.error(\n            \"Upgrade: Exception occurred while creating column '{0}' in table '{1}'.\",\n            sourceColumns[i],\n            targetTableName\n          );\n          return false;\n        }\n      }\n    }\n\n    var backupRecordGr = new GlideRecord(BACKUP_TABLE_NAME);\n    backupRecordGr.addQuery(\"table_name\", sourceTableName);\n    backupRecordGr.query();\n\n    while (backupRecordGr.next()) {\n      var recordId = backupRecordGr.getValue(\"record_id\");\n      var modelGr = new GlideRecord(sourceTableName);\n      modelGr.get(recordId);\n\n      if (gs.nil(modelGr.next())) {\n        continue;\n      }\n\n      var colValues = {};\n\n      for (var j = 0; j < sourceColumns.length; j++) {\n        colValues[sourceColumns[j]] = modelGr.getValue(sourceColumns[j]);\n      }\n\n      // Using GlideRecordClassSwitcher to migrate data from source to the target table\n      var switcher = new GlideRecordClassSwitcher(modelGr, sourceTableName, false);\n      var isSwitchingSuccessful = switcher.switchClass(\n        targetTableName,\n        sourceTableName + \" to \" + targetTableName + \" migration. \"\n      );\n\n      if (gs.nil(isSwitchingSuccessful)) {\n        continue;\n      }\n\n      modelGr = new GlideRecord(targetTableName);\n      modelGr.get(recordId);\n\n      try {\n        modelGr.setWorkflow(false);\n\n        for (var k = 0; k < sourceColumns.length; k++) {\n          var fieldName = sourceColumns[k];\n\n          // exclude populating column if it is excludeColumns list\n          if (excludeColumns.indexOf(fieldName) != -1) {\n            continue;\n          }\n\n          // checking if alternate mapping exists then skip for now\n          if (!gs.nil(alternateMapping[fieldName])) {\n            continue;\n          }\n\n          var fieldValue = colValues[fieldName];\n          modelGr.setValue(fieldName, fieldValue);\n        }\n\n        // setting alternateMapping in different loop to get it prioritize over the existing value\n        for (alternateMappingKey in alternateMapping) {\n          try {\n            modelGr.setValue(alternateMapping[alternateMappingKey], colValues[alternateMappingKey]);\n          } catch (ex) {\n            gs.error(\n              \"Upgrade: Exception occurrred while setting column '{0}' in table '{1}' for sys_id '{2}': {3}\",\n              alternateMappingKey,\n              targetTableName,\n              modelGr.getUniqueValue(),\n              ex\n            );\n          }\n        }\n\n        modelGr.update();\n        backupRecordGr.setValue(\"state\", PROCESSED_BACKUP_STATE);\n        backupRecordGr.update();\n      } catch (ex) {\n        gs.error(\n          \"Upgrade: Exception occurred while inserting data in '{0}' with sys_id '{1}': {2}\",\n          targetTableName,\n          modelGr.getUniquevalue(),\n          ex\n        );\n      }\n    }\n  } catch (ex) {\n    gs.error(\n      \"Upgrade: Exception occurred while migrating data from '{0}' to '{1}': {2}\",\n      sourceTableName,\n      targetTableName,\n      ex\n    );\n  }\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Multiply records from filer breadcrumbs/Multiply records.js",
    "content": "//Enter the table name\nvar table = 'rm_story';\n\n//Enter filer breadcrumbs, the script will multiply the records that match the conditions\nvar breadcrumbs = \"short_descriptionSTARTSWITHexample\";\n\n//The script will create as many duplicates of the filtered records as many user SysIds you add to the \"assignedToUsers\" array\n//Make sure that the user is a member of the Assignment group, or the record will be created without an assignee\n\nvar assignedToUsers = [\"d2dd13ea1b2e919039f811739b4bcbe4\", \"dedd13ea1b2e919039f811739b4bcbdc\"];\n\nvar gr = new GlideRecord(table);\ngr.addEncodedQuery(breadcrumbs);\ngr.query();\nwhile (gr.next()) {\n    //clone the old sysID for copying the attachments later\n    var oldSysId = gr.sys_id.toString();\n\n    for (var i = 0; i < assignedToUsers.length; i++) {\n        //assign a the next available number on the table to the record\n        gr.number = new NumberManager(table).getNextObjNumberPadded();\n        //modify the Assign to value to the one in the array\n        gr.assigned_to = assignedToUsers[i];\n        //create the duplicate, and save the new record's sys_id\n        var newSysId = gr.insert();\n        //copy the attachments from the original record to the duplicated one\n        new GlideSysAttachment().copy(table, oldSysId, table, newSysId);\n        gr.update();\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Multiply records from filer breadcrumbs/README.md",
    "content": "## Multiply records from filer breadcrumbs\n\nUsing this script you can multiply records (such as Story, Incident, SCTASK, ...), while assigning them to a different user. \nThe script will also copy the attachments to the duplicate record.\n\nI use this script to create sample stories / incidents for our new joiners that they can practice on in a non productive instance.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Clear Email Queue/README.md",
    "content": "# Post-clone script for email properties\n\nTo be used as a fix script or background script, meant to help with small adjustments needed per-environment after a clone.\n\nNotes: Make sure you fill in all of the instance names up top, with anything in your instance name before the [.service-now.com]\n\nThis clears out the email queue."
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Clear Email Queue/script.js",
    "content": "//define the email redirect here, just in case any emails get through...\nvar emailRedirect = \"\";\n//define the instance names here\nvar devInstance = \"\";\nvar testInstance = \"\";\nvar learnInstance = \"\";\nvar prodInstance = \"\";\nvar extraInstance = \"\"\n\n//get this instance's name\nvar thisInstance = gs.getProperty(\"instance_name\");\n\nswitch (thisInstance) {\n    case devInstance:\n        setConfig();\n        break;\n    case extraInstance:\n        setConfig();\n        break;\n    case testInstance:\n        setConfig();\n        break;\n    case learnInstance:\n        setConfig();\n        break;\n    case prodInstance:\n        gs.print(\"**** You're running this script in production, are you asking for trouble?\");\n        break;\n    default:\n        gs.print(\"**** I don't understand what this instance is for: \" + thisInstance);\n}\n\nfunction setConfig() {\n    clearEmailQueue();\n\n    gs.print(\"Applied \" + thisInstance + \" Configurations\");\n\n}\n\nfunction clearEmailQueue() {\n    // Clear out the email queue\n    var ignoreEmail = new GlideRecord('sys_email');\n    ignoreEmail.addQuery('type', 'send-ready');\n    ignoreEmail.query();\n    while (ignoreEmail.next()) {\n        ignoreEmail.type = 'send-ignored';\n        gs.print('Email ' + ignoreEmail.subject + ' ignored');\n        ignoreEmail.update();\n    }\n    gs.print(\"Email queue cleared!\");\n}"
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Email Properties Script/README.md",
    "content": "# Post-clone script for email properties\n\nTo be used as a fix script or background script, meant to help with small adjustments needed per-environment after a clone.\n\nNotes: Make sure you fill in all of the instance names up top, with anything in your instance name before the [.service-now.com]\n\nThis makes sure your email properties are disabled."
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Email Properties Script/script.js",
    "content": "//define the email redirect here, just in case any emails get through...\nvar emailRedirect = \"\";\n//define the instance names here\nvar devInstance = \"\";\nvar testInstance = \"\";\nvar learnInstance = \"\";\nvar prodInstance = \"\";\nvar extraInstance = \"\"\n\n//get this instance's name\nvar thisInstance = gs.getProperty(\"instance_name\");\n\nswitch (thisInstance) {\n    case devInstance:\n        setConfig();\n        break;\n    case extraInstance:\n        setConfig();\n        break;\n    case testInstance:\n        setConfig();\n        break;\n    case learnInstance:\n        setConfig();\n        break;\n    case prodInstance:\n        gs.print(\"**** You're running this script in production, are you asking for trouble?\");\n        break;\n    default:\n        gs.print(\"**** I don't understand what this instance is for: \" + thisInstance);\n}\n\nfunction setConfig() {\n    setEmailProperties();\n\n    gs.print(\"Applied \" + thisInstance + \" Configurations\");\n\n}\n\nfunction setEmailProperties() {\n    //disable email notifications\n    gs.setProperty(\"glide.email.read.active\", true);\n    gs.setProperty(\"glide.email.smtp.active\", false);\n\n    //Sets outbound email to test inbox\n    gs.setProperty(\"glide.email.test.user\", emailRedirect);\n\n    var afterEmailProperty = gs.getProperty(\"glide.email.test.user\");\n    gs.print(\"The test email account has been set to \" + afterEmailProperty + \".\");\n}"
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Set Banner Name/README.md",
    "content": "# Post-clone script for email properties\n\nTo be used as a fix script or background script, meant to help with small adjustments needed per-environment after a clone.\n\nNotes: Make sure you fill in all of the instance names up top, with anything in your instance name before the [.service-now.com]\n\nThis sets the banner description (that appears at the top of the instance) to the instance name and the date/time of the last clone. Helps keep users of that sub-prod instance informed of when the last clone date/time."
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Set Banner Name/script.js",
    "content": "//define the email redirect here, just in case any emails get through...\nvar emailRedirect = \"\";\n//define the instance names here\nvar devInstance = \"\";\nvar testInstance = \"\";\nvar learnInstance = \"\";\nvar prodInstance = \"\";\nvar extraInstance = \"\"\n\n//get this instance's name\nvar thisInstance = gs.getProperty(\"instance_name\");\n\nswitch (thisInstance) {\n    case devInstance:\n        setConfig();\n        break;\n    case extraInstance:\n        setConfig();\n        break;\n    case testInstance:\n        setConfig();\n        break;\n    case learnInstance:\n        setConfig();\n        break;\n    case prodInstance:\n        gs.print(\"**** You're running this script in production, are you asking for trouble?\");\n        break;\n    default:\n        gs.print(\"**** I don't understand what this instance is for: \" + thisInstance);\n}\n\nfunction setConfig() {\n    setHeaderName();\n\n    gs.print(\"Applied \" + thisInstance + \" Configurations\");\n\n}\n\nfunction setHeaderName() {\n    //set header name\n\n    //get the current date\n    var glideDate = GlideDate();\n    gs.info(glideDate.getByFormat(\"dd-MM-yyyy\"));\n    var instance = gs.getProperty(\"instance_name\");\n    //set the \"glide.product.description\" property based on the instance suffix and current date to display in the banner frame\n    gs.setProperty(\"glide.product.description\", \"ServiceNow Environment (Cloned on \" + glideDate.getDisplayValue().replace(\" \", \" @ \") + \")\");\n\n    var finalHeader = gs.getProperty(\"glide.product.description\");\n    gs.print(\"Header set to \" + finalHeader + \".\");\n}"
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Set Instance Banner/README.md",
    "content": "# Set a unique banner to your non-production instance to help users realise where they are :)\n ## Can be used as clean-up script on a clone profile, or run as fix script, background script etc. manually on the target instance after cloning.\n\n\n ### Prerequisites:\n * 1) You need to have the target instance's banner image attached to a record on the source system, e.g. have a knowledge article in production with the banner attached to it.\n * 2) Make sure the table above mentioned table and record and included in your clone!\n * 3) Set the source table name and the source record's sys_id as values for srcTbl and srcRec variables in the below section!\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Post-clone Set Instance Banner/set_instance_banner.js",
    "content": "/**\n* You can add this as clean-up script to a clone profile or run as fix script, background script etc. manually on the target instance after cloning.\n*\n*\n* Prerequisites:\n*\n* 1) You need to have the target instance's banner image attached to a record on the source system,  \n* e.g. have a knowledge article in production with the banner attached to it.\n*\n* 2) Make sure the table above mentioned table and record and included in your clone!\n*\n* 3) Set the source table name and the source record's sys_id as values for srcTbl and srcRec variables in the below section!\n**/\n\n/** MAKE SURE TO SET THESE 2 VARIABLES **/\nvar srcTbl = ''; // TABLE NAME, e.g. kb_knowledge\nvar srcRec = ''; // SYS ID of the record that you have attached the target instance's banner\n\n// Copy attachment from KBA\nGlideSysAttachment.copy(srcTbl,srcRec,'sys_properties','3458de2aff7102007729ffffffffff7c'); // glide.product.image.light\nGlideSysAttachment.copy(srcTbl,srcRec,'sys_properties','71e1b8dac0a8016a01ea6a1ca634c46d'); // glide.product.image\n\n// Get attachment sys_ids\nvar lightIMG = new GlideRecord('sys_attachment');\nlightIMG.addQuery('table_sys_id','3458de2aff7102007729ffffffffff7c');\nlightIMG.setLimit(1);\nlightIMG.query();\nvar lightIMGID = '';\nwhile(lightIMG.next()){\n  lightIMGID = lightIMG.sys_id;\n}\n\nvar IMG = new GlideRecord('sys_attachment');\nIMG.addQuery('table_sys_id','71e1b8dac0a8016a01ea6a1ca634c46d ');\nIMG.setLimit(1);\nIMG.query();\nvar IMGID = '';\nwhile(IMG.next()){\n  IMGID = IMG.sys_id;\n}\n\n// Set attachment sys_id in respective properties\nvar propLightIMG = new GlideRecord('sys_properties');\npropLightIMG.get('3458de2aff7102007729ffffffffff7c');\npropLightIMG.setValue('value',lightIMGID + '.iix');\npropLightIMG.update();\n\nvar propIMG = new GlideRecord('sys_properties');\npropIMG.get('71e1b8dac0a8016a01ea6a1ca634c46d');\npropIMG.setValue('value',IMGID + '.iix');\npropIMG.update();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Reject RITM via fix script/README.md",
    "content": "This fix script helps to reject the RITM in a scenario where workflow not triggered properly, so we can use this script manually to reject the RITM.\nRecently I also landed up into the same situation where workflow not able to the reject the RITM so at that point of time this script was a life saver to without having any business impact.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Reject RITM via fix script/Reject RITM.js",
    "content": "var item = new GlideRecord(\"sc_req_item\");\nitem.addQuery(\"sys_id\",\"31cd7552db252200a6a2b31be0b8f55c\"); // sys_id of the RITM\nitem.query();\nif(item.next()){\n  var approval = new GlideRecord(\"sysapproval_approver\");\n  approval.addQuery(\"document_id\",item.getUniqueValue()); // Get the sys_Id of document Id field on approval table\n  approval.query();\n  if(approval.next()){\n    approval.state=\"Rejected\";\n    item.apprpval=\"Rejected\";\n  }\n  item.setWorkflow(false);\n  item.update();\n  approval.update(); // Update the record on approval table.\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Remove User Groups/README.md",
    "content": "contribution\nA fix script is used to remove inactive users from the specified list of Groups. The script checks for users who have been inactive in the system more than 7 days from the current date and removes them from the respective groups accordingly. If users are inactive within 7 days then users will skip for deletion.\n\nAdditionally, if any of these users are assigned to existing open incidents, the \"Assigned to\" field on those open incidents will be cleared.\n\nGroups:\nIT ServiceNow L1 IT ServiceNow L2 IT ServiceNow L3\n\nFix script Goal:\nFix script 1name - Remove Inactive Users from Groups Record for Rollback - Enable check box\n\nThe goal of Fix script is to clean up inactive users from the above mentioned groups in ServiceNow, and unassign anyincidents they were responsible for — but only if they have been inactive for more than 7 days.\n\nTaking Groups sysid in system Property with \",\" seperator\n\nQueries group members (sys_user_grmember) where the group matches one of the specified groups.\n\nSkips for Active users , Inactive users who were updated in the last 7 days.\n\nFinds Incident assigned to the user that are not closed (assuming state = 3 means closed). Clears the assigned_to field and updates the record.\n\nSystem Property : Store groups sysid.\n\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Remove User Groups/removeuser.js",
    "content": "var SEVEN_DAYS_AGO = new GlideDateTime();\n\nSEVEN_DAYS_AGO.addDaysUTC(-7);\n \n//Get group sys_ids from system property\n\nvar groupSysIdsStr = gs.getProperty('inactiveuser.groupname');\n\nif (!groupSysIdsStr) {\n\n    \n\n\n}\n \nvar GroupSysIds = groupSysIdsStr.split(','); // Split grp sysid with ,\n \n//group members in those groups\n\nvar groupMemberGR = new GlideRecord('sys_user_grmember');\n\ngroupMemberGR.addQuery('group', 'IN', GroupSysIds.join(','));\n\ngroupMemberGR.query();\n \nwhile (groupMemberGR.next()) {\n\n    var userID = groupMemberGR.getValue('user');\n\n    if (!userID) continue;\n \n    var userGR = new GlideRecord('sys_user');\n\n    if (!userGR.get(userID)) continue;\n \n    if (userGR.active) continue;        // Active user skip\n \n    var updatedOn = new GlideDateTime(userGR.sys_updated_on);\n\n    if (updatedOn.compareTo(SEVEN_DAYS_AGO) >= 0) {   // User was updated within 7 days, so skip\n\n       // gs.info(\"Skipping user: \" + userGR.name + \" - updated on \" + userGR.getDisplayValue('sys_updated_on'));\n\n        continue;\n\n    }\n \n    var groupName = groupMemberGR.group.getDisplayValue();\n \n   \n \n    //Unassign Incident record\n\n    var inc = new GlideRecord('incident');\n\n    inc.addQuery('assigned_to', userID);\n   \n\n    inc.addQuery('state', '!=', '3');\n\n    inc.query();\n\n    while (inc.next()) {\n        \n\n        inc.assigned_to = '';\n\n        inc.update();\n\n       \n\n    }\n \n    \n \n    // Remove user from group\n\n    gs.info(\"Removing user: \" + userGR.name + \" from group: \" + groupName);\n\n    groupMemberGR.deleteRecord();\n\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Remove extra spaces/readme.md",
    "content": "## ServiceNow Fix Script: Remove Extra Spaces\nA generic ServiceNow fix script to clean data by removing extra whitespace from any specified field on any table.\n\n### Problem It Solves\nThis script resolves data integrity issues caused by inconsistent user input, such as:\n- Leading or trailing spaces (e.g., \" Hello World \").\n- Multiple spaces between words (e.g., \"Hello   World\").\n  \n### How to use\n1. Create and Configure the Fix Script\n    - First, create a new Fix Script in your ServiceNow instance (**System Definition > Fix Scripts**) add past the code\n\n2. Add your table name and field that you want to clean up\n    - Before running, you must update the following variables inside the script to define your target:\n      ```js\n      var tableName = 'incident'; // <-- CHANGE THIS to your table name\n      var fieldName = 'short_description'; // <-- CHANGE THIS to your field name\n      ```\n\n3. Change `processRecords` value and run\n    - To see what changes will be made without actually updating records, ensure the `processRecords` variable in the script is set to `false`\n      ```js\n      var processRecords = false;\n      ```\n    - To actually do the update, change the `processRecords` variable to `true` and run the script\n      ```js\n      var processRecords = true;\n      ```\n\n4. Run the script\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Remove extra spaces/removeSpaces.js",
    "content": "/**\n * Fix Script: Remove Extra Spaces from a Field\n *\n * Description:\n * This script removes leading/trailing spaces and collapses multiple\n * consecutive spaces into a single space for a specified field on a specified table.\n *\n * INSTRUCTIONS:\n * 1. Update the tableName and fieldName with the table and field you want to remove spaces from, usually fieldName will be name or short_description\n * 2. Set 'processRecords' to false for a preview, set to true to do the updates\n */\n\n(function () {\n    // Set to true to perform the update, false to just preview the changes\n    var processRecords = false;\n\n    var tableName = 'incident'; // Add your table name\n    var fieldName = 'short_description'; // Add the field that might contain leading spaces\n\n    var recordsUpdated = 0;\n\n    if (gs.nil(tableName) || gs.nil(fieldName)) {\n        gs.print('Please set the table and field name');\n        return;\n    }\n\n    gs.info('Starting space removal process for table: [' + tableName + '], field: [' + fieldName + ']');\n    gs.info('Run mode: ' + (processRecords ? 'UPDATE' : 'PREVIEW'));\n\n    try {\n        var gr = new GlideRecord(tableName);\n        // Only query records where the target field is not empty\n        gr.addNotNullQuery(fieldName);\n        gr.query();\n\n        while (gr.next()) {\n            var originalValue = gr.getValue(fieldName);\n\n            // Replace all occurrences of 2 or more spaces with a single space and trim leading/trailing whitespace\n            var cleanedValue = originalValue.replace(/\\s\\s+/g, ' ').trim();\n\n            // Check if the value has actually changed\n            if (originalValue !== cleanedValue) {\n                gs.print('Record sys_id: ' + gr.getUniqueValue() + ' (Number: ' + gr.getDisplayValue('number') + ')\\n' +\n                    '---> Original: \"' + originalValue + '\"\\n' +\n                    '---> Cleaned:  \"' + cleanedValue + '\"\\n'\n                );\n\n                if (processRecords) {\n                    gr.setValue(fieldName, cleanedValue);\n                    gr.setWorkflow(false);\n                    gr.autoSysFields(false);\n                    gr.update();\n                    recordsUpdated++;\n                }\n            }\n        }\n\n        gs.print('Space removal process finished.');\n        if (processRecords) {\n            gs.info(recordsUpdated + ' records were updated.');\n        } else {\n            gs.print('This was a PREVIEW run. No records were updated. To apply changes, set the \"processRecords\" variable to true.');\n        }\n\n    } catch (e) {\n        gs.error('An error occurred during the space removal script: ' + e.message);\n    }\n})();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Remove leading and trailing spaces/README.md",
    "content": "## Fix script to remove leading and trailing spaces from a field in bulk\n\n- we often encounter in a situation that a field has leading or trailing spaces and we want to remove it\n- This fix script helps to remove leading and trailing spaces from a field in bulk number.\n- Just for an example I have taken incident table and short description as a field.\n- feel free to modify and use it as per your requirement.\n  \n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Remove leading and trailing spaces/RemoveLeadingTrailingSpaces.js",
    "content": "var page = 1;\nvar count = 0;\nvar infoMsg = 'Fix Script to remove leading or trailing spaces from short_description. \\n';\nvar sd = '';  //sd inshort for shortdescription \n\n\nvar regExSpacecheck = /^\\s+|\\s+$/g;  //regular expression to check if a sentence has leading and trailing spaces.\n\nvar grInc = new GlideRecord('incident');\ngrInc.addEncodedQuery('short_descriptionISNOTEMPTY');\ngrInc.query();\nwhile (grInc.next()) {\n\n    if (regExSpacecheck.test(grInc.short_description)) {\n\n        infoMsg += 'incident record found with leading or trailing space for short_description: ' + grInc.sys_id + '\\n';\n        infoMsg += 'Sd ' + grInc.short_description + 'Contains leading or trailing spaces \\n';\n        sd = grInc.short_description;\n        sd = sd.trim();    //trimmed value of short description\n        infoMsg += 'sd is ' + sd + '\\n';\n\n        if (regExSpacecheck.test(sd)) {\n            infoMsg += 'sd: ' + sd + ' still has a trailing space\\n';\n        } else {\n            infoMsg += 'sd: ' + sd + ' no longer contains trailing space \\n';\n        }\n        //Replace the value for u_location_svid with the trimmed version\n        grInc.setValue('short_description', sd);\n        grInc.setWorkflow(false);\n        grInc.update();\n\n        count++;\n\n        if (count == 50) {\n            gs.info(infoMsg);\n            page++;\n            count = 0;\n            infoMsg = 'Fix Script to remove leading or trailing spaces from short_description  (' + page + ')\\n';\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Replace inactive group managers with group members/script.js",
    "content": "/*\nCheck if manager of group is inactive.\nReplace the manager with oldest group member.\n*/\nfunction replacewithActiveManager() {\n    var inactiveMgrGrp = new GlideRecord('sys_user_group'); // Glide group Table\n    inactiveMgrGrp.addEncodedQuery('manager.active=false'); // get groups with inactive managers\n    inactiveMgrGrp.query();\n    while (inactiveMgrGrp.next()) {\n        inactiveMgrGrp.setValue('manager', getOlderGroupMember(inactiveMgrGrp).sys_id); // set inactive manager with active group member.\n        inactiveMgrGrp.autoSysFields(false);\n        inactiveMgrGrp.update();\n        gs.info(\"Group \" + inactiveMgrGrp.name + \" manager changed to \" + getOlderGroupMember(inactiveMgrGrp).name);\n    }\n\n    /*\n    input: group, type = string.\n    Function return the older group member user record\n    */\n    function getOlderGroupMember(grp) {\n        var getUser = new GlideRecord('sys_user_grmember'); // Glide group member table\n        getUser.addEncodedQuery('user.active=true^group=' + grp.getUniqueValue()); // encoded query to get group member.\n        getUser.orderByAsc('sys_created_on'); // get oldest added group member\n        getUser.query();\n        if (getUser.next())\n            return getUser.user.getRefRecord();\n        else\n            gs.info(\"Group \" + grp.name + \" does not have any active user\"); // incase there is no active user in group\n    }\n}\nreplacewithActiveManager(); // main function to replace inactive manager.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Restart Flow (CatalogItem)/README.md",
    "content": "# Restart a Catalog Item Flow\n\nTo be used as a fix script or background script to restart a flow for a catalog item or selection of catalog items.  Typically this might be used in a scenarion in which you've modified a Flow and need to ensure that existing items are running the current version of the flow.\n\nNote: This will RESTART your flows from the beginning.  So if your item is in progress you may see some undesirable behavior.  Also keep in mind any notifications you may have set to fire from the Flow.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Restart Flow (CatalogItem)/restart_flow.js",
    "content": "var ritm = new GlideRecord('sc_req_item');\nvar enc = '';\nritm.addEncodedQuery(enc);\nritm.query();\nwhile (ritm.next()){\n\tritm.state = '1';\n\tritm.approval = 'requested';\n\tritm.setWorkflow(false);\n\tritm.autoSysFields(false);\n\tritm.update();\n\n\t// Execute the global flow called test_flow \n\tsn_flow_trigger.FlowTriggerAPI.fireCatalogTrigger('flow_name', ritm);\n\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Restore From Audit History/README.md",
    "content": "This script retrieves the most recent old value of a specified field from the audit history of a given record in ServiceNow. \nIt queries the sys_audit table, filtering by the table name, field name, and document key (record ID). \nThe results are ordered by the creation date in descending order, and only the latest entry is returned.\n\nPre-requisite\nMake Sure columns of sys_audit table - (tablename, fieldname & documentkey) are indexed for better query result.\n\nUsage Example\nTo use the getAuditHistoryData function, you can retrieve the previous state of an incident and update it accordingly. Here's how you might implement it:\nRetrieve Previous State: Use the function to get the last state before it was changed to \"canceled.\"\nUpdate the Record: Set the incident state back to the retrieved value.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Restore From Audit History/restore_from_audit_fix.js",
    "content": "/*************\nPre-requisite\nMake Sure columns of sys_audit table - (tablename, fieldname & documentkey) are indexed for better query result.\n**************/\nfunction getAuditHistoryData(id, field, table) {\n    // Create a new GlideRecord object for the 'sys_audit' table\n    var grSysAudit = new GlideRecord('sys_audit');\n    \n    // Add query conditions to filter by table name, field name, and document key (record ID)\n    grSysAudit.addEncodedQuery(\"tablename=\" + table + \"^fieldname=\" + field + \"^documentkey=\" + id);\n    \n    // Order the results by the creation date in descending order\n    grSysAudit.orderByDesc('sys_created_on');\n    \n    // Limit the results to the most recent entry\n    grSysAudit.setLimit(1);\n    \n    // Execute the query\n    grSysAudit.query();\n    \n    // Iterate through the results (should only be one due to limit)\n    while (grSysAudit.next()) {\n        // Return the old value of the field from the audit record\n        return grSysAudit.getValue('oldvalue');\n    }\n}\n\n/*************\nExample Script to Invoke the Function and Update the Record\nScenario: Revert the incident state to its previous value after it was mistakenly marked as canceled.\n**************/\n\nvar grIncident = new GlideRecord('incident');\nif (grIncident.get('<incident_sys_id>')) {\n  grIncident.state = getAuditHistoryData('incident_sys_id', 'state', 'incident');\n}\n\n/****************\nGeneric Script\n****************/\nvar tableName= '<your_table_name>';\nvar recordSysId = '<record_sys_id>';\nvar field = 'field_name';\nvar grIncident = new GlideRecord(tableName);\nif (grIncident.get(recordSysId)) {\n  grIncident[field] = getAuditHistoryData(recordSysId, field, tableName);\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Run Subscription Job On Demand/README.md",
    "content": "# Usage\nIf you wish to update/re-calculate your allocated entitlements on the subscription management dashboard, execute the following two lines of code. You can run this server script either in the background script or maintain it as a fix script and run it on-demand.\n\n```\nvar summarizer = new SNC.SubscriptionSummarizer();\n\t\t             summarizer.runSummary();\n```\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Run Subscription Job On Demand/code.js",
    "content": "var summarizer = new SNC.SubscriptionSummarizer();\nsummarizer.runSummary();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/SchemaGenerator/README.md",
    "content": "The mentioned script provides a schema for any table in ServiceNow."
  },
  {
    "path": "Specialized Areas/Fix scripts/SchemaGenerator/cmdb_ci_server_schema.json",
    "content": "{\n    \"classification\": {\n        \"label\": \"Classification\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"Critical Infrastructure\": \"Critical Infrastructure\",\n            \"Development\": \"Development\",\n            \"Development Test\": \"Development Test\",\n            \"Disaster Recovery\": \"Disaster Recovery\",\n            \"Production\": \"Production\",\n            \"UAT\": \"UAT\"\n        }\n    },\n    \"dr_backup\": {\n        \"label\": \"Disaster backup\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmdb_ci_server\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"host_name\": {\n        \"label\": \"Host name\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"maintenance_schedule\": {\n        \"label\": \"Maintenance schedule\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmn_schedule\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"firewall_status\": {\n        \"label\": \"Firewall status\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"DMZ\": \"DMZ\",\n            \"Internet\": \"Internet\",\n            \"Intranet\": \"Intranet\"\n        }\n    },\n    \"dns_domain\": {\n        \"label\": \"DNS Domain\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"assigned\": {\n        \"label\": \"Assigned\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"purchase_date\": {\n        \"label\": \"Purchased\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date\",\n        \"active_status\": \"1\"\n    },\n    \"life_cycle_stage\": {\n        \"label\": \"Life Cycle Stage\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"life_cycle_stage\",\n        \"reference_field_max_length\": \"40\"\n    },\n    \"short_description\": {\n        \"label\": \"Description\",\n        \"max_length\": \"1000\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"cd_speed\": {\n        \"label\": \"CD Speed\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"floppy\": {\n        \"label\": \"Floppy\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"3.5 inch\": \"3.5 inch\",\n            \"5.25 inch\": \"5.25 inch\",\n            \"None\": \"None\"\n        }\n    },\n    \"managed_by\": {\n        \"label\": \"Managed by\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"os_domain\": {\n        \"label\": \"OS Domain\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"last_discovered\": {\n        \"label\": \"Most recent discovery\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"can_print\": {\n        \"label\": \"Can Print\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"sys_class_name\": {\n        \"label\": \"Class\",\n        \"max_length\": \"80\",\n        \"choice_list\": true,\n        \"internal_type\": \"sys_class_name\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"A10 Load Balancer\": \"cmdb_ci_lb_a10\",\n            \"ACE\": \"cmdb_ci_lb_ace\",\n            \"AIX Server\": \"cmdb_ci_aix_server\",\n            \"Alteon\": \"cmdb_ci_lb_alteon\",\n            \"CIM Server\": \"cmdb_ci_cim_server\",\n            \"Cisco CSM\": \"cmdb_ci_lb_cisco_csm\",\n            \"Cisco CSS\": \"cmdb_ci_lb_cisco_css\",\n            \"Cisco GSS\": \"cmdb_ci_lb_cisco_gss\",\n            \"Citrix Netscaler\": \"cmdb_ci_lb_netscaler\",\n            \"Cloud Host\": \"cmdb_ci_cloud_host\",\n            \"Data Power Hosting Server\": \"cmdb_ci_datapower_server\",\n            \"ESX Server\": \"cmdb_ci_esx_server\",\n            \"F5 BIG-IP\": \"cmdb_ci_lb_bigip\",\n            \"F5 BigIP GTM\": \"cmdb_ci_lb_f5_gtm\",\n            \"F5 BigIP LTM\": \"cmdb_ci_lb_f5_ltm\",\n            \"HPUX Server\": \"cmdb_ci_hpux_server\",\n            \"Hyper-V Server\": \"cmdb_ci_hyper_v_server\",\n            \"IBM Mainframe\": \"cmdb_ci_mainframe\",\n            \"IBM Mainframe LPAR\": \"cmdb_ci_mainframe_lpar\",\n            \"IBM zOS server\": \"cmdb_ci_ibm_zos_server\",\n            \"ISA Server\": \"cmdb_ci_lb_isa\",\n            \"ISAM Server\": \"cmdb_ci_isam_server\",\n            \"Linux Server\": \"cmdb_ci_linux_server\",\n            \"Load Balancer\": \"cmdb_ci_lb\",\n            \"Netware Server\": \"cmdb_ci_netware_server\",\n            \"Network Appliance Hardware\": \"cmdb_ci_net_app_server\",\n            \"Network Load Balancer\": \"cmdb_ci_lb_network\",\n            \"OS/X Server\": \"cmdb_ci_osx_server\",\n            \"Radware Load Balancer\": \"cmdb_ci_lb_radware\",\n            \"Server\": \"cmdb_ci_server\",\n            \"Server Chassis\": \"cmdb_ci_chassis_server\",\n            \"Server Hardware\": \"cmdb_ci_server_hardware\",\n            \"Server Tape Unit\": \"cmdb_ci_tape_server\",\n            \"Solaris Server\": \"cmdb_ci_solaris_server\",\n            \"Storage Node Element\": \"cmdb_ci_storage_node_element\",\n            \"Storage Server\": \"cmdb_ci_storage_server\",\n            \"UNIX Server\": \"cmdb_ci_unix_server\",\n            \"Virtualization Server\": \"cmdb_ci_virtualization_server\",\n            \"VMware vCenter Server Object\": \"cmdb_ci_vcenter_server_obj\",\n            \"Windows Server\": \"cmdb_ci_win_server\"\n        }\n    },\n    \"manufacturer\": {\n        \"label\": \"Manufacturer\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"core_company\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"cpu_count\": {\n        \"label\": \"CPU count\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"vendor\": {\n        \"label\": \"Vendor\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"core_company\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"life_cycle_stage_status\": {\n        \"label\": \"Life Cycle Stage Status\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"life_cycle_stage_status\",\n        \"reference_field_max_length\": \"40\"\n    },\n    \"model_number\": {\n        \"label\": \"Model number\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"assigned_to\": {\n        \"label\": \"Assigned to\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"start_date\": {\n        \"label\": \"Start date\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"os_version\": {\n        \"label\": \"OS Version\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"serial_number\": {\n        \"label\": \"Serial number\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"cd_rom\": {\n        \"label\": \"CD\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"support_group\": {\n        \"label\": \"Support group\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user_group\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"unverified\": {\n        \"label\": \"Requires verification\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"correlation_id\": {\n        \"label\": \"Correlation ID\",\n        \"max_length\": \"512\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"attributes\": {\n        \"label\": \"Attributes\",\n        \"max_length\": \"65000\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"asset\": {\n        \"label\": \"Asset\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"alm_asset\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"form_factor\": {\n        \"label\": \"Form factor\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"Blade\": \"Blade\",\n            \"Desktop\": \"Desktop\",\n            \"Hand Held\": \"Hand Held\",\n            \"Laptop\": \"Laptop\",\n            \"Rack Mount\": \"Rack Mount\",\n            \"Stand Alone\": \"Stand Alone\"\n        }\n    },\n    \"cpu_core_count\": {\n        \"label\": \"CPU core count\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"skip_sync\": {\n        \"label\": \"Skip sync\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"product_instance_id\": {\n        \"label\": \"Product instance identifier\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"most_frequent_user\": {\n        \"label\": \"Most frequent logged in user\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"attestation_score\": {\n        \"label\": \"Attestation Score\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"sys_updated_by\": {\n        \"label\": \"Updated by\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"sys_created_on\": {\n        \"label\": \"Created\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"sys_domain\": {\n        \"label\": \"Domain\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"domain_id\",\n        \"active_status\": \"1\",\n        \"reference_table\": null,\n        \"reference_field_max_length\": \"32\"\n    },\n    \"cpu_type\": {\n        \"label\": \"CPU type\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"install_date\": {\n        \"label\": \"Installed\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"asset_tag\": {\n        \"label\": \"Asset tag\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"hardware_substatus\": {\n        \"label\": \"Substatus\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"Available\": \"available\",\n            \"In Use\": \"in_use\",\n            \"Lost\": \"lost\",\n            \"Ordered\": \"ordered\",\n            \"Repairable\": \"repairable\",\n            \"Awaiting Vendor Delivery\": \"awaiting_vendor_delivery\",\n            \"Disposable\": \"disposable\",\n            \"Reserved\": \"reserved\",\n            \"Sold\": \"sold\",\n            \"Credit\": \"credit\",\n            \"Received\": \"received\",\n            \"Dereferenced\": \"dereferenced\",\n            \"Divested\": \"divested\",\n            \"Stolen\": \"stolen\",\n            \"Donated\": \"donated\",\n            \"Scrapped\": \"scrapped\",\n            \"Duplicate Error\": \"duplicate_error\"\n        }\n    },\n    \"fqdn\": {\n        \"label\": \"Fully qualified domain name\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"change_control\": {\n        \"label\": \"Approval group\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user_group\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"internet_facing\": {\n        \"label\": \"Internet Facing\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"delivery_date\": {\n        \"label\": \"Order received\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"hardware_status\": {\n        \"label\": \"Hardware Status\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"Installed\": \"installed\",\n            \"In Maintenance\": \"in_maintenance\",\n            \"In Stock\": \"in_stock\",\n            \"Out of Stock\": \"out_of_stock\",\n            \"In Transit\": \"in_transit\",\n            \"Defective\": \"defective\",\n            \"In Disposition\": \"in_disposition\",\n            \"Retired\": \"retired\",\n            \"On Order\": \"on_order\",\n            \"Pending Install\": \"pending_install\",\n            \"Pending Repair\": \"pending_repair\",\n            \"Pending Transfer\": \"pending_transfer\",\n            \"Stolen\": \"stolen\"\n        }\n    },\n    \"install_status\": {\n        \"label\": \"Install Status\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"Absent\": \"100\",\n            \"In Maintenance\": \"3\",\n            \"In Stock\": \"6\",\n            \"Installed\": \"1\",\n            \"On Order\": \"2\",\n            \"Pending Install\": \"4\",\n            \"Pending Repair\": \"5\",\n            \"Retired\": \"7\",\n            \"Stolen\": \"8\"\n        }\n    },\n    \"supported_by\": {\n        \"label\": \"Supported by\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"name\": {\n        \"label\": \"Name\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"subcategory\": {\n        \"label\": \"Subcategory\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"default_gateway\": {\n        \"label\": \"Default Gateway\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"chassis_type\": {\n        \"label\": \"Chassis type\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"All in One\": \"All in One\",\n            \"Bus Expansion Chassis\": \"Bus Expansion Chassis\",\n            \"Desktop\": \"Desktop\",\n            \"Docking Station\": \"Docking Station\",\n            \"Expansion Chassis\": \"Expansion Chassis\",\n            \"Hand Held\": \"Hand Held\",\n            \"Laptop\": \"Laptop\",\n            \"Low Profile Desktop\": \"Low Profile Desktop\",\n            \"Lunch Box\": \"Lunch Box\",\n            \"Main System Chassis\": \"Main System Chassis\",\n            \"Mini Tower\": \"Mini Tower\",\n            \"Notebook\": \"Notebook\",\n            \"Other\": \"Other\",\n            \"Peripheral Chassis\": \"Peripheral Chassis\",\n            \"Pizza Box\": \"Pizza Box\",\n            \"Portable\": \"Portable\",\n            \"Rack Mount Chassis\": \"Rack Mount Chassis\",\n            \"Sealed-Case PC\": \"Sealed-Case PC\",\n            \"Space-Saving\": \"Space-Saving\",\n            \"Storage Chassis\": \"Storage Chassis\",\n            \"Sub Notebook\": \"Sub Notebook\",\n            \"SubChassis\": \"SubChassis\",\n            \"Tower\": \"Tower\",\n            \"Unknown\": \"Unknown\"\n        }\n    },\n    \"virtual\": {\n        \"label\": \"Is Virtual\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"assignment_group\": {\n        \"label\": \"Change Group\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user_group\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"managed_by_group\": {\n        \"label\": \"Managed By Group\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user_group\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"po_number\": {\n        \"label\": \"PO number\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"checked_in\": {\n        \"label\": \"Checked in\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"sys_class_path\": {\n        \"label\": \"Sys class path\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"sys_class_path\",\n        \"active_status\": \"1\"\n    },\n    \"mac_address\": {\n        \"label\": \"MAC Address\",\n        \"max_length\": \"24\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"company\": {\n        \"label\": \"Company\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"core_company\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"justification\": {\n        \"label\": \"Justification\",\n        \"max_length\": \"80\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"New employee hire\": \"New employee hire\",\n            \"Replace in repair\": \"Replace in repair\",\n            \"Replace stolen\": \"Replace stolen\",\n            \"Replacement\": \"Replacement\",\n            \"Stock replenishment\": \"Stock replenishment\",\n            \"Swap\": \"Swap\",\n            \"Testing\": \"Testing\",\n            \"Upgrade\": \"Upgrade\"\n        }\n    },\n    \"department\": {\n        \"label\": \"Department\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmn_department\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"cost\": {\n        \"label\": \"Cost\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"float\",\n        \"active_status\": \"1\"\n    },\n    \"comments\": {\n        \"label\": \"Comments\",\n        \"max_length\": \"4000\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"os\": {\n        \"label\": \"Operating System\",\n        \"max_length\": \"50\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"AIX\": \"AIX\",\n            \"ESX\": \"ESX\",\n            \"GNU/Linux\": \"GNU/Linux\",\n            \"HP/UX\": \"HP/UX\",\n            \"Hyper-V\": \"Hyper-V\",\n            \"Hyper-V 2012\": \"Hyper-V 2012\",\n            \"Linux Fedora\": \"Linux Fedora\",\n            \"Linux Oracle\": \"Linux Oracle\",\n            \"Linux Red Hat\": \"Linux Red Hat\",\n            \"Linux SuSE\": \"Linux SuSE\",\n            \"Mac OS 10 (OS/X)\": \"Mac OS 10 (OS/X)\",\n            \"Mac OS 8\": \"Mac OS 8\",\n            \"Mac OS 9\": \"Mac OS 9\",\n            \"Mac OS/X\": \"Mac OS/X\",\n            \"OneFS\": \"EMC\",\n            \"OS/400\": \"OS/400\",\n            \"Solaris\": \"Solaris\",\n            \"SunOS\": \"SunOS\",\n            \"Windows\": \"Windows\",\n            \"Windows 2000\": \"Windows 2000\",\n            \"Windows 2000 Advanced Server\": \"Windows 2000 Advanced Server\",\n            \"Windows 2000 Datacenter Server\": \"Windows 2000 Datacenter Server\",\n            \"Windows 2000 Professional\": \"Windows 2000 Professional\",\n            \"Windows 2000 Server\": \"Windows 2000 Server\",\n            \"Windows 2003 Datacenter\": \"Windows 2003 Datacenter\",\n            \"Windows 2003 Enterprise\": \"Windows 2003 Enterprise\",\n            \"Windows 2003 Standard\": \"Windows 2003 Standard\",\n            \"Windows 2003 Web\": \"Windows 2003 Web\",\n            \"Windows 2008 R2 Datacenter\": \"Windows 2008 R2 Datacenter\",\n            \"Windows 95\": \"Windows 95\",\n            \"Windows 98\": \"Windows 98\",\n            \"Windows ME\": \"Windows ME\",\n            \"Windows NT 4.0\": \"Windows NT 4.0\",\n            \"Windows XP\": \"Windows XP\",\n            \"Windows XP Home\": \"Windows XP Home\",\n            \"Windows XP Professional\": \"Windows XP Professional\"\n        }\n    },\n    \"attestation_status\": {\n        \"label\": \"Attestation Status\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"Not Yet Reviewed\": \"Not Yet Reviewed\",\n            \"Attested\": \"Attested\",\n            \"Rejected\": \"Rejected\"\n        }\n    },\n    \"sys_mod_count\": {\n        \"label\": \"Updates\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"monitor\": {\n        \"label\": \"Monitor\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"model_id\": {\n        \"label\": \"Model ID\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmdb_model\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"ip_address\": {\n        \"label\": \"IP Address\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"ip_addr\",\n        \"active_status\": \"1\"\n    },\n    \"duplicate_of\": {\n        \"label\": \"Duplicate Of\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmdb_ci\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"cost_cc\": {\n        \"label\": \"Cost currency\",\n        \"max_length\": \"3\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"EUR\": \"EUR\",\n            \"GBP\": \"GBP\",\n            \"USD\": \"USD\"\n        }\n    },\n    \"order_date\": {\n        \"label\": \"Ordered\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"environment\": {\n        \"label\": \"Environment\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"Development\": \"Development\",\n            \"Production\": \"Production\",\n            \"Test\": \"Test\"\n        }\n    },\n    \"due\": {\n        \"label\": \"Due\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"attested\": {\n        \"label\": \"Attested\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"boolean\",\n        \"active_status\": \"1\"\n    },\n    \"location\": {\n        \"label\": \"Location\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmn_location\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"category\": {\n        \"label\": \"Category\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"fault_count\": {\n        \"label\": \"Fault count\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"lease_id\": {\n        \"label\": \"Lease contract\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"schedule\": {\n        \"label\": \"Schedule\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmn_schedule\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"attested_date\": {\n        \"label\": \"Attested Date\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"operational_status\": {\n        \"label\": \"Operational status\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"Operational\": \"1\",\n            \"Non-Operational\": \"2\",\n            \"Repair in Progress\": \"3\",\n            \"DR Standby\": \"4\",\n            \"Ready\": \"5\",\n            \"Retired\": \"6\"\n        }\n    },\n    \"os_service_pack\": {\n        \"label\": \"OS Service Pack\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"cpu_core_thread\": {\n        \"label\": \"CPU core thread\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"cpu_manufacturer\": {\n        \"label\": \"CPU manufacturer\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"core_company\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"sys_updated_on\": {\n        \"label\": \"Updated\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"discovery_source\": {\n        \"label\": \"Discovery source\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"ACC-Visibility\": \"ACC-Visibility\",\n            \"AgentClientCollector\": \"AgentClientCollector\",\n            \"Altiris\": \"Altiris\",\n            \"Cloud Insights Billing\": \"Cloud Insights Billing\",\n            \"CredentiallessDiscovery\": \"CredentiallessDiscovery\",\n            \"EventManagement\": \"EventManagement\",\n            \"Health Log Analytics\": \"Health Log Analytics\",\n            \"ImportSet\": \"ImportSet\",\n            \"LANDesk\": \"LANDesk\",\n            \"Manual Entry\": \"Manual Entry\",\n            \"ManualStagingTable\": \"ManualStagingTable\",\n            \"MS SMS\": \"MS SMS\",\n            \"OpenView\": \"OpenView\",\n            \"Other Automated\": \"Other Automated\",\n            \"PatternDesigner\": \"PatternDesigner\",\n            \"ServiceNow\": \"ServiceNow\",\n            \"ServiceWatch\": \"ServiceWatch\",\n            \"SNAssetManagement\": \"SNAssetManagement\",\n            \"Tivoli\": \"Tivoli\"\n        }\n    },\n    \"first_discovered\": {\n        \"label\": \"First discovered\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"due_in\": {\n        \"label\": \"Due in\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"1 Day\": \"1 Day\",\n            \"1 Hour\": \"1 Hour\",\n            \"1 Week\": \"1 Week\"\n        }\n    },\n    \"invoice_number\": {\n        \"label\": \"Invoice number\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"gl_account\": {\n        \"label\": \"GL account\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"sys_created_by\": {\n        \"label\": \"Created by\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"warranty_expiration\": {\n        \"label\": \"Warranty expiration\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date\",\n        \"active_status\": \"1\"\n    },\n    \"ram\": {\n        \"label\": \"RAM (MB)\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\"\n    },\n    \"cpu_name\": {\n        \"label\": \"CPU name\",\n        \"max_length\": \"100\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"cpu_speed\": {\n        \"label\": \"CPU speed (MHz)\",\n        \"max_length\": \"15\",\n        \"choice_list\": false,\n        \"internal_type\": \"decimal\",\n        \"active_status\": \"1\"\n    },\n    \"owned_by\": {\n        \"label\": \"Owned by\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"checked_out\": {\n        \"label\": \"Checked out\",\n        \"max_length\": \"40\",\n        \"choice_list\": false,\n        \"internal_type\": \"glide_date_time\",\n        \"active_status\": \"1\"\n    },\n    \"sys_domain_path\": {\n        \"label\": \"Domain Path\",\n        \"max_length\": \"255\",\n        \"choice_list\": false,\n        \"internal_type\": \"domain_path\",\n        \"active_status\": \"1\"\n    },\n    \"disk_space\": {\n        \"label\": \"Disk space (GB)\",\n        \"max_length\": \"15\",\n        \"choice_list\": false,\n        \"internal_type\": \"decimal\",\n        \"active_status\": \"1\"\n    },\n    \"object_id\": {\n        \"label\": \"Object ID\",\n        \"max_length\": \"512\",\n        \"choice_list\": false,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\"\n    },\n    \"business_unit\": {\n        \"label\": \"Business Unit\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"business_unit\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"cost_center\": {\n        \"label\": \"Cost center\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"cmn_cost_center\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"attested_by\": {\n        \"label\": \"Attested By\",\n        \"max_length\": 32,\n        \"choice_list\": false,\n        \"internal_type\": \"reference\",\n        \"active_status\": \"1\",\n        \"reference_table\": \"sys_user\",\n        \"reference_field_max_length\": \"32\"\n    },\n    \"used_for\": {\n        \"label\": \"Used for\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"string\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"Production\": \"Production\",\n            \"Staging\": \"Staging\",\n            \"QA\": \"QA\",\n            \"Test\": \"Test\",\n            \"Development\": \"Development\",\n            \"Demonstration\": \"Demonstration\",\n            \"Training\": \"Training\",\n            \"Disaster recovery\": \"Disaster recovery\"\n        }\n    },\n    \"sys_id\": {\n        \"label\": \"Sys ID\",\n        \"max_length\": \"32\",\n        \"choice_list\": false,\n        \"internal_type\": \"GUID\",\n        \"active_status\": \"1\"\n    },\n    \"os_address_width\": {\n        \"label\": \"OS Address Width (bits)\",\n        \"max_length\": \"40\",\n        \"choice_list\": true,\n        \"internal_type\": \"integer\",\n        \"active_status\": \"1\",\n        \"choice_list_values\": {\n            \"-- None --\": \"\",\n            \"32\": \"32\",\n            \"64\": \"64\"\n        }\n    }\n}"
  },
  {
    "path": "Specialized Areas/Fix scripts/SchemaGenerator/schemaGenerator.js",
    "content": "var table = '<name_of_the_table_for_schema_generation>';    // Ex: incident,cmdb_ci_server,etc.\nvar fields = getTableColumns(table);\nvar fieldGr = new GlideRecord('sys_dictionary');\nfieldGr.addEncodedQuery(fields);\nfieldGr.query();\nvar schema = {};\nvar data = new GlideRecord(table);\ndata.setLimit(1);\ndata.query();\nif (data.next()) {\n    while (fieldGr.next()) {\n        var obj = {};\n        var choices = GlideChoiceList.getChoiceList(table, fieldGr.element);\n        var check = false;\n        if (choices.getSize() > 1) {\n            check = true;\n        }\n        obj['label'] = fieldGr.getValue('column_label');\n        // if custom label is present\n        var customLabel = new GlideRecord('sys_documentation');\n        customLabel.addEncodedQuery('name=' + table + '^element=' + fieldGr.element.toString());\n        customLabel.query();\n        if (customLabel.next()) {\n            obj['label'] = customLabel.label.toString();\n        }\n        obj['max_length'] = (fieldGr.internal_type == 'reference' || fieldGr.internal_type == 'domain_id') ? 32 : fieldGr.getValue('max_length');\n        obj['choice_list'] = check;\n        obj['internal_type'] = fieldGr.getValue('internal_type');\n        obj['active_status'] = fieldGr.getValue('active');\n        if (check == true) {\n            var obj2 = {};\n            for (var i = 0; i < choices.getSize(); i++) {\n                obj2[choices.getChoice(i).getLabel().toString()] = choices.getChoice(i).getValue().toString();\n            }\n            obj['choice_list_values'] = obj2;\n        }\n        if (fieldGr.internal_type == 'reference' || fieldGr.internal_type == 'domain_id') {\n            obj['reference_table'] = fieldGr.getValue('reference');\n            obj['reference_field_max_length'] = fieldGr.getValue('max_length');\n        }\n        schema[fieldGr.element] = obj;\n    }\n}\ngs.print(JSON.stringify(schema));\n\n\nfunction getTableColumns(table) {\n    // Get the ancestors of the given table using the SNC.TableEditor.getTableAncestors function\n    var tableAncestors = SNC.TableEditor.getTableAncestors(table);\n    // Create a new GlideRecord for the 'sys_dictionary' table\n    var gr = new GlideRecord('sys_dictionary');\n    // Add a query to find all records where the 'name' field is one of the ancestors of the given table\n    gr.addQuery('name', 'ONE IN', tableAncestors);\n    // Add a query to exclude any records where the 'element' field is NULL\n    gr.addQuery('element', '!=', 'NULL');\n    // Execute the query\n    gr.query();\n    // Create an empty array called 'sysIds'\n    var sysIds = []; //Changed the variable name 'array' to a more descriptive name 'sysIds'.\n    //Changed the 'new Array()' syntax to an array literal syntax '[]' for consistency and readability.\n    // Loop through each record in the GlideRecord\n    while (gr.next()) {\n        // Add the sys_id of the current record to the 'sysIds' array as a string\n        sysIds.push(gr.sys_id.toString());\n    }\n    // Return a string that consists of 'sys_idIN' followed by a comma-separated list of the sys_id values in the 'sysIds' array\n    //Changed the return statement to explicitly concatenate the string 'sys_idIN' and the sys_id values in the 'sysIds' array using the 'join' method instead of relying on implicit type conversion.\n    return 'sys_idIN' + sysIds.join(',');\n}"
  },
  {
    "path": "Specialized Areas/Fix scripts/Search Results Weight/README.md",
    "content": "# Find the weight of Search Results\n\nThe order in which search results are returned is based on the weight of the object against the searched value.  This script can be run as a background script to determine what results are returned for specific search results and what their weight is in the search.\n\nThis can be used to determine why one item might returned in front of another item.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Search Results Weight/sr_weight.js",
    "content": "var qSet = \"leave,maternity\"; //Search values\n\n\nvar q = qSet.split(\",\");\nvar message = \"\";\nq.sort();\n\nfor (var i=0;i<q.length;i++)\n{\n\t\n\tmessage =  message + \"\\n~~\" + q[i];\n\t\n\tvar sc = new GlideRecord('sc_cat_item');\n\tsc.addQuery('123TEXTQUERY321', q[i]);\n\tsc.addQuery('active',true);\n\tsc.addQuery('sys_class_name', 'NOT IN', 'sc_cat_item_wizard,sc_cat_item_content');\n\tsc.query();\n\tmessage =  message + \"\\n~~~~ FORMS RESULTS \" + q[i] + \"(\"+ sc.getRowCount() +\")\";\n\twhile (sc.next()) \n\t{\n\t\tmessage = message + \"\\n~~~~~~~~ \" +  sc.ir_query_score   + \" \" + sc.name;\t\n\t}\n\t\n\tvar kb = new GlideRecord('kb_knowledge');\n\tkb.addQuery('123TEXTQUERY321', q[i]);\n\tkb.addQuery('workflow_state', 'published');\n\tkb.addNotNullQuery('text');\t// tier 0 ans\n\tkb.setLimit(20);\n\t//kb.addQuery(getAgencyName(), true);\n\tkb.query();\n\tmessage =  message + \"\\n~~~~ KNOWLEDGE RESULTS \" + q[i] + \"(\"+ kb.getRowCount() +\")\";\n\twhile (kb.next()) \n\t{\n\t\tmessage = message + \"\\n~~~~~~~~ \" +  kb.ir_query_score   + \" \" + kb.number + \" \" + kb.short_description;\t\n\t}\t\n}\n\n\ngs.log(message,\"Search Results\");\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Swiss German Language Update/README.md",
    "content": "# Swiss German Language Update\n\nThis Fix Script replaces all \"ß\" special characters generated by the German language plugin (com.snc.i18n.german). \nIn Swiss German, this character does not exist and must be replaced with \"ss\". Currently, unfortunately, there is no dedicated Swiss German language plugin available. \n\n### Instruction\n\nAs a prerequisite the \"regular\" German language plugin (com.snc.i18n.german) must be installed. \nAfter that the Fix Script can be executed, replacing the German special charactrer \"ß\" in the following relevant language tables with \"ss\": \nsys_translated, sys_ui_message, sys_documentation, sys_choice and sys_translated_text. \nI would recommend to run the Fix Script in the background, since the execution might take some time. \n\n\n### Benefits\n\n- Quick fix to automatically replace all customer/end user visible special characters \"ß\" \n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Swiss German Language Update/script.js",
    "content": "//edit all labels in sys_translated table\nvar sys_translated = new GlideRecord(\"sys_translated\");\nsys_translated.addQuery(\"language\", \"de\");\nsys_translated.addQuery(\"label\", \"CONTAINS\", \"ß\");\nsys_translated.query();\nif (sys_translated.hasNext()) {\n    var counter1 = 0;\n    while (sys_translated.next()) {\n        var old_label = sys_translated.label;\n        if (old_label.indexOf('ß') > -1) { //additional check is needed, since GlideRecord query does not uniquely recognize ß character\n            var new_label = old_label.replaceAll(\"ß\", \"ss\");\n            sys_translated.label.setDisplayValue(new_label);\n            sys_translated.update();\n            counter1++;\n        }\n    }\n    gs.log(\"in table sys_translated \" + counter1 + \" records were updated\");\n}\n\n//edit all labels in sys_ui_message table\nvar sys_ui_message = new GlideRecord(\"sys_ui_message\");\nsys_ui_message.addQuery(\"language\", \"de\");\nsys_ui_message.addQuery(\"message\", \"CONTAINS\", \"ß\");\nsys_ui_message.query();\nif (sys_ui_message.hasNext()) {\n    var counter2 = 0;\n    while (sys_ui_message.next()) {\n        var old_message = sys_ui_message.message;\n        if (old_message.indexOf('ß') > -1) { //additional check is needed, since GlideRecord query does not uniquely recognize ß character\n            var new_message = old_message.replaceAll(\"ß\", \"ss\");\n            sys_ui_message.message.setDisplayValue(new_message);\n            sys_ui_message.update();\n            counter2++;\n        }\n    }\n    gs.log(\"in table sys_ui_message \" + counter2 + \" records were updated\");\n}\n\n//edit all labels in sys_documentation table\nvar sys_documentation = new GlideRecord(\"sys_documentation\");\nsys_documentation.addQuery(\"language\", \"de\");\nsys_documentation.addQuery(\"label\", \"CONTAINS\", \"ß\").addOrCondition(\"plural\", \"CONTAINS\", \"ß\")\nsys_documentation.query();\nif (sys_documentation.hasNext()) {\n    var counter3 = 0;\n    while (sys_documentation.next()) {\n        var old_label = sys_documentation.label;\n        var old_plural = sys_documentation.plural;\n        if ((old_label.indexOf('ß') > -1) || (old_label.indexOf('ß') > -1)) { //additional check is needed, since GlideRecord query does not uniquely recognize ß character\n            var new_label = old_label.replaceAll(\"ß\", \"ss\");\n            var new_plural = old_plural.replaceAll(\"ß\", \"ss\");\n            sys_documentation.label.setDisplayValue(new_label);\n            sys_documentation.plural.setDisplayValue(new_plural);\n            sys_documentation.update();\n            counter3++;\n\t\t\tgs.log(new_value);\n\t\t\tgs.log(new_plural);\n        }\n    }\n    gs.log(\"in table sys_documentation \" + counter3 + \" records were updated\");\n}\n\n//edit all labels in sys_choice table\nvar sys_choice = new GlideRecord(\"sys_choice\");\nsys_choice.addQuery(\"language\", \"de\");\nsys_choice.addQuery(\"label\", \"CONTAINS\", \"ß\");\nsys_choice.query();\nif (sys_choice.hasNext()) {\n    var counter4 = 0;\n    while (sys_choice.next()) {\n        var old_label = sys_choice.label;\n        if (old_label.indexOf('ß') > -1) { //additional check is needed, since GlideRecord query does not uniquely recognize ß character\n            var new_label = old_label.replaceAll(\"ß\", \"ss\");\n            sys_choice.label.setDisplayValue(new_label);\n            sys_choice.update();\n            counter4++;\n        }\n    }\n    gs.log(\"in table sys_choice \" + counter4 + \" records were updated\");\n}\n\n//edit all values in sys_translated_text table\nvar sys_translated_text = new GlideRecord(\"sys_translated_text\");\nsys_translated_text.addQuery(\"language\", \"de\");\nsys_translated_text.addQuery(\"value\", \"CONTAINS\", \"ß\");\nsys_translated_text.query();\nif (sys_translated_text.hasNext()) {\n    var counter5 = 0;\n    while (sys_translated_text.next()) { //additional check is needed, since GlideRecord query does not uniquely recognize ß character\n        var old_value = sys_translated_text.value;\n        if (old_value.indexOf('ß') > -1) {\n            var new_value = old_value.replaceAll(\"ß\", \"ss\");\n            sys_translated_text.value.setDisplayValue(new_value);\n            sys_translated_text.update();\n            counter5++;\n        }\n    }\n    gs.log(\"in table sys_translated_text \" + counter5 + \" records were updated\");\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Sync Data Between Instances/README.md",
    "content": "The script leverages the ServiceNow REST API to retrieve records from a specified table on the source instance, then transmits them to the target instance for insertion.\n\nAs an example, it is set up to sync active user records, but it can be easily modified for any other table and filter criteria.\n\n\nUsage:\n\nThe script is configured with the following parameters:\n\ntable: Specifies the name of the table to sync (Example is sys_user).\n\nquery: A GlideRecord query string to filter the records to be synchronized\n\ntargetInstance: The ServiceNow instance to which data will be sent.\n\nuser and password: Credentials for authenticating with the target instance.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Sync Data Between Instances/SyncData.js",
    "content": "//Add below Details\nvar table = \"sys_user\";\nvar query = \"active=true\";\nvar targetInstance = \"dev1234\";\nvar user, password;\n\nsendData(table, targetInstance, query, user, password);\n\nfunction sendData(table, targetInstance, query) {\n    var pload = {};\n    var tableGR = new GlideRecord(table);\n    if (query) {\n        tableGR.addQuery(query);\n    }\n    tableGR.query();\n    while (tableGR.next()) {\n        var dictionaryGR = new GlideRecord('sys_dictionary');\n        dictionaryGR.addQuery('name=' + table);\n        dictionaryGR.query();\n        while (dictionaryGR.next()) {\n            var element = dictionaryGR.element.toString();\n            pload[dictionaryGR.element] = tableGR.getValue(element);\n        }\n        var requestBody = JSON.stringify(pload);\n        //gs.info(requestBody);\n        var request = new sn_ws.RESTMessageV2();\n        request.setEndpoint('https://' + targetInstance + '.service-now.com/api/now/table/' + table);\n        request.setHttpMethod('POST');\n        request.setBasicAuth(user, password);\n        request.setRequestHeader(\"Accept\", \"application/json\");\n        request.setRequestBody(requestBody);\n        var response = request.execute();\n        gs.log(response.getBody());\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Syslog_top10Contributors/README.md",
    "content": "Log[syslog] table Optimization Script ( Server Side)\n - This fix script will execute on syslog table to identify top '10' contributors in last '15 minutes'. \n - This can be altered as per requirement to idenitfy daily top contributors to take action on unnecessary log contibutors\n - This will help to maintain the health of log table and reduce load\n - It will ultimately contribute to your instance performance and reviewing the transaction occured \n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Syslog_top10Contributors/syslog_script.js",
    "content": "/* Create a GlideAggregate on the Syslog table to identify the Top 10 contributors by 'Source name' and 'Number of occurrences on 'Daily' or'any specific interval'.\n\nThis could be vital to maintain the instance performance by regualar optimizing the Syslog table by identifying the top contributors. Based on this syslog table \nCould it be reviewed by owners to make the correct decisions on whether logging is required for these tables?\n\n*/\n\ntopN('syslog', 'source', 10);          //Create a function to identify top 'N' number of records by source. Eg. 10 \n\nfunction topN(pTable, pColumn, pCount) {\n    var ga = new GlideAggregate(pTable);    // query on table required\n    ga.addAggregate('COUNT', pColumn);      // Count the number of records by source to record how many times it generated log\n    ga.orderByAggregate('COUNT', pColumn);  \n    ga.addEncodedQuery('sys_created_onONLast 15 minutes@javascript:gs.beginningOfLast15Minutes()@javascript:gs.endOfLast15Minutes()'); //query for last 15min data\n    ga.query();\n    var i = 0;\n    var stdout = [];\n    stdout.push('\\nTop ' + pCount + ' ' + pColumn + ' values from ' + pTable + '\\n');\n    while (ga.next() && (i++ < pCount)) {\n        stdout.push(ga.getValue(pColumn) + ' ' + ga.getAggregate('COUNT', pColumn));\n    }\n    gs.print(stdout.join(\"\\n\"));         // display data by 'Sourve' and Number of occurance count \n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Update field with value in sys_audit/README.md",
    "content": "Hopefully this is something you never need to use.   It will udpate with either the newest or oldest entry from sys_audit.\n\n## Example\n\n```Javscript\nvar updateArgs = {\n    encodedQuery: 'u_some_cool_field=blahblahblahL',\n    table: 'my_cool_table',\n    updateField: 'field to update in target table',\n    auditField: 'field name in sys_audit',\n    sort: 'DESC'\n}\n\n\ntry {\n\n    gs.print(updateRecords(updateArgs).join('\\n'));\n} catch (ex) {\n    gs.error(ex.message || ex);\n}\n\n```\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Update field with value in sys_audit/updateFromAudit.js",
    "content": "function validateQuery(grToCheck) {\n    return grToCheck.isEncodedQueryValid(grToCheck.getEncodedQuery());\n}\n\nfunction isNullOrEmpty(val) {\n\n    return val == null || val == '' || val == undefined;\n}\n\nfunction validateArgs() {\n    for (var i = 0; i < arguments.length; i++) {\n        if (isNullOrEmpty(arguments[i])) {\n            return false;\n        }\n    }\n    return true;\n}\n\nfunction updateRecords(args) {\n    var tbl = args['table'];\n    var qry = args['encodedQuery'];\n    var updateField = args['updateField'];\n    var auditField = args['auditField'];\n    var sort = args['sort'];\n    \n    if (!validateArgs(tbl, qry, updateField, auditField)) {\n        throw new Error(\"UpdateRecords: Missing or invalid arguments\");\n    }\n\n    var grTicket = new GlideRecord(tbl);\n    grTicket.addEncodedQuery(qry);\n    if (!validateQuery(grTicket)) {\n        throw new Error(\"function requires valid encoded query\");\n    }\n    if (!grTicket.isValid()) {\n        throw new Error(\"Invalid table name\");\n    }\n\n    grTicket.query();\n\n    var auditVal;\n    var asdf = [];\n\n    while (grTicket.next()) {\n\n        auditVal = getAuditValue(tbl, grTicket.getUniqueValue(), auditField,sort);\n\n        if (!isNullOrEmpty(auditVal)) {\n            asdf.push(grTicket.getValue('number') + \":\" + auditVal);\n            grTicket.setValue(updateField, auditVal);\n            grTicket.update();\n        }\n\n\n    }\n\n\n    return asdf;\n}\n\nfunction getAuditValue(tbl, sysid, field, sort) {\n\n    sort == sort || 'ASC';\n    if (isNullOrEmpty(tbl) || isNullOrEmpty(sysid) || isNullOrEmpty(field)) {\n        throw new Error(\"all arguments are required\");\n    }\n    var grAudit = new GlideRecord('sys_audit');\n    grAudit.addQuery('documentkey', sysid);\n    grAudit.addQuery('fieldname', field);\n    grAudit.addNotNullQuery('newvalue');\n\n    if (sort == 'DESC') {\n        grAudit.orderByDesc('sys_created_on');\n    } else {\n        grAudit.orderBy('sys_created_on');\n    }\n\n    grAudit.setLimit(1)\n    grAudit.query();\n    if (grAudit.next()) {\n        return grAudit.getValue('newvalue');\n\n    }\n\n    return;\n\n}\n\n/*  ##################################################################\n############ EXAMPLE:  CHANGE THE BELOW CODE!!!!! #################\n######################################################################\n*/\n\nvar updateArgs = {\n    encodedQuery: 'u_some_cool_field=blahblahblahL',\n    table: 'my_cool_table',\n    updateField: 'field to update in target table',\n    auditField: 'field name in sys_audit',\n    sort: 'DESC\n}\n\n\ntry {\n\n    gs.print(updateRecords(updateArgs).join('\\n'));\n} catch (ex) {\n    gs.error(ex.message || ex);\n}\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/Updateset checker/README.md",
    "content": "# Check all Work in Progress update-sets for Cross Application Scopes\nIf an update-set has more than one application scope it will cause issues when you are previewing the update-set in the target instance. Therefore, it is the best to check the update-sets before closing and retrieving them to the target instance. \n\nThis sample script, checks all the WIP update-sets and find the update-sets that has more than one Application Scope in them. It will show which update-set and whose update is causing it."
  },
  {
    "path": "Specialized Areas/Fix scripts/Updateset checker/Updateset_checker.js",
    "content": "/*\n    Find all the update-sets that are WIP and the customer updates contains more than one applications\n*/\n\n//-------------------------------------------------------------------------------------------------------------\n// If we want specific users e.g. if there are multiple partners and we want only our techs then add them to\n// the list below else clear the list!\n//-------------------------------------------------------------------------------------------------------------\nvar createdByUsers = [\n    // TODO: Add your specific user user_name based on sys_user table\n    ];\n    \n    //-------------------------------------------------------------------------------------------------------------\n    // Go though all the Update-sets that are WIP or alternatively add the filter to grab for specific users\n    // Ignore the Default update-sets\n    //-------------------------------------------------------------------------------------------------------------\n    var grSysUpdateSet = new GlideRecord('sys_update_set');\n    grSysUpdateSet.addQuery('state', 'in progress');\n    grSysUpdateSet.addQuery('name', '!=', 'Default');\n    \n    //-------------------------------------------------------------------------------------------------------------\n    // If users list exist then add it to the query\n    //-------------------------------------------------------------------------------------------------------------\n    if(createdByUsers && createdByUsers.length > 0){\n        var createdByUsersQuery = createdByUsers.join(',').trim(',');\n        grSysUpdateSet.addQuery('sys_created_by', 'IN', createdByUsersQuery);\n    }\n    \n    grSysUpdateSet.query();\n    \n    // Distinct Update-set names\n    var distinctXMLList = [];\n    \n    while (grSysUpdateSet.next()) {\n    \n        //-------------------------------------------------------------------------------------------------------------\n        // Find all the customer updates belongs to this update-set and are different than the update-set application\n        //-------------------------------------------------------------------------------------------------------------\n        var grCustomerUpdates = new GlideRecord('sys_update_xml');\n        grCustomerUpdates.addQuery(\"update_set.sys_id\", grSysUpdateSet.getUniqueValue());\n        grCustomerUpdates.addQuery(\"application\", '!=', grSysUpdateSet.getValue('application'));\n        grCustomerUpdates.query();\n    \n    \n        while (grCustomerUpdates.next()) {\n            \n            // Don't report the same update-set name more than once, as long as it is reported once is enough!!\n            if(distinctXMLList.indexOf(grSysUpdateSet.getValue('name')) == -1){\n                gs.debug('-------------------------------------------------------------------------------------------------------------');\n                gs.debug('Found an update-set that its customer updates has more than one applications (at least)!');\n                gs.debug('-------------------------------------------------------------------------------------------------------------');\n                gs.debug('Update-set name (sys_update_set): ' + grSysUpdateSet.getValue('name'));\n                gs.debug('Update-set application: ' + grSysUpdateSet.getDisplayValue('application'));\n                gs.debug('Customer updates application (sys_update_xml.application): ' + grCustomerUpdates.getDisplayValue('application'));\n                gs.debug('-------------------------------------------------------------------------------------------------------------');\n            \n                distinctXMLList.push(grSysUpdateSet.getValue('name'));\n            }\n        }\n        \n    }"
  },
  {
    "path": "Specialized Areas/Fix scripts/cleanupOrphanedWorkflowContexts/README.md",
    "content": "# Cleanup Orphaned and Stale Workflow Contexts\n\n**Purpose:** Automated maintenance script to identify and cancel stale or orphaned workflow contexts in ServiceNow\n\n## Overview\n\nThis fix script addresses a common ServiceNow instance health issue where workflow contexts remain stuck in \"Executing\" state indefinitely due to orphaned parent records or incomplete workflow execution. Over time, these stale contexts accumulate and can impact system performance, reporting accuracy, and workflow metrics. The script systematically identifies problematic workflow contexts based on configurable criteria and cancels them to maintain instance hygiene.\n\n## Detailed Description\n\n### What Problem Does This Solve?\n\nIn ServiceNow environments, workflow contexts (`wf_context` table) occasionally become \"stuck\" in an executing state when:\n\n1. **Parent Record Deletion:** The record that initiated the workflow gets deleted before workflow completion\n2. **Workflow Design Issues:** Improperly designed workflows without proper completion logic\n3. **System Errors:** Database issues, timeouts, or system failures interrupt workflow execution\n4. **Data Integrity Problems:** Missing or corrupted table references in the context record\n\nThese orphaned workflows continue to show as \"executing\" indefinitely, creating false metrics and consuming system resources during workflow engine operations.\n\n### How It Works\n\nThe script operates through a systematic validation and cleanup process:\n\n#### **Phase 1: Configuration & Initialization**\n- Establishes a time threshold (default: 180 days) to identify long-running workflows\n- Calculates the cutoff date by subtracting the threshold from the current date\n- Sets up batch processing limits to prevent transaction timeouts\n- Initializes counters for tracking processed, cancelled, and orphaned workflows\n\n#### **Phase 2: Identification**\nThe script queries the `wf_context` table for workflows matching these criteria:\n- **State:** Currently in \"executing\" status\n- **Age:** Created more than 180 days ago (configurable)\n- **Batch Limit:** Processes up to 500 records per execution (configurable)\n\n#### **Phase 3: Validation**\nFor each identified workflow context, the script performs validation checks:\n\n1. **Reference Validation:** Verifies the workflow has valid table and record ID references\n   - If either `table` or `id` field is empty → Mark for cancellation\n   - Reason: \"Missing table or record reference\"\n\n2. **Parent Record Validation:** Checks if the parent record still exists\n   - Queries the parent table using the stored record ID\n   - If record cannot be retrieved → Mark for cancellation\n   - Reason: \"Parent record no longer exists\"\n\n#### **Phase 4: Cleanup Execution**\nFor workflows marked for cancellation:\n- **Dry Run Mode (Default):** Logs findings without making changes\n- **Execution Mode:** When `DRY_RUN = false`:\n  - Sets workflow state to \"cancelled\"\n  - Calls `setWorkflow(false)` to prevent triggering additional workflows during the update\n  - Updates the workflow context record\n  - Increments cancellation counter\n\n#### **Phase 5: Reporting**\nGenerates comprehensive execution logs including:\n- Threshold date applied\n- Execution mode (dry run vs. actual)\n- Total workflows processed\n- Number of orphaned workflows identified\n- Number of workflows cancelled (if executed)\n- Individual workflow details with cancellation reasons\n\n## Configuration Variables\n\n### `DAYS_THRESHOLD`\n- **Type:** Integer\n- **Default:** 180\n- **Purpose:** Defines how old a workflow must be (in days) to be evaluated for cleanup\n- **Recommendation:** Start with 180 days; adjust based on your organization's longest-running legitimate workflows\n\n### `BATCH_SIZE`\n- **Type:** Integer\n- **Default:** 500\n- **Purpose:** Limits the number of records processed in a single execution to prevent database transaction timeouts\n- **Recommendation:** Keep at 500 for most instances; reduce to 250-300 if you experience timeout errors\n\n### `DRY_RUN`\n- **Type:** Boolean\n- **Default:** `true`\n- **Purpose:** Safety mechanism that logs findings without making actual changes\n- **Critical:** Always run in dry run mode first to review what would be affected\n\n## Prerequisites & Dependencies\n\n### Required Access\n- **Admin Role:** Required to execute background scripts and modify workflow contexts\n- **Table Access:** Read/write access to `wf_context` table\n\n### Scope Requirements\n- Should be executed in **Global scope**\n- Can be run as a Fix Script or Background Script\n\n### Testing Requirements\n- **Mandatory:** Test in non-production environment first\n- Verify workflows being cancelled are truly orphaned\n- Review dry run logs before actual execution\n\n## Execution Instructions\n\n### Step 1: Prepare the Script\n1. Copy the script to a Background Script or Fix Script module\n2. Review and adjust configuration variables based on your requirements\n3. Ensure `DRY_RUN = true` for initial execution\n\n### Step 2: Dry Run Execution\n1. Execute the script in dry run mode\n2. Review the System Logs for identified workflows\n3. Validate that workflows marked for cancellation are legitimate candidates\n4. Note the \"Total Processed\" and \"Orphaned Workflows Found\" counts\n\n### Step 3: Actual Execution\n1. Set `DRY_RUN = false` in the script\n2. Re-execute the script\n3. Monitor execution logs\n4. Verify workflows are successfully cancelled in the `wf_context` table\n\n### Step 4: Schedule (Optional)\nFor ongoing maintenance, consider:\n- Creating a scheduled job to run this monthly or quarterly\n- Adjusting `DAYS_THRESHOLD` to a lower value for regular maintenance (e.g., 90 days)\n- Implementing notifications for cleanup execution results\n\n## Use Cases\n\n### Common Scenarios for This Script\n\n1. **Instance Health Maintenance:** Regular cleanup as part of quarterly instance maintenance activities\n2. **Pre-Upgrade Cleanup:** Clearing stale data before major version upgrades\n3. **Performance Optimization:** Reducing wf_context table bloat when workflow reports show high executing counts\n4. **Data Migration Cleanup:** After bulk record deletions or data migrations that leave orphaned workflows\n5. **Workflow Redesign Projects:** Cleaning up contexts from deprecated or redesigned workflows\n\n## Best Practices\n\n### Safety Measures\n- Always start with dry run mode enabled\n- Test in sub-production environments first\n- Document workflows being cancelled before execution\n- Schedule during low-usage maintenance windows\n\n### Monitoring\n- Review System Logs after each execution\n- Compare before/after counts in the wf_context table\n- Verify no legitimate long-running workflows are impacted\n- Monitor workflow execution metrics post-cleanup\n\n### Maintenance Schedule\n- Run quarterly for preventive maintenance\n- Run immediately if you notice unusually high executing workflow counts\n- Adjust `DAYS_THRESHOLD` based on your environment's workflow patterns\n\n## Technical Considerations\n\n### Performance Impact\n- **Batch Processing:** Limits database load through `BATCH_SIZE` control\n- **Query Efficiency:** Uses indexed fields (state, sys_created_on) for optimal performance\n- **Transaction Management:** `setWorkflow(false)` prevents cascade operations\n\n### Data Integrity\n- **No Data Loss:** Only cancels workflows; doesn't delete parent records\n- **Audit Trail:** All cancellations are logged in System Logs\n- **Reversibility:** Cancelled workflows remain in the table for audit purposes\n\n### Limitations\n- Processes only one batch per execution; may need multiple runs for large datasets\n- Focuses on orphaned workflows; doesn't detect all types of stuck workflows\n- Requires manual verification for workflows without obvious orphaning issues\n\n## Support & Enhancement Ideas\n\n### Potential Enhancements\n1. Add email notification summarizing cleanup results\n2. Implement additional validation for workflows stuck in activities\n3. Create reporting dashboard for workflow health metrics\n4. Add support for archiving cancelled contexts to secondary table\n5. Include validation for workflows without any executing activities\n\n***\n\nThis script is a practical maintenance tool that helps keep your ServiceNow instance healthy by addressing a common technical debt issue with workflow contexts, improving system performance and data accuracy.\n\n## Authors\nMasthan Sharif Shaik ( <a href=\"https://www.linkedin.com/in/nowsharif/\" target=\"_blank\">LinkedIn</a> ,  <a href=\"https://www.servicenow.com/community/user/viewprofilepage/user-id/668622\" target=\"_blank\">SN Community</a> )\n\n## Version History:\n* 0.1\n    * Initial Release\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/cleanupOrphanedWorkflowContexts/cleanupOrphanedWorkflowContexts.js",
    "content": "// Fix Script: Cleanup Orphaned and Stale Workflow Contexts\n// Purpose: Cancel and clean up workflow contexts that are:\n//   1. In \"Executing\" state for more than 180 days\n//   2. Associated with deleted/invalid parent records\n//   3. Stuck without valid activity states\n// \n// Author: Masthan Sharif Shaik\n// Date: October 2025\n// Tested on: Zurich, Yokohama\n//\n// IMPORTANT: Test in non-production environment first\n// This script will mark stale workflows as cancelled and prevent re-execution\n\n(function cleanupOrphanedWorkflowContexts() {\n\n    // Configuration - adjust these values based on your requirements\n    var DAYS_THRESHOLD = 180; // Workflows executing longer than this will be evaluated\n    var BATCH_SIZE = 500; // Process in batches to avoid transaction timeouts\n    var DRY_RUN = true; // Set to false to actually cancel workflows\n\n    // Calculate date threshold\n    var thresholdDate = new GlideDateTime();\n    thresholdDate.addDaysLocalTime(-DAYS_THRESHOLD);\n\n    var totalProcessed = 0;\n    var totalCancelled = 0;\n    var orphanedCount = 0;\n\n    gs.info('=== Workflow Context Cleanup Started ===');\n    gs.info('Threshold Date: ' + thresholdDate.getDisplayValue());\n    gs.info('Dry Run Mode: ' + DRY_RUN);\n\n    // Query for stale executing workflow contexts\n    var wfContext = new GlideRecord('wf_context');\n    wfContext.addQuery('state', 'executing');\n    wfContext.addQuery('sys_created_on', '<', thresholdDate);\n    wfContext.setLimit(BATCH_SIZE);\n    wfContext.query();\n\n    gs.info('Found ' + wfContext.getRowCount() + ' stale workflow contexts to process');\n\n    while (wfContext.next()) {\n        totalProcessed++;\n        var shouldCancel = false;\n        var reason = '';\n\n        // Check if parent record exists\n        var tableName = wfContext.getValue('table');\n        var recordId = wfContext.getValue('id');\n\n        if (!tableName || !recordId) {\n            shouldCancel = true;\n            reason = 'Missing table or record reference';\n            orphanedCount++;\n        } else {\n            // Verify parent record exists\n            var parentRecord = new GlideRecord(tableName);\n            if (!parentRecord.get(recordId)) {\n                shouldCancel = true;\n                reason = 'Parent record no longer exists';\n                orphanedCount++;\n            }\n        }\n\n\n        if (shouldCancel) {\n            gs.info('Context: ' + wfContext.getDisplayValue() +\n                ' | Age: ' + wfContext.sys_created_on.getDisplayValue() +\n                ' | Reason: ' + reason);\n\n            if (!DRY_RUN) {\n                // Cancel the workflow context\n                wfContext.state = 'cancelled';\n                wfContext.setWorkflow(false); // Prevent additional workflows from triggering\n                wfContext.update();\n                totalCancelled++;\n            }\n        }\n    }\n\n    gs.info('=== Workflow Context Cleanup Complete ===');\n    gs.info('Total Processed: ' + totalProcessed);\n    gs.info('Orphaned Workflows Found: ' + orphanedCount);\n    gs.info('Workflows Cancelled: ' + (DRY_RUN ? '0 (Dry Run)' : totalCancelled));\n    gs.info('To execute cleanup, set DRY_RUN = false');\n\n})();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/deleteMultiple/README.md",
    "content": "This script is recommended to be used (with caution) when it's necessary to delete multiple records from a table.\n\ndeleteMultiple is considerably faster than deleteRecord\n\nAs stated in the script, it's important to limit the amount of records being processed by this script at a time and never test this on Production without doing so on Development first\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/deleteMultiple/code.js",
    "content": "var gr = new GlideRecord('table_name'); //replace table_name with actual table name\ngr.addQuery('field', 'value'); //recommended to add queries to help filter out unrelated records\ngr.setLimit(100); //recommended to start with a small batch of records first to check performance and function -- this sets the limit to only 100 records returned\ngr.query();\ngr.setWorkflow(false); //recommended to keep this line as this will help prevent business rules from running on this table -- keep in mind this doesn't stop business rules from related records from running\ngr.deleteMultiple(); //this will delete multiple records quicker than using deleteRecord()\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/get Groups without Member/README.md",
    "content": "Use this fix script to get the list of groups having no members or group without active members in it.\n\nSuch gruups can be deactivated if not required.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/get Groups without Member/getGroupsWithoutMember.js",
    "content": "gs.info('Below groups have no memebers');\nvar groupArr = [];\nvar groupGr = new GlideRecord('sys_user_group');\ngroupGr.addActiveQuery();\ngroupGr.query();\nwhile(groupGr.next()){\n\tvar grMem = new GlideRecord('sys_user_grmember');\n\tgrMem.addQuery('group',groupGr.getUniqueValue());\n\tgrMem.addQuery('user.active',true);\n\tgrMem.query();\n\tif(!grMem.hasNext()){\n\t\tgroupArr.push(groupGr.getUniqueValue());\n\t\tgs.info('Name: '+groupGr.getValue('name')+' , Sys ID: '+groupGr.getUniqueValue())\n\t}\n}\ngs.info('Group SysID: '+groupArr.toString());\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/update variable role/README.md",
    "content": "# Fix script to update varialbes write roles\n\n- we use variables in catalog items for that we have to provide roles who can access the variable\n- we tend to forget adding roles after creating many varialbes\n- To update multiple variables write role this fix script will help to add them.\n\n\n```\nfunction updateItemOptionRoles() {\n     var query = 'sys_scope=5f414691db10a4101b2733f3b9961961';   // sys_id of application\n     var varGr = new GlideRecord('item_option_new');  // GlideRecord of variables table\n     varGr.addEncodedQuery(query);\n     varGr.query();\n     gs.info('Starting update for ' + varGr.getRowCount() + ' records.'); \n     varGr.setValue('write_roles', 'role1, role2, role3'); // add the write roles \n     varGr.updateMultiple();\n    gs.info('Updated ' + varGr.getRowCount() + ' records.');\n }\n```\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/update variable role/updateWriteRolesOfVariables.js",
    "content": "// Fix script to update varialbes write roles \nfunction updateItemOptionRoles() {\n    var query = 'sys_scope=5f414691db10a4101b2733f3b9961961'; // sys_id of application\n    var varGr = new GlideRecord('item_option_new');\n    varGr.addEncodedQuery(query);\n    varGr.query();\n\n    gs.info('Starting update for ' + varGr.getRowCount() + ' records.');\n\n    varGr.setValue('write_roles', 'role1, role2, role3'); // \n    varGr.updateMultiple();\n\n    gs.info('Updated ' + varGr.getRowCount() + ' records.');\n}\n\nupdateItemOptionRoles();\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/updateMultipleRecords/README.md",
    "content": "//This Fix scripts is to clean up multiple record errors\n// Navigate to Scripts-Background\n// Past the script and update the place holder variable value: table name, field, and value etc.\n// Also advisable to validate the row count the //gr.getRowCount() and remove from codebase.\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/updateMultipleRecords/readme.md",
    "content": "## update Multiple Records\nThese 2 scripts are used to update Multiple records but in different ways\n### update_multiple_records.script file\nIn this script it uses `updateMultiple()` Function \n1. In this script it uses `updateMultiple()` Function \n2. Automatically updates system fields like sys_updated_by and sys_updated_on.\n3. Cannot skip system fields using autoSysFields(false).\n4. Always triggers workflows, business rules, and script actions.\n5. Cannot disable per record.\n\n### update_multiple_records_v2.script file\nIn this script it uses `update()` Function \n1. update() is a GlideRecord method used to update a single record in the database. It is commonly used inside a loop to update multiple records individually.\n2. Can skip updating system fields using autoSysFields(false).\n3. Can skip workflows/business rules using setWorkflow(false). "
  },
  {
    "path": "Specialized Areas/Fix scripts/updateMultipleRecords/update_multiple_records.js",
    "content": "var GrQry = \"\"; //Query of the affected records.\n\nvar grTableName = new GlideRecord('table_name');\ngrTableName.addEncodedQuery(GrQry);\ngrTableName.query();\n{\ngrTableName.setValue(\"field\", \"value\"); // Replace 'field' and 'value'\ngrTableName.autoSysFields(false); // Prevents updating system fields like 'updated by'\ngrTableName.setWorkflow(false); // Prevents triggering workflows\ngrTableName.updateMultiple(); \n}\ngs.print(\"Records updated successfully\");\n"
  },
  {
    "path": "Specialized Areas/Fix scripts/updateMultipleRecords/update_multiple_records_v2.js",
    "content": "var GrQry = \"\"; //Query of the affected records.\n\nvar grTableName = new GlideRecord('table_name');\ngrTableName.addEncodedQuery(GrQry);\ngrTableName.query();\n  \nwhile (grTableName.next()) {\ngrTableName.setValue(\"field\", \"value\"); // Replace 'field' and 'value'\ngrTableName.autoSysFields(false); // Prevents updating system fields like 'updated by'\ngrTableName.setWorkflow(false); // Prevents triggering workflows\ngrTableName.update(); \n    }\ngs.print(\"Records updated successfully\");\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Add signature and update fields to a fillable PDF document/README.md",
    "content": "Use case: Add signatures and update information on a fillable PDF\n\nThis has a logic check to see if the PDF has editable fields or not. If there are no fillable fields, it throws an error.\n\nPdfMergeSignRequestor and PDFGenerationAPI have been used here.\n\nIt takes the below inputs:\n\nSignature image: the Sys_id of the signature image in the Attachments table.\n\nPDF record: Sys_id of the PDF record in the Attachments table.\n\nTarget table: Target table name (ex: sc_req_item, sc_task, etc.)\n\nTarget record: sys_id of the target record.\n\nOutput screenshots:\n\n![image](https://github.com/user-attachments/assets/26fe1a1c-9339-4f65-bb0f-0f1d258c460e)\n\n![image](https://github.com/user-attachments/assets/9a651aa2-b914-440b-8fc7-f7f593f9a2e8)\n\n\n![image](https://github.com/user-attachments/assets/5a283faa-c4ec-49d4-b4b8-cbbd6f2ce7c3)\n\n![image](https://github.com/user-attachments/assets/0c8ea249-720a-47a3-9a96-9efc5de8abb3)\n\n\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Add signature and update fields to a fillable PDF document/pdf_signature.js",
    "content": "(function execute(inputs, outputs) {\n/*\nFlow action name: Update Fillable PDF and Add Signature\n\ninputs:signature image, PDF record, target table, target record \n\nOutputs: \n\nIf successful: Final Attachment Sys_id and Status\n\nIf failed: Error Message:PDF doesn't have editable fields.\n*/\n\nvar Signatureid = inputs.Signature;\nvar PDFRecord = inputs.PDFRecord;\nvar TargetTableName = inputs.TargetTableNmae;\nvar TargetTableId = inputs.TargetTableId;\nvar result;\n\n\n//check if PDF is editable/Fillable Fields\nvar e = new sn_pdfgeneratorutils.PDFGenerationAPI;\nvar edit = e.isDocumentFillable(PDFRecord);\ngs.info(edit.document_editable);\n\nif(edit.document_editable == \"true\" || edit.document_editable == true){\n    //get PDF record tablename and table record\n\nvar sourcePDF = new GlideRecord(\"sys_attachment\");\nif(sourcePDF.get(PDFRecord)){\nvar sourcePDFName=sourcePDF.table_name;\nvar sourcePDFRecord=sourcePDF.table_sys_id;\n}\n\nvar fieldMap = new Object();\nfieldMap[\"Name\"] = \"Hemanth\";\nvar paramMap = new Object();\nparamMap[\"FlattenType\"] = \"partially_flatten\";\n\n//add signature to the PDF\nvar requestor = new sn_pdfgeneratorutils.PdfMergeSignRequestor;\nrequestor.createRequest(PDFRecord, sourcePDFName, sourcePDFRecord, \"filledPdfdone\");\nrequestor.addSignatureMapping(1, 43, 120, 250, 50, Signatureid);  //adjust signature position and size\n\n//add signature to the PDF and attach to tagert record\nvar v = new sn_pdfgeneratorutils.PDFGenerationAPI;\nresult = v.fillFieldsAndMergeSignature(fieldMap, PDFRecord, TargetTableName, TargetTableId, requestor, \"filledPdfdone\", paramMap);\n\noutputs.result =result;\n}\nelse{\n    outputs.result =\"Can't Eidt this PDF\"\n}\n\n\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Adhoc Assessment Generator Flow Action/Assessment.js",
    "content": "Flow action(add in your main flow):\n\nInputs:Metric Type, Metric Category,Target Table,Assessment Assignment user and Target record sys_id\n\nOutput:Assessment record gets created and attached to the target record\n\nScript login inside the action. : \n(function execute(inputs, outputs) {\n// ... code ...\n//get current record object \nvar record = new GlideRecord(inputs.TargetTable);\nrecord.get(inputs.CurrentRecord)\n\n//sys_id of the metric type\nvar metricType = inputs.MetricType;\n\n// Sys Ids for the Metric Categories\nvar general = inputs.MetricCategory;\n\nvar metricCategories = [];\nmetricCategories.push(general);\n\n// Function to fetch the assessable record for the current record if exists\nfunction getAssessableRecord(record) {\n    assessableRecordGR.addQuery(\"source_id\", inputs.CurrentRecord);\n    assessableRecordGR.addQuery(\"source_table\", inputs.TargetTable);\n    assessableRecordGR.addQuery(\"metric_type\", metricType);\n    assessableRecordGR.query();\n    if (assessableRecordGR.hasNext()) {\n        assessableRecordGR.next();\n        return assessableRecordGR;\n    }\n    return null;\n}\n\ntry {\n\n    var assessableRecordGR = new GlideRecord('asmt_assessable_record');\n\n    var m2mGR = new GlideRecord(\"asmt_m2m_category_assessment\");\n    // Fetch assessable record for current record\n    var assessable = getAssessableRecord(record);\n    if (!assessable) {\n        // Create assessable record for current record\n        new global.AssessmentUtils().checkRecord(record, metricType, true);\n        assessable = getAssessableRecord(record);\n        // Map all the metric categories to the assessible record (Create records in m2m table)\n        for (var i = 0; i < metricCategories.length; i++) {\n            m2mGR.initialize();\n            m2mGR.category = metricCategories[i];\n            m2mGR.assessment_record = assessable.sys_id;\n            m2mGR.insert();\n        }\n    }\n\n    // Generate an Assessment Instance\n    var assessment = new global.AssessmentUtils().createAssessments(metricType, record.sys_id, inputs.AssessmentAssignto);\n    // Extract the Sys Id for the Assessment Instance\n    var assessmentID = assessment.split(',')[0];\n\n    var assessmentInstanceGR = new GlideRecord(\"asmt_assessment_instance\");\n    assessmentInstanceGR.get(assessmentID);\n    // Set Trigger table to current table\n    assessmentInstanceGR.trigger_table = inputs.TargetTable;\n    // Set Trigger record to current record\n    assessmentInstanceGR.trigger_id = record.sys_id;\n    // Set Task record to current record (For Related List)\n    assessmentInstanceGR.task_id = record.sys_id\n    assessmentInstanceGR.update();\n\n    gs.addInfoMessage(\"Assessment Instance Generated\");\n} catch (e) {\n    gs.addErrorMessage(\"Error Generating Assessment Instance\");\n}\noutputs.out=assessment;\noutputs.type=metricType;\noutputs.category=general;\noutputs.id=record.sys_id\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Adhoc Assessment Generator Flow Action/README.md",
    "content": "Use Case: Lets say you would like to trigger/create an assessment in your flow designer flow for any taregt record? \n\nEiither you have to create a custom logic/script in your flow or update appropriate assessment condition and update target to meet the assessment condition.\n\nHere is the generic action where you just have to select appropriate records and it just triggers assessment.\n\nThis generic action takes following inputs to create assessment record.\n\n1)Metric type: Assessment name\n\n2)Metric Category : Category of the assessment (this includes Assessment questions)\n\n3)Target Table : Select target table (ex: RITM or any custom table)\n\n4)Assessment Assignment user: Select user whom do you like to assign the assessment\n\n5)Target record sys_id: provide sys_id of the target record , you can input dynamic value from the flow data pills\n\n![image](https://github.com/user-attachments/assets/e1a034b3-cfef-4793-b559-dee2905da70e).\n\n![image](https://github.com/user-attachments/assets/16ddf545-4a0c-47e0-981d-dd524047a7ed)\n\n![image](https://github.com/user-attachments/assets/e8c9b431-0980-47f9-b598-a8d4640f42d4)\n\n\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Assign Role/README.md",
    "content": "# Assign Role\nA custom flow action which will help us to assign the a role to a user. \n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Assign Role/assignRole.js",
    "content": "//Action will take the user and role as input. User and role fields are of reference type.\n(function execute(inputs, outputs) {\n\n    var user = inputs.user;\n    var role = inputs.role;\n\n    var userRoleGR = new GlideRecord('sys_user_has_role');\n    userRoleGR.initialize();\n    userRoleGR.setValue('user', user.sys_id);\n    userRoleGR.setValue('role', role.sys_id);\n    userRoleGR.insert();\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Calculate Ticket Age/README.md",
    "content": "This is a custom action that calculate the age of the ticket/incident on the basis of its created date which is started date and the date at which it is updated.\n\n\nInput Variable:\n\n1. Start Date Type-Date/Time, Mandatory\n2. End Date  Type-Date/Time, Mandatory\n\nOutput variable:\n1. Ticket Age  Type-string\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Calculate Ticket Age/script.js",
    "content": "(function execute(inputs, outputs) {\nvar start_date = new GlideDateTime(inputs.start_date); //created date\n  var end_date = new GlideDateTime(inputs.end_date);  //updated\n  outputs.ticket_age = GlideDateTime.subtract(start_date,end_date).getDisplayValue();\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Check MID Server Availability/README.md",
    "content": "## Action Script to check mid server availability\n\n### Use this script in a new flow action to check if a mid server is up/available or not. This can be used to for orchestration projects or similar. \n### Note: The script returns availability as true/false back as the action output to the flow.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Check MID Server Availability/check_mid_server_availablility.js",
    "content": "(function execute(inputs, outputs) {\n\n  var encodedQuery = 'status=Up'; \n  var process = false;\n      \n  var grMidServer = new GlideRecord('ecc_agent');\n  \tgrMidServer.addEncodedQuery(encodedQuery); //Queries mid server with names from list which status is Up\n  \tgrMidServer.query();\n  \twhile(grMidServer.next()){   //If any of the mid servers are up, return to process request.\n      \tif (this.isMidserverCapable(grMidServer) && this.isMidserverSupport(grMidServer)){\n        \tprocess = true;\n          \tbreak;\n        }\n          \n    }\n  \n   outputs.processrequest = process;\n  \n  \n})(inputs, outputs);\n\n//checks if mid server the correct capablities.\nfunction isMidserverCapable(midserver){\n  \n  var midServerID = midserver.sys_id;\n  var GRcapability = new GlideRecord('ecc_agent_capability_m2m');\n  var ALLcapabilityID = 'eeab973fd7802200bdbaee5b5e610381'; // All sys ID\n      GRcapability.addQuery('capability',ALLcapabilityID);\n      GRcapability.addQuery('agent',midServerID);\n      GRcapability.query();\n  \n  \t\tif (GRcapability.next()){\n         \treturn true; \n        }else{\n         \treturn false; \n        }\n\n}\n\n//checks if mid server the supports Orchestration.\nfunction isMidserverSupport(midserver){\n  \n  var midServerID = midserver.sys_id;\n  var GRapp = new GlideRecord('ecc_agent_application_m2m');\n  var ALLappID = '35aa573fd7802200bdbaee5b5e610375'; // ALL sys ID.\n  var ORCHappID = 'b5f91a57d7002200bdbaee5b5e6103ec'; // Orchestration sys ID\n\n  \t\tGRapp.addQuery('agent',midServerID);\n  \t\t// query if MIDserver supports has ALL or Orchestration applitions\n  \t\tGRapp.addEncodedQuery('application=' + ALLappID + '^ORapplication=' + ORCHappID);\n  \n     \tGRapp.query();\n  \n  \t\tif (GRapp.next()){\n         \treturn true; \n        }else{\n         \treturn false; \n        }\n \n}\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Create Student Weekday Pickup Schedule/README.md",
    "content": "## Flow Action Script to Create Weekday student pickup schedule\n\nThis code is part of a scope app where we create a pickup schedule for active Students\n\nThis is part of a scheduled flow that runs on Sunday night and created a set of records for each active student and builds a pickup schedule accoreding to the School Start and end times.\nIt also accounts for early release days such as Wednesday at 2:00pm \n\nThis script can be modified to fit your specific needs. \n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Create Student Weekday Pickup Schedule/script.js",
    "content": "(function execute(inputs, outputs) {\n  // Define the start time as 8:00 am and the end times as 3:00 pm and 2:00 pm for regular and early release days, respectively\n  var startTime = inputs.StartTime;\n  var endTimeRegular = inputs.RegularEndTime;\n  var endTimeEarlyRelease = inputs.EarlyReleaseEndTime;\n  var student = inputs.Student;\n  var pickupLocation = inputs.PickupLocation;\n  var parent = inputs.ContractRecord\n  var destination = inputs.Destination\n\n  // Define an array of the weekdays from Monday to Friday\n  var weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];\n\t\n  // Create a Date object for the current date and get the day of the week as a number (0 = Sunday, 1 = Monday, etc.)\n  var currentDate = new GlideDateTime();\n  var currentDayOfWeek = currentDate.getDayOfWeekLocalTime();\n\t\n  // Calculate the date for Monday of the current week by subtracting the current day of the week from the current date and adding 1\n  var mondayDate = new GlideDateTime(currentDate);\n  mondayDate.setDisplayValue(currentDate.getLocalDate() - currentDayOfWeek + 1);\n\tdayStart = mondayDate;\n    scheduledPickup = mondayDate.getLocalDate();\n  // Loop through the weekdays array and create a new GlideRecord for each weekday with the start and end times set to the specified values\n  weekdays.forEach(function(day, index) {\n    // Calculate the date for the current day by adding the index to the Monday date\n    var currentDate = new GlideDateTime(mondayDate);\n    \n    var scheduledDate = new GlideDateTime(mondayDate);\n    currentDate.setDisplayValue(mondayDate.getLocalDate() + index);\n     var theDay= (scheduledDate.getLocalDate() + ' 08:00:00');\n     scheduledDate.setValue(theDay);\n     var serviceDate = currentDate.getLocalDate()\n    gs.info(\"**** Current Date: \" + currentDate);\n    \n    // Determine the end time based on whether it's an early release day or not\n    var endTime;\n    if (day === 'Wednesday') {\n      endTime = endTimeEarlyRelease;\n    } else {\n      endTime = endTimeRegular;\n    }\n    \n    \n    \n    // Create a new GlideRecord for the current weekday and set the fields\n    var newRecord = new GlideRecord('x_407566_adventu_0_transportation');\n    newRecord.initialize();\n    newRecord.setValue('active', true);\n    newRecord.setValue('school_start_time', startTime);\n    newRecord.setValue('school_end_time', endTime);\n    newRecord.setValue('weekday', day);\n    newRecord.setValue('student', student);\n    newRecord.setValue('state', 'scheduled');\n    newRecord.setValue('type', 'to_school');\n    newRecord.setValue('pickup_location', pickupLocation);\n   newRecord.setValue('drop_off_location', destination);\n    newRecord.setValue('notes', \"Auto Generated Schedule 2023\");\n   newRecord.setValue('scheduled_pickup', serviceDate);\n    newRecord.setValue('parent', parent);\n    newRecord.insert();\n  });\n\n  outputs.weekdays = serviceDate;\n  outputs.schedule = scheduledDate;\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Data Stream/README.md",
    "content": "# Pagination Setup for Data Stream Integration\n\nThis script is used to manage pagination for a Data Stream integration in ServiceNow.\nUse this script to set pagination variables or manipulate variables extracted by XPath or JSON Path.\n\n## Code Overview\n\nThe script calculates the next offset based on the current page's limit and offset, and determines whether or not to retrieve the next page of data based on the total count of records available.\n\n### Key Variables:\n- **`limit`**: The number of records to retrieve per page.\n- **`offset`**: The starting point for the next page of records.\n- **`getNextPage`**: A boolean flag indicating whether to continue fetching more pages.\n\n### How it works:\n- The script compares the `nextOffset` (calculated as `currentOffset + limit`) to the total number of records (`totalCount`).\n- If the `nextOffset` is less than the `totalCount`, the `getNextPage` variable is set to `true` to fetch the next page.\n- If there are no more records to fetch, the `getNextPage` variable is set to `false`.\n\nThis setup is particularly useful for handling large datasets in paginated API requests, ensuring efficient and scalable data retrieval.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Data Stream/pagination-setup.js",
    "content": "(function paginate(variables, pageResponse) {\n    // Parse the limit (maximum number of records per page) from the variables\n    var limit = parseInt(variables.limit);\n\n    // Extract the total number of records from the response headers (usually provided by the external API)\n    var totalCount = parseInt(pageResponse.response_headers['X-Total-Count']);\n\n    // Parse the current offset (starting point for the current page of records)\n    var currentOffset = parseInt(variables.offset);\n\n    // Calculate the offset for the next page by adding the limit to the current offset\n    var nextOffset = currentOffset + limit;\n\n    // Check if the next offset is still within the total number of records\n    if (nextOffset < totalCount) {\n        // If there are still more records to retrieve, set the flag to true to get the next page\n        variables.getNextPage = true;\n\n        // Update the offset for the next page, converting the number back to a string\n        variables.offset = nextOffset.toString();\n    } else {\n        // If there are no more records to retrieve, set the flag to false to stop further pagination\n        variables.getNextPage = false;\n    }\n})(variables, pageResponse);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Extract JSON Key without Flow Transformation/README.md",
    "content": "This code will be helpful to those who are looking out to remove HTML tags and convert it to String variable within the Flow Designer. The Replace String transformation function doesn't works well with the HTML variable so to overcome this challenge, we can make use of a Run Script Flow action along with a regex and JavaScript replace method.\n\nThe function expects the input HTML string to be passed into and we get a string free from HTML tags and whitespaces as an output.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Extract JSON Key without Flow Transformation/remove-html-tags.js",
    "content": "(function execute(inputs, outputs) {\nvar htmlString = inputs.htmlValue; // Assuming HTML string is stored in the input variable htmlValue\nvar plainText = htmlString.replace(/<[^>]+>/g,''); // Regex to replace HTML tags\noutputs.plainString = plainText.trim(); // Trimming the whitespaces, if any and pushing the string without HTML to output variable plainString\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Generate unique value based on sequence/README.md",
    "content": "# Generate Unique Value Based on Sequence\n\nThe `execute()` function in action is generates a unique sequence value based on the existing sequence in a column of a table. This function inside a action script is designed to be used in database environments. It takes the following input parameters:\n\n- `table`: The name of the table to generate the unique value for.\n- `column`: The name of the column to generate the unique value for.\n- `default_value`: The default value to use if there are no records in the table.\n- `useLowerUpperBoth`: A Boolean value indicating whether to use both lower and upper case characters when generating the next sequence value.\n\n## Function Behavior\n\n1. The function begins by querying the specified table to retrieve all the values in the specified column.\n\n2. If there are records in the table:\n   - The function then generates the next sequence value based on the last value in the column.\n   - It automatically detects the type of sequence value to generate based on the postfix of the last value in the column:\n     - If the postfix ends with digits, the function generates a numeric sequence value.\n     - If the postfix ends with alphabetic characters, the function generates an alphabetic sequence value.\n   - If the `useLowerUpperBoth` parameter is set to `true`, the function will use both lower and upper case characters when generating alphabetic sequence values.\n   - If the `useLowerUpperBoth` parameter is set to `false`, the function will only use upper case characters when generating alphabetic sequence values.\n\n3. If there are no records in the table, the function returns the `default_value`.\n\n4. The function handles any exceptions by setting the output value to `null`.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Generate unique value based on sequence/next-unique-sequence.js",
    "content": "(function execute(inputs, outputs) {\n    // ... code ...\n    try {\n        var records = []; // Initialize an array to store records.\n        var tableGR = new GlideRecord(inputs.table); // Create a GlideRecord for the specified table.\n        tableGR.query(); // Execute the query to retrieve records.\n        while (tableGR.next()) {\n            if (tableGR.hasOwnProperty(inputs.column)) {\n                // Check if the record has the specified column.\n                records.push(tableGR[inputs.column].getDisplayValue()); // Add the column's display value to the records array.\n            }\n        }\n\n        if (records.length > 0) {\n            // If there are records in the array.\n            outputs.value = records[records.length - 1]; // Set the output to the last record's value.\n\n            while (true) {\n                outputs.value = nextSequence(outputs.value, inputs.useLowerUpperBoth); // Generate the next sequence value.\n                if (!records.includes(outputs.value)) {\n                    // Check if the generated value is not in the records array.\n                    break; // Exit the loop.\n                }\n            }\n        } else {\n            // If there are no records.\n            outputs.value = inputs.default_value; // Set the output to the default value.\n        }\n    } catch (e) {\n        outputs.value = null; // Handle any exceptions by setting the output to null.\n    }\n\n})(inputs, outputs);\n\n// Function to generate the next sequence based on the input value and options.\nfunction nextSequence(input, useLowerUpperBoth) {\n    if (/\\d+$/.test(input.toString())) {\n        // If the input ends with digits.\n        var postfix = input.match(/\\d+$/)[0]; // Extract the digits.\n        var initLength = postfix.length;\n        postfix = parseInt(postfix);\n        ++postfix;\n        if (postfix.toString().length < initLength) {\n            postfix = padStart(postfix.toString(), initLength, '0'); // Ensure consistent length by padding with zeros.\n        }\n        return input.replace(/\\d+$/, postfix); // Replace the digits with the incremented value.\n    } else {\n        // If the input ends with alphabetic characters.\n        var postfix = input.match(/[A-Za-z]+$/)[0]; // Extract the alphabetic characters.\n        var index = postfix.length - 1;\n        while (true) {\n            if (index === -1) {\n                // If all characters have been processed.\n                var charCode = postfix.charCodeAt(0);\n                if (useLowerUpperBoth === true) {\n                    postfix = 'A' + postfix;\n                } else {\n                    postfix = (charCode < 96 ? 'A' : 'a') + postfix;\n                }\n                break;\n            }\n            var charCode = postfix.charCodeAt(index);\n\n            if (useLowerUpperBoth) {\n                // If both lower and upper case characters are allowed.\n                if (charCode == 90) {\n                    postfix = replaceAt(postfix, index, String.fromCharCode(97));\n                    break;\n                } else if (charCode == 122) {\n                    postfix = replaceAt(postfix, index, String.fromCharCode(97));\n                    index--;\n                } else {\n                    postfix = replaceAt(postfix, index, String.fromCharCode(charCode + 1));\n                    break;\n                }\n            } else {\n                // If only upper case characters are allowed.\n                if (charCode == 90) {\n                    postfix = replaceAt(postfix, index, String.fromCharCode(65));\n                    index--;\n                } else if (charCode == 122) {\n                    postfix = replaceAt(postfix, index, String.fromCharCode(97));\n                    index--;\n                } else {\n                    postfix = replaceAt(postfix, index, String.fromCharCode(charCode + 1));\n                    break;\n                }\n            }\n        }\n\n        return input.replace(/[A-Za-z]+$/, postfix); // Replace the alphabetic characters with the updated value.\n    }\n}\n\n// Function to replace a character at a specific index in a string.\nfunction replaceAt(str, index, ch) {\n    return str.substring(0, index) + ch + str.substring(index + 1);\n}\n\n// Function to pad a string with a specified character to a certain length.\nfunction padStart(str, length, init) {\n    return str.length < length ? padStart(init + str, length, init) : str;\n}\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get Days difference/README.md",
    "content": "# Get Days Difference\nFrequently, in our flow, there's a need to calculate the difference in days between two dates. To address this, I've developed a specialized flow action that facilitates this calculation.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get Days difference/getDaysDifference.js",
    "content": "// A custom flow action to get the days difference between two dates using GlideDateTime class.\n//Input variable of the action are startDate and endDate. Output variable is difference.\n(function execute(inputs, outputs) {\n    var startDate = new GlideDateTime(inputs.startDate);\n    var endDate = new GlideDateTime(inputs.endDate);\n    var startDateInMilliseconds = startDate.getNumericValue();\n    var endDateInMilliseconds = endDate.getNumericValue();\n    var timeDifference = endDateInMilliseconds - startDateInMilliseconds;\n    var daysDifference = Math.ceil(timeDifference / (1000 * 3600 * 24));\n    outputs.difference = daysDifference;\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get KB Article Permalink/README.md",
    "content": "# Get KB Article Permalink\nFlow Action which will take KB Article Number as input and returns the latest version of Permalink. This URL will stay constant always even if the KB Article is updated with new version.\n\n**Input** : KB Article Number (Type : String)\n\n**Script Step** : Generates the Permalink URL for KB Article (see the script.js file in this folder)\n\n**Output** : Permalink (Type : URL)\n\n**Usage** : This can be used in multiple scenarios where the KB Article Link/URL is required. It can either be in notifications, scripts, Integrations, etc. Since this Permalink is always fixed and works even if the KB Article is updated to new version, there is no maintainance required.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get KB Article Permalink/script.js",
    "content": "(function execute(inputs, outputs) {\n\n    var instanceURL = gs.getProperty('glide.servlet.uri').toString();\n    var permalink = instanceURL + 'kb?id=kb_article_view&sysparm_article=' + inputs.kb_number;\n    outputs.kb_article_permalink = permalink;\n    \n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get Property/README.md",
    "content": "Flow Action to return the value of a sys_properties\n\nAction requires:\n- One string input for sys_properties name\n- Script Step (provided in getPropertyFlowAction.js)\n- One string output for returned value"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get Property/getPropertyFlowAction.js",
    "content": "(function execute(inputs, outputs) {\n    outputs.value = gs.getProperty(inputs.property);\n})(inputs, outputs);"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get choice field value (mitigating known error)/README.md",
    "content": "# Get choice field value (mitigating known error)\n\nThis Flow Action serves as a solution for addressing a recognized problem within Flow Designer ([known error](https://support.servicenow.com/kb?id=kb_article_view&sysparm_article=KB0813846)). The issue involves copying of a choice field from one record to another through the drag-and-drop functionality of the data pill. In this scenario, the copied choice field's label is incorrectly set instead of the actual underlying value. Consequently, this leads to the emergence of the \"missing choice value\" error, indicated by the choice value appearing in blue, disrupting subsequent logic that relies on this field.\nTo resolve this issue, I have written a simple generic Flow Designer Action “Get choice field label and value”. This action provides a reusable and effective workaround for addressing the problem in any instance where copying a choice field is required.\n\n## Instruction\n\nCreate a Flow Action with the following inputs and outputs:\n- inputs: table, record_id, choice_field_name\n- outputs: choice_value, choice_label\n\nIn your Flow you can then access the choice_value via the data pill and drag and drop it to set the choice value of another record. If the display value is needed as well, it can also be accessed via the choice_label output. \n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Get choice field value (mitigating known error)/script.js",
    "content": "(function execute(inputs, outputs) {\n  var table = inputs.table;\n  var recordId = inputs.record_id;\n  var choiceFieldName = inputs.choice_field_name;\n\n  try {\n    var gr = new GlideRecord(table);\n    gr.addQuery(\"sys_id\", recordId);\n    gr.query();\n\n    if (gr.next()) {\n      outputs.choice_value = gr.getValue(choiceFieldName);\n      outputs.choice_label = gr.getDisplayValue(choiceFieldName);\n    } else {\n      // Handle the case where the record with the specified ID is not found.\n      gs.error(\"Record not found with sys_id: \" + recordId);\n    }\n  } catch (ex) {\n    // Handle any exceptions that may occur during the script execution.\n    gs.error(\"An error occurred: \" + ex);\n  }\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/GetIPRange/README.md",
    "content": "Overview\nTo be used for getting all the IP Addresses within a given range eg 192.168.0.1 - 192.168.0.255. This will print everything between those IP ADDRESSES\n\nInputs\ninputs.firstip - First ipaddress in the range\ninputs.secondip - Second ip address in the range\n\nScript Step\nCreate a script step with the code provided to look up a range of ip address\n\nOutputs\nreturns all ip addresses in a given range\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/GetIPRange/getIPRange.js",
    "content": "(function execute(inputs, outputs) {\n    \n    outputs.ipoutput = getIpsFromRange(ipToHex((transformIP(inputs.firstip))), ipToHex(transformIP(inputs.secondip)));\n  \t\n\t\n//transform the IP Address into a decimal number. Each octet will need to converted to a decimal first and then added back together\n    function transformIP(ip) {\n        var d = ip.split(\".\");\n        var num = 0;\n        num += Number(d[0]) * Math.pow(256, 3);\n        num += Number(d[1]) * Math.pow(256, 2);\n        num += Number(d[2]) * Math.pow(256, 1);\n        num += Number(d[3]);\n        return num;\n    }\n  //Transform Decimal number into a HEX value, the decimal number will be to large to deal with. HEX will shorten it an make it easier to deal with. HEX Value Example> 817EB263 or 0A0A0A0A\n    function ipToHex(ipList) {\n        var dec = ipList.toString().split(''),\n            sum = [],\n            hex = [],\n            i, s;\n        while (dec.length) {\n            s = 1 * dec.shift();\n            for (i = 0; s || i < sum.length; i++) {\n                s += (sum[i] || 0) * 10;\n                sum[i] = s % 16;\n                s = (s - sum[i]) / 16;\n            }\n        }\n        while (sum.length) {\n            hex.push(sum.pop().toString(16));\n        }\n        return \"0x\" + hex.join('').toString();\n    }\n  //Take the HEX Values and transform them back into an IP Address by shifting the bits right as seen in oc4,oc3,oc2 below. This will loop through untill it reaches the final HEX value converting along the way. it will. then push into an array\n    function getIpsFromRange(hex1, hex2) {\n        var ipArr = [];\n        for (var i = hex1; i < hex2; i++) {\n            var oc4 = (i >> 24) & 255;\n            var oc3 = (i >> 16) & 255;\n            var oc2 = (i >> 8) & 255;\n            var oc1 = i & 255;\n            gs.debug(\"IP ADDRESSES: \" + oc4 + \".\" + oc3 + \".\" + oc2 + \".\" + oc1);\n            ipArr.push(oc4 + \".\" + oc3 + \".\" + oc2 + \".\" + oc1 + \"\\\\n\");\n        }\n        gs.debug(\"ARRRAY: \" + ipArr);\n        return ipArr;\n    }\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Group Similar Assessments Using Flow Designer Action Native UI/FlowAction.js",
    "content": "(function execute(inputs, outputs) {\n    // ... code ...\n\n    //Get Source ids from existing Attestation;\n    //Check if an assessable record exists for all attestations in the group if not, throw an error message.\n    var sources = '';\n    var asmtmetricType = inputs.metricType; //Assessment metric type sys_id\n    var asmtAssignee =inputs.AttestationAssignee.sys_id.toString(); //Assessment assignee.\n    var assessmentList =inputs.AttestationList.trim(); //comma separated individual assessment.\n    \n    var asmt = new GlideRecord(\"asmt_assessment_instance\"); //Assessment instance table\n    asmt.addQuery(\"sys_id\", \"IN\", assessmentList);\n    asmt.query();\n    while (asmt.next()) { //Need this while loop to check 1)check state and Assessable record.\n        if ((asmt.state == \"complete\" || asmt.state == \"canceled\")) {\n            outputs.out = \"can't proceed, Attestation states are in Complete or Canceled, Please review.\";\n            return;\n        }\n        //for each assessment check if assessable record exist\n        var assessableRecord = new GlideRecord(\"asmt_assessable_record\"); //assessment assessable table\n        assessableRecord.addQuery(\"source_id\", asmt.sn_grc_item);\n        assessableRecord.query();\n        if (assessableRecord.next()) {\n            sources = sources + \",\" + asmt.sn_grc_item;\n        } else {\n            outputs.out = \"can't proceed, Assesssable record doesn't for this inntance please \" + asmt.number + \" review\";\n            return;\n        }\n    }\n    sources = sources.slice(1); //Comma separated source sys_ids where assessment exist\n\n    //Create Grouped Attestation using below api\n    var result = new global.AssessmentUtils().createAssessments(asmtmetricType, sources + '', asmtAssignee, '');\n\n    //set grouped assessmemt as parent to all the instance\n    var asmtFinal = new GlideRecord(\"asmt_assessment_instance\");\n    asmtFinal.addQuery(\"sys_id\", \"IN\", assessmentList);\n    asmtFinal.query();\n    while(asmtFinal.next()){\n        asmtFinal.setValue(\"sn_grc_parent\", result.split(',')[0]);\n        asmtFinal.update(); //set parent on to the each assessment\n    }\n\n    outputs.out = result.split(',')[0]; //return the grouped attestation instance id.\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Group Similar Assessments Using Flow Designer Action Native UI/readme.md",
    "content": "**Create a flow action with the inputs below:**\n<img width=\"1483\" height=\"712\" alt=\"image\" src=\"https://github.com/user-attachments/assets/08dbdce3-a189-4a29-af81-49f6c0fcccae\" />\n\n\nAdd the script from action.js file\n\n**Test and publish the action.**\n<img width=\"1655\" height=\"252\" alt=\"image\" src=\"https://github.com/user-attachments/assets/7a973573-016d-45a7-ba96-e8e4a495fbaf\" />\n**Plug this into the appropriate flow where you need to group the attestation.**\n<img width=\"714\" height=\"298\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b82612fa-bbb7-4375-ba54-13fe2286b582\" />\n\n**Output:**\n\n**List of Sample Assessments**\n<img width=\"1655\" height=\"252\" alt=\"image\" src=\"https://github.com/user-attachments/assets/297be503-ee0c-4495-baca-5693f9a3fa42\" />\n\n<img width=\"962\" height=\"566\" alt=\"image\" src=\"https://github.com/user-attachments/assets/c79e4dc3-77ab-47b5-89e9-030972d01351\" />\n\n**Flow Context:**\n<img width=\"1469\" height=\"463\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b9c894af-f39d-4f72-a606-473b2d757fdf\" />\n\n**Grouped Assessment:**\n<img width=\"1252\" height=\"694\" alt=\"image\" src=\"https://github.com/user-attachments/assets/b9ca0d68-dd9d-4381-97b7-2cfbec638f99\" />\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/How to refer MID server cluster in integration flow step/MID Server Cluster.js",
    "content": "//Glide MID server cluster table\nvar midCluster = new GlideRecord(\"ecc_agent_cluster\");\ngr.midCluster(\"name\", \"Name of your MID server cluster name\"); //refer mid server cluster name\ngr.midCluster();\nif(midCluster.next()){\n    return midCluster.sys_id; //this returns mid server cluser sys_id which intern uses availble MID server for connect\n}\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/How to refer MID server cluster in integration flow step/README.md",
    "content": "During integration set up using flow designer integration action step (JDBC, Powershell,REST, SOAP etc..) wheh you select connection type as \"Define Connection inline\", you have an option to use MID Cluster when MID selection is \"Specific MID server\"\n![image](https://github.com/gowdah/code-snippets/assets/42912180/dcb8c69b-72a3-493b-8db2-72b92daefce0)\nMID cluster step expects \"MID Server cluster sys_id (not the MID cluster server name or individual MID servers) to to use availble MID server in the cluster.\nMID Server Cluster.js file logic return expected result\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Look Up MRVS Rows/LookUpMrvsRowIndex.js",
    "content": "(function execute(inputs, outputs) {\n  // Look up the 'Multi Row Question Answers' records associated with a specific sys_id and return the unique 'Row indexes' \n  var array = [];\n\n  var grMrvs = new GlideRecord('sc_multi_row_question_answer');\n  // Update line below with correct input variable\n  grMrvs.addQuery('parent_id',inputs.ParentID);\n  grMrvs.query();\n\n  while (grMrvs.next()) {\n    array.push(grMrvs.row_index.toString());\n  }\n\n  var arrayUtil = new ArrayUtil();\n  // Update line below with correct output variable\n  outputs.row_index_array = arrayUtil.unique(array);\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Look Up MRVS Rows/README.md",
    "content": "## Overview\nTo be used within an action to retrieve the unique **Row indexes** for each row within a MRVS\n\n## Inputs\nPass in the sys_id of the record the MRVS is associated with.  E.g., the sys_id of a RITM record.\n\n## Script Step\nCreate a script step with the code provided to look up each unique **Row index** associated with the MRVS\n\n## Outputs\nReturns an array of strings with each string representing the **Row index**\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Look up Support group of CI/LookUpSupportGroupFromCI.js",
    "content": "(function execute(inputs, outputs) {\n  \n  // Be sure to update inputs variable for Configuration Item\n  var supportGroup = inputs.configuration_item.support_group;\n  var answer = '';\n\n  if (supportGroup == '') {\n    answer = inputs.default_support_group; //Return the group specified in the \"Default support group\" input\n  } else {\n    answer = supportGroup; //Return the 'Support group' specified on the CI input\n  }\n\n  outputs.support_group = answer;\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Look up Support group of CI/README.md",
    "content": "## Overview\nTo be used within an action to retrieve the **Support group** of a specified CI.  If specified CI does not have a **Support group** a default value can be provided to return\n\n## Inputs\nPass in the Configuration Item record and specify a **Support group** to return if the provided CI does not have a **Support group** populated\n\n## Script Step\nCreate a script step with the code provided to look up the CI's **Support group** and return the appropriate value\n\n## Outputs\nReturns a group\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Milliseconds to Duration/README.md",
    "content": "When you use flow designer to calcualte the difference between two duration fields, these duration fields need to be changed into milliseconds using dateNumericValue(). This can be done in a flow variable similar to the example below:\n\nvar timeWorked = fd_data.trigger.current.u_total_time_worked;\ntimeWorked =  timeWorked.dateNumericValue();\n\nvar estimatedTime = fd_data.trigger.current.change.u_impact_assessment.u_change_request_effort_total;\nestimatedTime =  estimatedTime.dateNumericValue()\n\nvar remainingTime = estimatedTime - timeWorked;\n\nreturn remainingTime;\n\nTo be able to convert the millisections back into a duration fields, this flow designer action uses:\n\n- the milliseconds (reaminingTime) from the calculation as the input in an integer value\n- the the code will add the milliseconds to the ServiceNow Epoch DateTime which the duration fields uses and then returns the time difference back into a string that can be added to a duration field\n- the output of the action needs to be set to the time as a duration type field\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Milliseconds to Duration/millisecondsToDuration.js",
    "content": "(function execute(inputs, outputs) {\n\nvar gdt0 = new GlideDateTime(\"1970-01-01 00:00:00\"); //Epoch DateTime\nvar gdt1 = inputs.milliseconds;\ngdt0.add(gdt1); // Adds the milliseconds to the epoch date/time\n\noutputs.time = gdt0.toString(); \n\n})(inputs, outputs);  \n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Remove HTML Tags from a String in a Flow/README.md",
    "content": "# Remove HTML Tags from a String in a Flow\n\n## Use Case / Requirement\nNormalize HTML-rich content such as email-derived descriptions by stripping markup before continuing through a Flow Designer action.\n\n## Solution\nCreate a reusable subflow action that accepts an HTML string, removes all tags with a regular expression, and returns clean text ready for downstream logic.\n\n## Implementation\n1. Create a new custom Flow action with an input named htmlValue and an output named plainString.\n2. Paste the contents of removeHtmlTags.js into the script step of the action.\n3. Publish the action and invoke it in your flows wherever you need to sanitize user-provided HTML.\n\n## Notes\n- The regular expression removes any markup tag; adjust the pattern if you need to preserve specific tags.\n- The script trims leading and trailing whitespace generated after stripping tags.\n- Combine with HTML entity decoding if your inputs contain encoded characters.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Remove HTML Tags from a String in a Flow/removeHtmlTags.js",
    "content": "(function execute(inputs, outputs) {\n  var htmlString = inputs.htmlValue || '';\n  outputs.plainString = htmlString.replace(/<[^>]+>/g, '').trim();\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Runscript activities/CatalogvariablesSummary.js",
    "content": "//Set the RITM description \nvar descriptionSummary = \"\";\nvar ritmid=\"000000921b09dd18aa8cdbd7b04bcb7b\";//  RITM sys id\n//insted of \"ritmid \"use current.sys_id in rnscript activity\nvar grSIOM = new GlideRecord('sc_item_option_mtom');\n//grSIOM.addQuery(\"request_item\", current.sys_id); ## reference sample code\ngrSIOM.addQuery(\"request_item\", ritmid);\ngs.log(\"This is sc_item_option_mtom number\" + grSIOM);\ngrSIOM.query();\nwhile (grSIOM.next())\n   {\n   var visible = grSIOM.sc_item_option.item_option_new.visible_summary;\n   var question = GlideappQuestion.getQuestion(grSIOM.sc_item_option.item_option_new);\n   question.setValue(grSIOM.sc_item_option.value);\n   if (question.getLabel() != \"\" && question.getDisplayValue() != \"\" && question.getDisplayValue() != \"false\" &&question.getDisplayValue() != \"-- None --\"&& visible == true)\n      {  descriptionSummary += question.getLabel() + \" = \" + question.getDisplayValue() + \"\\n\\n\";  }\n}\n// current.description = descriptionSummary;  uncomment the code in workflow activity while re using\ngs.print(descriptionSummary);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Runscript activities/README.md",
    "content": "This code help to set the RITM description as the summary of catalog item variables.\nwe can reuse the code on catalog workflows in runscript activity and flow designers\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Scheduled data import trigger/README.md",
    "content": "This action script will execute the scheduled import via a flow action.\n\nInputs are  - 'importSet'  - the sys_id of the scheduled import set  - mandatory\n\nOutputs are - 'returnerror'- true if no import set found             - mandatory\n\nWe found this useful when triggering a data import from a catalog item. User attaches the import file to catalog item and submit, which triggers flow, which then\nhad this action to import the file using the right import set.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Scheduled data import trigger/Scheduled data import trigger.js",
    "content": "(function execute(inputs, outputs) {\n  \n  \t// Input: sys_id of a scheduled_import_set record\n  \t// Will execute the scheduled import for the given sys_id\n  \n  \toutputs.returnError = false;\n\t  var exportGr = new GlideRecord(\"scheduled_import_set\");\n  \tif(exportGr.get(inputs.importSet)){\n      \n\t\tSncTriggerSynchronizer.executeNow(exportGr);\n      \n    } else { // no import set found so return error to main flow\n      outputs.returnerror = true;\n    }\n      \n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/ShuffleArrayMatches/README.md",
    "content": "# Shuffle Array Matches\nFlow Action: inputs of a List of SysID's and it creates a Shuffled Arrays, Matches of 2 sysIDs\n\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/ShuffleArrayMatches/Shuffle Array Matches.js",
    "content": "(function execute(inputs, outputs) {\n function shuffleArray(array) {\n    for (var i = array.length - 1; i > 0; i--) {\n        var j = Math.floor(Math.random() * (i + 1));\n        var temp = array[i];\n        array[i] = array[j];\n        array[j] = temp;\n    }\n    return array;\n}\n\nfunction createRandomPairs(array) {\n    var shuffledArray = shuffleArray(array.slice()); // Make a copy of the array and shuffle it\n    var pairs = [];\n    for (var i = 0; i < shuffledArray.length; i += 2) {\n        if (i + 1 < shuffledArray.length) {\n            pairs.push([shuffledArray[i], shuffledArray[i + 1]]);\n        } else {\n            pairs.push([shuffledArray[i]]);\n        }\n    }\n    return pairs;\n}\n\n\nvar randomPairs = createRandomPairs(inputs.wheels);\n\n\n\n    outputs.match = randomPairs;\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Trigger event action/README.md",
    "content": "Useful for tasks like triggering notifications or to trigger asynchronous script actions from a flow. \n\nThis flow action triggers the passed in event with optional parameters. Will work for most scenarios as the event record is passed in as a Document ID input.\n\nInputs:\n\nevent_name\t\t- Reference.Event Record - Mandatory\n\nevent_record\t- Document ID            - Mandatory\n\nparm1\t\t\t\t\t- String \n\nparm2\t\t\t\t\t- String\n\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Trigger event action/trigger event flow action.js",
    "content": "(function execute(inputs, outputs) {\n  \n  gs.eventQueue(inputs.event_name, inputs.event_record, inputs.parm1, inputs.parm2);\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Validate MID servers status inside cluster/MID Server Availibility inside MID Server Cluster.js",
    "content": "var flag = 0; //initial flag to 0\n  var glideCluster = new GlideRecord('ecc_agent_cluster_member_m2m'); //m2m table stores cluser and all the midserver relation\n  glideCluster.addQuery('cluster', \"sys_id of the cluster\"); //replace \"sys_id of the cluster\" with sys_id of the MID server cluster\n  glideCluster.query();\n  while(glideCluster.next()){\n    if(glideCluster.agent.status=='Up'){\n      outputs.flag = 1;  //if any one MID server is up proceed next step in the integration\n      break;\n    }\n    //else returns 0 and no MID server are up inside the cluster, abort integration logic and report to the concerened team.\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/Validate MID servers status inside cluster/README.md",
    "content": "During integration set up with flow designers which involves MID servers  , its better to check if MID server is up and running before we proceed with next steps in the integration.\nwhen we are using MID Server cluster (where more than one MID server as a failover) its recommended to verify if atleast one MID server is up and establish the connect \nMID server status inside cluster.js logic check if atleast one MID server is up if yes process next step in the integration if not abort the integration (we can have process to inform concered team)\nexample flow action :\nwhere \"inputs.midServerCluster.sys_id\" is Cluster sys_id as input to the action\n![image](https://github.com/gowdah/code-snippets/assets/42912180/82916a5b-213d-440f-94ef-bc3b99465fc6)\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/get Catalog Variables as JSON/README.md",
    "content": "Get catalog variables from a given RITM\n"
  },
  {
    "path": "Specialized Areas/Flow Actions/get Catalog Variables as JSON/getCatVarsAsJson.js",
    "content": "(function execute(inputs, outputs) {\n  \n  var grRequestedItem = new GlideRecord(\"sc_req_item\");\n  grRequestedItem.get(inputs.req);\n  \n  var jsonObj={};\n  for (var prop in grRequestedItem.variables) {\n    jsonObj[prop] = grRequestedItem.variables[prop].toString();\n  }\n  \n  outputs.json = JSON.stringify(jsonObj);\n\n})(inputs, outputs);\n"
  },
  {
    "path": "Specialized Areas/Formula Builder/Formula builder overview/README.md",
    "content": "## Formula Builder at a glance\n\nFormula Builder provides a more Excel-like experience in creating calculated fields in Glide tables, *without JavaScript*.\n\n## Benefits of a formula over a script\n\nNot only is it easier to write and read a formula over a script, formulas are more accurate than scripts due to the fact that JavaScript can have problems with floating point arithmetic which can be a big problem for financial calculations. Formulas, which are using arbitrary precision, are more accurate in this way.\n\n## How to use Formula Builder\n\nFormula builder has been added to the existing \"Calculated Value\" section of a column's dictionary entry.\n\n![1](1.jpg)\n\nAs a reminder, calculated values in a table column take values from other fields and the result of that computation is what is shown as the field value (similar to how a cell in Excel could contain a formula like `=A1+B1`)\n\nPrior to Tokyo, calculated values were only possible via JavaScript. With the new formula builder, it will be easier to build these formulas and also easier to read.\n\n1. Open a field's dictionary entry\n2. Navigate to the \"Calculated Value\" section/tab\n3. Check the checkbox next to \"Calculated\"\n4. Change the \"Calculation Type\" to `Formula`\n5. Build your calculation's formula in the \"Formula\" field\n6. Save your record\n\nIf you do not see the \"Calculated Value\" section/tab, switch to Advanced view via the Related Link UI Action.\n\n## Examples of possible formulas\n\n`CONCATENATE(first_name , \".\", last_name , \"@\", org.name , \".com\")`<br>\nCombine the first_name field and the last_name field as an email address.\n\n`LOWERCASE(CONCATENATE(file_prefix, NOW(), \".tmp\"))`<br>\nBuilds a filename with a prefix, the current date, and a file type, all in lowercase letters\n\n`AVERAGE(SUM(price_1, fuel_1), SUM(price_2, fuel_2))`<br>\nSums two sets a fields then finds the average of the two\n\n`SUM(MULTIPLY(celcius_temp, 1.8), 32)`<br>\nCalculate Fahrenheit based off of a Celcius field\n\n`TIMEDIFF(user.dob, NOW())`<br>\nCalculate the difference between a user's birthday (notice the dot walked field) and today's date\n\n## Formula components\n\nFormulas can have the following:\n\n- **Functions** like `SUM`, `REPLACE`, `TIMEDIFF`\n- **Variables** like column names on the current table. This includes dot walked fields. Note that you should use only column names, not display names.\n- **Operators** like `=` (equals), `<>` (not equals), `>` (greater  than), `>=` (greater than or equals to), `<`, and `<=`. Note that arithmetic binary operators (`+`, `-`, `/`, and `*`) are not supported. Instead, you would use the arithemtic functions instead (like `SUM` and `MULTIPLY`)\n- **Constants** like `\"hello\"` (a string) or `24` (a number). While JavaScript can use single quotes, note that string constants here are declared with double quotes only\n\nAs a reminder, formulas that contain nested functions work as you would expect in Excel: a function called in a parameter of another function must fully resolve before the parent function can continue.\n\nLet's break down the Celcius to Fahrenheit example above:\n\n`SUM(number1, number2, ...)`<br>\nSUM is the function name, whereas number1 and number2 (and so on) are the function's parameters\n\n`MULTIPLY(number1, number2, ...)`<br>\nSimilarly, MULTIPLY is the function name and number1 and number2 are the function's parameters\n\nAll together: `SUM(MULTIPLY(celcius_temp, 1.8), 32)`\n\n1. The calculated field attempts to SUM its 1st parameter and 2nd parameter together (in this case, the constant number `32`)\n2. The first parameter in the SUM function is another function, MULTIPLY, so the SUM function has to wait now\n3. The MULTIPLY function tries to multiply its 1st parameter (the value of the celcius_temp field of this record) and its second parameter (in this case, the constant number `1.8`). The MULTIPLY function has all that it needs to resolve and comes up with an answer.\n4. Now that SUM's 1st parameter has resolved, it can now finally resolve itself too.\n\n## Validation too!\n\n- If you misspell a column name, you will receive an \"invalid update\" error when trying to save the dictionary entry.\n- Syntax is validated\n- Symbol validation (checking if the field exists)\n- Incorrect function names are also validated\n\nExample:\n\n![2](2.png)\n\n## Available functions\n\nAt the time of this post being written:\n\n- `AND` Performs a logical AND operation on the arguments.\n- `AVERAGE` Returns the average value of the arguments.\n- `CONCATENATE` Joins one or more input strings into a single string.\n- `DIVIDE` Returns the quotient value after dividing argument 2 by argument 1.\n- `IF` Executes the specified statements based on the Boolean output of the conditional expression.\n- `ISBLANK` Finds white spaces or blank values in the string and returns true if there are any.\n- `LENGTH` Returns the total number of characters in the input string.\n- `LOWERCASE` Converts the input string to all lowercase characters.\n- `MAX` Returns the highest value in the specified arguments.\n- `MIN` Returns the lowest value in the specified arguments.\n- `MULTIPLY` Returns the multiplied value of the arguments.\n- `NOW` Returns the current date and time of the instance in ISO format.\n- `OR` Performs logical OR operation on the arguments.\n- `POWER` Returns the result of the base value raised to the power of the exponent value.\n- `REPLACE` Replaces characters in the source string with the characters in the target string.\n- `SUBTRACT` Returns the result value after subtracting argument 2 from argument 1.\n- `SUM` Returns the sum of all the arguments.\n- `TIMEDIFF` Finds difference between 2 dates for Duration field.\n- `TITLECASE` Converts the input string to all title case characters.\n- `UPPERCASE` Converts the input string to all uppercase characters.\n\n## Other things to note\n\n- Data types are not checked during validation. For example, if you have a number field and your formula returns a string, the result will be an error. An \"Unparsable\" error label will appear next to the calculated field in form view.\n- The list of available functions can be found on the `sys_formula_function` table\n\n## Formula Builder in Table Builder\n\nThe same functionality can be utilized in Table Builder. For details, you can visit the [release notes](https://docs.servicenow.com/bundle/tokyo-application-development/page/administer/form-builder/task/add-formula-column-table-builder.html)."
  },
  {
    "path": "Specialized Areas/Formula Builder/Get Age From Birthdate/README.md",
    "content": "## Formula builder to calculate the Age from birthdate\n\nThis Formula calculates at the beginning of Today to calculate the exact age from Birthdate.\n\nIF(\n  OR(\n    MONTH(birthdate) < MONTH(TODAY()), \n  AND(MONTH(birthdate) = MONTH(TODAY()), DAY(birthdate) < DAY(TODAY()))),  \n    SUBTRACT(YEAR(TODAY()), YEAR(birthdate)),  \n    SUBTRACT(SUBTRACT(YEAR(TODAY()), 1) ,YEAR(birthdate)))\n"
  },
  {
    "path": "Specialized Areas/Formula Builder/Get Age From Birthdate/script.js",
    "content": "IF(OR(MONTH(birthdate) < MONTH(TODAY()), AND(MONTH(birthdate) = MONTH(TODAY()), DAY(birthdate) < DAY(TODAY()))),  SUBTRACT(YEAR(TODAY()), YEAR(birthdate)),  SUBTRACT(SUBTRACT(YEAR(TODAY()), 1) ,YEAR(birthdate)))\n"
  },
  {
    "path": "Specialized Areas/ITOM/Bulk Location Update/README.md",
    "content": "This script will get all the CI classes mentioned in the property \"nonDiscovery.location.update\" in a comma-separated and fetch all the sub-classes of the mentioned classes and update the location data based on subnet information stored in 'cmdb_ci_ip_network_subnet' table and make updates in bulk on location basis."
  },
  {
    "path": "Specialized Areas/ITOM/Bulk Location Update/script.js",
    "content": "function convertIPtoNumber(ip){\n    return ip.split('.').reduce(function(ipInt, octet) { return (ipInt * 256) + parseInt(octet, 10);}, 0);\n}\n\nvar locationIps = {};\nvar locationInfo = {};\nvar hash = {};\n\n// 'cmdb_ci_ip_network_subnet' contains the subnet data along with location of the subnet\nvar grLocation = new GlideRecord('cmdb_ci_ip_network_subnet');\ngrLocation.query();\nwhile(grLocation.next()){\n\tvar location = grLocation.location.toString();\n\tvar hashKey = grLocation.u_starting_ip.toString()+\":\"+grLocation.u_ending_ip.toString();\n\thash[hashKey] = [convertIPtoNumber(grLocation.u_starting_ip.toString()),convertIPtoNumber(grLocation.u_ending_ip.toString())];\n\tlocationIps[hashKey] = location;\n\tif(!locationInfo.hasOwnProperty(location)){\n\t\tlocationInfo[location] = [];\n\t}\n}\n\n\ngs.include('j2js');\nvar ciClassesProperty = gs.getProperty('nonDiscovery.location.update').split(','); // This property contains the CI classes for which all the classes which are extending from the mentioned classes get the location updated. Ex: cmdb_ci_server, cmdb_ci_rubrik_node,etc.\nvar ciClasses = [];\nvar arrUtils = new ArrayUtil();\nfor(var ip in ciClassesProperty){\n\tvar tableUtil = new TableUtils(ciClassesProperty[ip]);\n    arrUtils.push(ciClassesProperty[ip]);\n\tarrUtils.concat(ciClasses,j2js(tableUtil.getTableExtensions()));\n}\n\nvar ciLocationData = new GlideRecord('cmdb_ci');\nciLocationData.addEncodedQuery('sys_class_nameIN'+ciClasses.toString()+'^locationISEMPTY^ip_addressISNOTEMPTY^install_status!=7^operational_status!=2');\nciLocationData.query();\nvar ipDict = {};\nwhile(ciLocationData.next()){\n\tvar ciIp = ciLocationData.ip_address.toString();\n\tvar ciInt = convertIPtoNumber(ciIp);\n\tipDict[ciInt] = ciIp;\n}\n\nfor(var ipVal in ipDict){\n\tfor(var ipKey in hash){\n\t\tvar ips = hash[ipKey];\n\t\tif(ips[0] <= ipVal && ipVal <= ips[1]){\n\t\t\tlocationInfo[locationIps[ipKey]].push(ipDict[ipVal]);\n\t\t\tbreak;\n\t\t}\n\t}\n}\n\nfor(var locationKey in locationInfo){\n\tvar ciUpdate = new GlideRecord('cmdb_ci');\n\tciUpdate.addEncodedQuery('sys_class_nameIN'+ciClasses.toString()+'^locationISEMPTY^install_status!=7^operational_status!=2^ip_addressIN'+locationInfo[locationKey].toString());\n\tciUpdate.query();\n\tciUpdate.setValue('location',locationKey);\n\tciUpdate.setWorkflow(false);\n\tciUpdate.updateMultiple();\n}"
  },
  {
    "path": "Specialized Areas/ITOM/Discovery/Pre README.md",
    "content": "# ServiceNow Discovery Pre Sensor Script: IP Router Association\n\nThis script is a **ServiceNow Discovery Pre Sensor Script** designed to enrich discovery payload data before it reaches the **Identification and Reconciliation Engine (IRE)**.  \n\nIt focuses on linking **Interface Cards (`cmdb_ci_interface_card`)** to their parent **IP Routers (`cmdb_ci_ip_router`)** by resolving the router name to its corresponding `sys_id` in the CMDB.\n\n\n## Overview\n\nWhen ServiceNow Discovery runs a pattern that identifies multiple components (e.g., routers and their interface cards), some payload items may contain **router names instead of sys_ids**.  \nThe IRE requires sys_ids for accurate relationship building.  \n\nThis script ensures that:\n- The `u_configuration_item` field on interface cards is replaced with the actual `sys_id` of the corresponding router.\n- The router’s `managed_by_group` field is also copied to the interface card record.\n- Payload is properly formatted and ready for IRE ingestion.\n\n##  Script Logic\n\n### Step-by-step Flow\n1. **Parse Payload:**  \n   Converts the input payload JSON string into an object for processing.\n\n2. **Iterate Items:**  \n   Loops through each payload item and filters those with `className = cmdb_ci_interface_card`.\n\n3. **Router Resolution:**  \n   For each interface card:\n   - Reads the router name from `u_configuration_item`.\n   - Searches for a matching router record in `cmdb_ci_ip_router`.\n   - If found:\n     - Replaces the router name with its `sys_id`.\n     - Copies the router’s `managed_by_group` value.\n\n4. **Return Updated Payload:**  \n   Returns the modified payload back to Discovery for further processing by the IRE.\n\n---\n\n##  Example Behavior\n\n### **Before Script Execution**\n```json\n{\n  \"items\": [\n    {\n      \"className\": \"cmdb_ci_interface_card\",\n      \"values\": {\n        \"name\": \"Router-1/Gigabit0/1\",\n        \"u_configuration_item\": \"Router-1\"\n      }\n    }\n  ]\n}\n\n### **After Script Execution**\n{\n  \"items\": [\n    {\n      \"className\": \"cmdb_ci_interface_card\",\n      \"values\": {\n        \"name\": \"Router-1/Gigabit0/1\",\n        \"u_configuration_item\": \"1b23cdef6f3123006a12ff3b8b3ee490\",\n        \"managed_by_group\": \"287ebd7da9fe198100f92cc8d1d2154e\"\n      }\n    }\n  ]\n}\n\n"
  },
  {
    "path": "Specialized Areas/ITOM/Discovery/PrePost Pattern designer.js",
    "content": "/*\n * Pre sensor: You can change payload before it will be proccesed by Identification Engine.\n * Use IEJsonUtility in order to add relevant information to the payload\n * Input parameters in Pre sensor mode: payload, patternIds\n */\n\n\nvar rtrn = {};\n\n// parsing the json string to a json object\nvar payloadObj = JSON.parse(payload);\n\n// Clearing payload string to save memory\npayload = null;\n\n// Put your business logic here\nvar handlegrIpRouterdata = function() {\n\n    gs.info('PD: handlegrIpRouter');\n\n    var ipRouterName = '';\n\n    var payloadItems = payloadObj.items;\n\n    for (var i = 0; i < payloadItems.length; i++) {\n\n        if (payloadItems[i].className === 'cmdb_ci_interface_card') { //Get Child class data  \n\n            var currentItem = payloadItems[i];\n\n            ipRouterName = currentItem.values.u_configuration_item;\n\n        \n\n            if (ipRouterName && ipRouterName.length) {\n\n                var grIpRouter = new GlideRecord('cmdb_ci_ip_router');\n\n                if (grIpRouter.get('name', ipRouterName)) { \n\n                    \n\n                    currentItem.values.u_configuration_item = grIpRouter.sys_id + '';\n\t\t\t\t\tcurrentItem.values.managed_by_group = grIpRouter.managed_by_group + '';\n\n                }\n\n\t\t\t\n\n            }\n\n        }\n\n    }\n\n};\nhandlegrIpRouterdata();\n// For node logger, please use: prePostNodeLogger.info\\warn\\error\\debug(prePostLogPrefix + '<YOUR_LOG_STATEMENT>')\n\n// You can return a message and a status, on top of the input variables that you MUST return.\n// Returning the payload as a Json String is mandatory in case of a pre sensor script, and optional in case of post sensor script.\n// If you want to terminate the payload processing due to your business logic - you can set isSuccess to false.\nrtrn = {\n\t'status': {\n\t\t'message': 'Enter your message here',\n\t\t'isSuccess' :true\n\t},\n\t'patternId': patternId,\n\t'payload': JSON.stringify(payloadObj)\n};\n"
  },
  {
    "path": "Specialized Areas/ITOM/Discovery/README.md",
    "content": "This code snippet automates the discovery of devices in ServiceNow using MID (Management, Instrumentation, and Discovery) servers by triggering Quick Discovery through a workflow. The code can also be modified for different workflows/ flows as needed.\n\nKey features include:\n\nMID Server Setup: It defines two MID servers to ensure redundancy, which improves the chances of successfully finding devices.\n\nIP Address Retrieval: The code retrieves the IP address to be scanned from the current workflow context.\n\nDiscovery Process:\n\nIt creates a Discovery object to initiate the process. The script first attempts to find the device using the first MID server. If that fails, it logs a message and tries again with the second MID server. Logging and Status Update: After the discovery attempts, the script logs the results and updates the current context with the discovery status.\n\nThis code enhances device discovery in ServiceNow, making the process more reliable by utilizing multiple MID servers.\n"
  },
  {
    "path": "Specialized Areas/ITOM/Discovery/Script to trigger quick discovery from workflow or flow.js",
    "content": "var midServer1 = 'SNOWMIDPRD01'; // Rename/input your MID server as per your organization\nvar midServer2 = 'SNOWMIDPRD02'; // Rename/input your second MID server accordingly\nvar ipAddressScan = current.variables.ip_address; // IP address to scan\nvar discovery = new Discovery(); // Create a new Discovery object\n\n// Attempt discovery using the first MID Server\nworkflow.scratchpad.statusID = discovery.discoveryFromIP(ipAddressScan, midServer1);\n\n// If the first attempt was unsuccessful, try the second MID Server\nif (workflow.scratchpad.statusID == null) {\n    gs.info('Discovery using ' + midServer1 + ' failed. Trying ' + midServer2);\n    workflow.scratchpad.statusID = discovery.discoveryFromIP(ipAddressScan, midServer2);\n}\n\n// Log the results\ngs.log('DiscoveryStatusId: ' + workflow.scratchpad.statusID);\ngs.log('QuickDiscoveryIPAddress: ' + ipAddressScan);\ncurrent.variables.discovery_status = workflow.scratchpad.statusID;"
  },
  {
    "path": "Specialized Areas/ITOM/Generate Discovery Schedule/README.md",
    "content": "This script will help in generating a discovery schedule with the list of IP addresses mentioned in the ip_list variable and then starting the discovery.\n\nIt has some properties present in it like:\n    -> discover.<region>.cluster => SYS ID of the regional cluster made\n    -> discover.mid_user => JSON object of mid_user accounts based on region similar to midCluster.\n        Ex:\n            {\n                \"europe\":\"<sys_id_of_mid_user_of_the_europe_cluster>\",\n                \"las_vegas\":\"<sys_id_of_mid_user_of_the_las_vegas_cluster>\",\n            }, etc.\n"
  },
  {
    "path": "Specialized Areas/ITOM/Generate Discovery Schedule/script.js",
    "content": "var randomNumberGenerator = Math.floor(Math.random() * (10000 - 1) + 1);\nvar regionSelect = '<select_one_of_the_regions_based_on_mid_cluster>';\nvar ip_list = '';   // all IP addresses which needs to be added to discovery schedule in a comma-separated fashion.\n\nvar midCluster = {      // Add more clusters based on region if required, I have taken example where there are 4 regions like europe, asia, norwalk, las vegas, etc.\n    'europe': gs.getProperty('discover.europe.cluster'),\n    'norwalk': gs.getProperty('discover.norwalk.cluster'),\n    'las_vegas': gs.getProperty('discover.las_vegas.cluster'),\n    'asia': gs.getProperty('discover.asia.cluster')\n};\n\nvar gds = new GlideRecord('discovery_schedule');\ngds.initialize();\ngds.name = randomNumberGenerator.toString();\ngds.discover = 'CIs';\ngds.run_as = JSON.parse(gs.getProperty('discover.mid_user'))[midCluster[regionSelect.toString()]];\ngds.active = true;\ngds.mid_select_method = 'specific_cluster';\ngds.mid_cluster = midCluster[regionSelect.toString()];\ngds.disco_run_type = 'on_demand';\ngds.shazzam_batch_size = 1000;\ngds.shazzam_cluster_support = true;\ngds.use_snmp_version = 'all';\nvar discovery_schedule_id = gds.insert();\n\n\nvar gdi = new GlideRecord('discovery_range_item');\ngdi.initialize();\ngdi.name = randomNumberGenerator.toString() + '-' + regionSelect.toString();\ngdi.active = true;\ngdi.type = 'IP Address List';\ngdi.schedule = discovery_schedule_id;\nvar discovery_range_set = gdi.insert();\n\n\nvar ipItems = [];\nvar grIp = new GlideRecord('discovery_range_item_ip');\nvar ips = ip_list;\nvar ip_list = ips.split(',');\nfor (var i = 0; i < ip_list.length; i++) {\n    grIp.initialize();\n    grIp.item_parent = discovery_range_set;\n    grIp.ip_address = ip_list[i];\n    var itemUniqVal = grIp.insert();\n    ipItems.push(itemUniqVal.toString());\n}\n\nvar discoverGr = new GlideRecord('discovery_schedule');\ndiscoverGr.addEncodedQuery('sys_id=' + discovery_schedule_id);\ndiscoverGr.query();\nvar discoveryStatus = '';\nif (discoverGr.next()) {\n    var discovery = new global.Discovery();\n    discoveryStatus = discovery.discoverNow(discoverGr);\n}\n\n"
  },
  {
    "path": "Specialized Areas/ITOM/Track Discovery Status/CheckDiscoveryStatus.js",
    "content": "// Configuration //\n// Define the list of IP addresses to check\nvar ipList = [\n    '192.168.1.35',\n    '192.168.1.27',\n    '192.168.1.15'\n];\n\n// Main Script //\nvar results = [];\n\nipList.forEach(function(ip) {\n    var result = {\n        ip_address: ip,\n        discovery_status_number: ''\n    };\n\n    //Find CI (Computer) with matching IP\n    var ciGR = new GlideRecord('cmdb_ci_computer');\n    ciGR.addQuery('ip_address', ip);\n    ciGR.query();\n\n    if (ciGR.next()) {\n        var ciSysId = ciGR.getUniqueValue();\n\n        //Check if CI has an entry in discovery_device_history\n        var historyGR = new GlideRecord('discovery_device_history');\n        historyGR.addQuery('ci', ciSysId);\n        historyGR.orderByDesc('sys_created_on');\n        historyGR.query();\n\n        if (historyGR.next()) {\n          \n            //Get discovery status number (e.g., DIS123456)\n            var statusGR = new GlideRecord('discovery_status');\n            if (statusGR.get(historyGR.discovery_status.toString())) {\n                result.discovery_status_number = statusGR.number.toString();\n            } else {\n                result.discovery_status_number = 'Discovery status record not found';\n            }\n        } else {\n            result.discovery_status_number = 'No discovery record found';\n        }\n    } else {\n        result.discovery_status_number = 'No CI found with this IP';\n    }\n\n    results.push(result);\n});\n\n// Output results to system log in JSON format\n\ngs.info('IP to Discovery Status Mapping:\\n' + JSON.stringify(results, null, 2));\n\n\n\n\n//Output//\n\n[\n  {\n    \"ip_address\": \"192.168.1.35\",\n    \"discovery_status_number\": \"DIS123145\"\n  },\n  {\n    \"ip_address\": \"192.168.1.27\",\n    \"discovery_status_number\": \"DIS123189\"\n  },\n  {\n    \"ip_address\": \"192.168.1.15\",\n    \"discovery_status_number\": \"No discovery record found\"\n  }\n]\n\n"
  },
  {
    "path": "Specialized Areas/ITOM/Track Discovery Status/ReadME.md",
    "content": "Defines a list of IP addresses to check (hardcoded in the script).\nExample IPs: 192.168.1.35, 192.168.1.27, 192.168.1.15.\nFor each IP address in the list:\nIt looks up the CI (Configuration Item) in the cmdb_ci_computer table with a matching IP.\nIf a CI is found:\nIt checks the discovery_device_history table to see if that CI was discovered by ServiceNow Discovery.\nIf discovery history exists, it finds the related Discovery Status record (discovery_status table) and gets its number (like DIS123456).\nIf no CI or discovery record is found, it notes the reason in the result.\nCompiles all results (IP + discovery status or error message) into a list.\nPrints the results in a clear JSON format in the system logs, making it easy to read and review.\n"
  },
  {
    "path": "Specialized Areas/ITOM/Track Discovery Status/discovery_ip_status.js",
    "content": "// Array of IP addresses to check\nvar ipAddressLists = ['192.168.1.35', '192.168.1.27', '192.168.1.15'];\n\n// CMDB table where CI records are stored(Could be Linux/Unix,AIX server,etc)\nvar ciTableName = 'cmdb_ci_computer';\n\n// Array to hold the final output objects\nvar ipDiscoveryMapping = [];\n\n// Loop through each IP address in the list\nfor (var i = 0; i < ipAddressLists.length; i++) {\n    var ip = ipAddressLists[i];\n\n    // Query the CMDB to find the CI record with the given IP address\n    var ciRecord = new GlideRecord(ciTableName);\n    ciRecord.addEncodedQuery('ip_address=' + ip);\n    ciRecord.query();\n\n    // Proceed only if a CI record is found for this IP\n    if (ciRecord.next()) {\n\n        // Query discovery_device_history to find the related discovery status\n        var deviceHistoryGr = new GlideRecord('discovery_device_history');\n        deviceHistoryGr.addEncodedQuery(\n            'cmdb_ci=' + ciRecord.getUniqueValue() +\n            '^last_state=Created CI^ORlast_state=Updated CI'\n        );\n        deviceHistoryGr.query();\n\n        // If discovery history exists, capture its status number\n        if (deviceHistoryGr.next()) {\n            // Create an object with IP and discovery status number\n            var recordObj = {\n                ip_address: ip,\n                discovery_status_number: deviceHistoryGr.getDisplayValue('status')\n            };\n            ipDiscoveryMapping.push(recordObj);\n        } else {\n            // Optional: handle case where no discovery status found\n            var noStatusObj = {\n                ip_address: ip,\n                discovery_status_number: 'No discovery record found'\n            };\n            ipDiscoveryMapping.push(noStatusObj);\n        }\n    } else {\n        // Optional: handle case where no CI record found\n        var noCIObj = {\n            ip_address: ip,\n            discovery_status_number: 'No CI found for this IP'\n        };\n        ipDiscoveryMapping.push(noCIObj);\n    }\n}\n\n// Log the final array of IP–Discovery Status mappings\ngs.info('IP to Discovery Status mapping: ' + JSON.stringify(ipDiscoveryMapping));\n"
  },
  {
    "path": "Specialized Areas/ITOM/Track Discovery Status/readme.md",
    "content": "After a recent migration, the client wanted a quick and reliable way to verify whether specific IP addresses were being discovered by ServiceNow Discovery, and if so, determine which Discovery Status records they were associated with.\n\nThis requirement was critical to help the client:\n\nIdentify IPs that were discovered successfully.\n\nFind those that were not rediscovered after migration.\n\nDiagnose potential discovery or configuration issues efficiently.\n\nThe script provided below serves as a powerful troubleshooting utility. It allows administrators to instantly check which discovery job (status) a given IP address belongs to, enabling faster debugging and drastically reducing resolution time.\n\nWhat the Script Does\n\nThe script performs the following steps:\n\nTakes a list of IP addresses as input.\n\nLooks up each IP in the cmdb_ci_computer table(It could be Linux, AIX,etc.) to find its corresponding Configuration Item (CI).\n\nQueries the Discovery Device History (discovery_device_history) to determine whether the CI was created or updated by a discovery process.\n\nBuilds a JSON array mapping each IP address to its respective discovery status number (e.g., DIS123145).\n\nPrints the result in the system logs for quick review and validation.\n\nExample output:\n\n[\n  {\"ip_address\": \"192.168.1.35\", \"discovery_status_number\": \"DIS123145\"},\n  {\"ip_address\": \"192.168.1.27\", \"discovery_status_number\": \"DIS123189\"},\n  {\"ip_address\": \"192.168.1.15\", \"discovery_status_number\": \"No discovery record found\"}\n]\n\nBenefits\n\nSaves significant debugging and analysis time.\n\nHelps identify why certain CIs stopped being discovered after migration.\n\nProvides clear mapping between IP addresses and their last discovery statuses.\n\nEnables faster root cause analysis and improves operational efficiency.\n"
  },
  {
    "path": "Specialized Areas/Notifications/Add KB Article Link Dynamic Email Script to Notification/readme.md",
    "content": "📘 README — KB Article Link Email Script for Notification\n✅ Overview\n\nThis script is used in a ServiceNow notification triggered from the kb_knowledge table.\nIt dynamically retrieves the instance URL and constructs a clickable Knowledge Article link in the email body.\n\n🧩 Script Functionality\n✔ What It Does\n\nBuilds a direct URL pointing to the KB article\nDisplays the KB Number as a clickable hyperlink\nOpens the article in a new browser tab\nImproves Knowledge Manager review workflow\n\n🔧 Script Used\n// This script assumes it is used in a notification triggered by the kb_knowledge table. \n//use this line to get KB Number ${mail_script:get_kbarticle_link}\n(function executeEmailScript(current, template) {\n    // Construct the URL to the KB article\n\n    var instanceURL = gs.getProperty('glide.servlet.uri'); // Get the instance URL\n    var kbLink = instanceURL + \"kb_view.do?sysparm_article=\" + current.number; // Adjust based on your URL structure\n\n    // Output the link in HTML format\n    template.print('<a href=\"' + kbLink + '\" target=\"_blank\">' + current.number + '</a>');\n})(current, template);\n\n📝 Notification Configuration\nSetting\tDetails\nTable\tKnowledge [kb_knowledge]\nCondition\tExample: State changes to Pending Approval\nEmail Script Used\t${mail_script:get_kbarticle_link}\nAudience\tKnowledge Managers / Approvers\n📥 Input\nInput Source\tDescription\ncurrent.number\tKB Article Number (e.g. KB0012345)\nSystem Property\tglide.servlet.uri — full instance URL\nNotification Payload\tUses script reference in message body\n\nExample Input Values:\n\nInstance URL: https://company.service-now.com/\nKB Number: KB1029384\n\n⚙️ Process Flow\n\n1️⃣ Notification triggered on kb_knowledge\n2️⃣ Script collects instance URL\n3️⃣ Script forms hyperlink using KB Number\n4️⃣ Link injected into template via template.print\n5️⃣ Email delivered to recipients\n\n✅ Output / Result\n\n📌 Email will show a clickable KB Number link:\n➡ Example Link Generated:\nhttps://company.service-now.com/kb_view.do?sysparm_article=KB1029384\n\n\n📌 In Email (HTML):\nKB1029384 → Click → Opens article in new browser tab\n\n📬 Final Email Result Includes:\nKB Article Number (hyperlinked)\nArticle details (added in notification body)\nApprove & Reject action buttons (if included)\nCleaner and faster approval workflow\n\n🖼️ Visual Result (Explained from Shared Image)\n\n<img width=\"733\" height=\"446\" alt=\"image\" src=\"https://github.com/user-attachments/assets/675b3d9b-f121-4c79-8253-bcb964ae05b3\" />\n\n\nThe screenshot you shared displays:\n✅ KB Article Number hyperlink\n✅ Metadata such as short description & requested by\n✅ Buttons for Article Approval / Rejection\n✅ HTML formatted clean layout for readability\n"
  },
  {
    "path": "Specialized Areas/Notifications/Add KB Article Link Dynamic Email Script to Notification/script.js",
    "content": "//Add KB Article Link Dynamic Email Script to Notification\n// This script assumes it is used in a notification triggered by the kb_knowledge table. \n//use this line to get KB Number ${mail_script:get_kbarticle_link}\n//NOTE THE NAME OF THIS SCRIPT IS \"get_kbarticle_link\"IN EMAIL SCRIPTS TABLE _ \"sys_script_email\"\n\n\n(function executeEmailScript(current, template) {\n    // Construct the URL to the KB article\n\n    var instanceURL = gs.getProperty('glide.servlet.uri'); // Get the instance URL\n    var kbLink = instanceURL + \"kb_view.do?sysparm_article=\" + current.number; // Adjust based on your URL structure\n\n    // Output the link in HTML format\n    template.print('<a href=\"' + kbLink + '\" target=\"_blank\">' + current.number + '</a>');\n})(current, template);\n"
  },
  {
    "path": "Specialized Areas/Notifications/Conditional Trigger/Notification_AdvancedCondition.js",
    "content": "var ritmis=new GlideRecord('sc_req_item');\nritmis.addQuery('request',current.sys_id);\nritmis.addQuery('cat_item.name','<catalog item name here>');\nritmis.query();\nif(ritmis.next())\n\t{\n\t\tif(ritmis.variables.variable_name=='ABCD')//replace variable_name with appropriate variable name and ABCD with correct value\n\t\t\t{\n\t\t\t\tanswer=true; //trigger\n\t\t\t}\n\t\telse\n\t\t\t{\n\t\t\t\tanswer=false; //do not trigger\n\t\t\t}\n\t}\n"
  },
  {
    "path": "Specialized Areas/Notifications/Conditional Trigger/README.md",
    "content": "This script is to be used in the Advanced condition field of the notification. This will help control if the notification is to be triggered or not to be triggered.\nReplace the <catalog item name here> with particular catalog item for which you want this notification condition to work.\nReplace the variable_name and ABCD with the values you want to compare and fire the notification accordingly.\nThis will help for cases where we want to check some condition/values not available on the table notification is on and trigger notification accordingly.\n"
  },
  {
    "path": "Specialized Areas/Notifications/Modern Email Layout Designs/Readme.md",
    "content": "### Overview\n\nAdded a **modern, fully responsive Email Layout** for ServiceNow notifications.  \nThis layout provides a professional and dynamic look for system emails such as approvals, alerts, and workflow updates.\n\n---\n\n### 🔑 Features\n\n- Clean, responsive HTML with inline CSS (Outlook-safe)\n- Dynamic placeholders for subject, body, recipient, and links\n- Supports unsubscribe and preference management variables\n- Compatible with all standard ServiceNow notification types\n- Easily customizable header colors, logo, and footer content\n\n---\n\n### 📁 Files Included\n\n| File          | Description                          |\n| ------------- | ------------------------------------ |\n| `Script.html` | Email Layout definition (HTML + CSS) |\n| `README.md`   | Setup guide and usage instructions   |\n\n---\n\n### ⚙️ Installation\n\n1. Navigate to **System Policy → Email → Email Layout** → New\n2. Paste the HTML layout above into the content field\n3. Save and name it **\"Modern Notification Layout\"**\n4. Assign this layout to your email notifications (under \"Layout\" field)\n\n---\n\n### 💡 Example Use Case\n\nUsed for travel approvals, expense updates, password resets, or ticket notifications:\n\n```html\n${mail_script:travel_notification}\n```\n"
  },
  {
    "path": "Specialized Areas/Notifications/Modern Email Layout Designs/script.html",
    "content": "<p></p>\n<style>\n  body {\n    font-family: Arial, sans-serif;\n    background-color: #f4f4f4;\n    margin: 0;\n    padding: 0;\n  }\n  .container {\n    width: 100%;\n    max-width: 600px;\n    margin: 20px auto;\n    background: #ffffff;\n    padding: 0;\n    border-radius: 8px;\n    box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.1);\n    overflow: hidden;\n  }\n  .header {\n    background: #004080;\n    color: #ffffff;\n    text-align: center;\n    padding: 20px;\n  }\n  .header img {\n    max-width: 150px;\n  }\n  .subject {\n    font-size: 20px;\n    font-weight: 600;\n    margin-top: 10px;\n  }\n  .content {\n    padding: 24px;\n    color: #333;\n    line-height: 1.6;\n    font-size: 15px;\n  }\n  .button {\n    display: inline-block;\n    padding: 10px 18px;\n    margin-top: 15px;\n    background: #004080;\n    color: #ffffff;\n    text-decoration: none;\n    border-radius: 5px;\n  }\n  .footer {\n    background: #f4f4f4;\n    text-align: center;\n    padding: 16px;\n    font-size: 12px;\n    color: #666;\n    border-top: 1px solid #e2e2e2;\n  }\n  a {\n    color: #004080;\n    text-decoration: none;\n  }\n</style>\n\n<div class=\"container\">\n  <!-- Header -->\n  <div class=\"header\">\n    <img src=\"${URI_REF}/travel_logo.png\" alt=\"Logo\" width=\"120\" height=\"40\" />\n  </div>\n\n  <!-- Body -->\n  <div class=\"content\">\n    ${notification.body}\n\n    <p style=\"margin-top: 30px; font-size: 13px; color: #777\">\n      If you have any questions, contact\n      <a href=\"mailto:support@company.com\">support@company.com</a>.\n    </p>\n  </div>\n\n  <!-- Footer -->\n  <div class=\"footer\">\n    Travel & Expense. All Rights\n    Reserved.<br />\n    <br />\n    <a href=\"${NOTIF_UNSUB}\">Unsubscribe</a> |\n    <a href=\"${NOTIF_PREFS}\">Manage Preferences</a>\n  </div>\n</div>\n"
  },
  {
    "path": "Specialized Areas/Notifications/Notify Users on Specific Date/NotifyUsers.js",
    "content": "// Trigger Business Rule that runs on Insert/Update\n\n(function executeRule(current, previous /*null when async*/) {\n\n\t// Add your code here\n\n\tvar gDT = new GlideDateTime(current.u_email_to_be_sent_for_date); // You can select your date field when an email needs to be sent out\n\n\tgs.eventQueueScheduled('email_date', current, '', '', gDT); // Trigger an eventQueueScheduled method so that event can be triggered, the event can be created of your choice\n\n})(current, previous);\n\n// The event Name here is email_date, it can changed according to the needs\n\n// Notification record will be created to generate a notification\n\nWhen to Send - The event is triggered, in my case it is email_date\nWho will receive - Caller of an incident, you can select your recipients\nWhat it will contain - An Email body of your choice\n"
  },
  {
    "path": "Specialized Areas/Notifications/Notify Users on Specific Date/README.md",
    "content": "This configuration will send an email on the specified date.\n\nThis will use a date field to trigger email notifications.\n\nBusiness Rule will run on Insert/Update to trigger the event. As soon as the event is triggered, the notification will be in the queue to be triggered on the specified date field.\n"
  },
  {
    "path": "Specialized Areas/On-Call Calendar/Show group on-call schedule/README.md",
    "content": "# Displaying On-Call Schedule or Calendar next to the assignment group\n\nThis code provides a Jelly script UI Macros to display an on-call schedule button in ServiceNow table. It allows users to view the on-call schedule for a specific group by generating a URL to the on-call workbench.\n\n## Features\n- Generates a button in ServiceNow that opens the on-call schedule for a selected group.\n- Uses GlideRecord to check for active on-call rotations.\n- Constructs a URL with proper encoding to navigate to the on-call workbench.\n\nI have used this code on the Incident Table \n\n\n"
  },
  {
    "path": "Specialized Areas/On-Call Calendar/Show group on-call schedule/show_group_on_call_schedule.js",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\" ?>\n<j:jelly trim=\"false\" xmlns:j=\"jelly:core\" xmlns:g=\"glide\" xmlns:j2=\"null\" xmlns:g2=\"null\">\n    <g:evaluate var=\"jvar_guid\" expression=\"gs.generateGUID(this);\" />\n    <j:set var=\"jvar_n\" value=\"show_schedule_${jvar_guid}:${ref}\"/>\n    <g:reference_decoration id=\"${jvar_n}\" field=\"${ref}\"\n        onclick=\"showOnCallSchedule('${jvar_n}')\"\n        title=\"${gs.getMessage('Show group on-call schedule')}\" image=\"images/icons/tasks.gifx\" icon=\"icon-calendar\"/>\n    <script>\n        function showOnCallSchedule(reference){\n            var group = g_form.getValue(reference.split('.')[1]);\n            var grpRota = new GlideRecord('cmn_rota');\n            grpRota.addQuery('group', group);\n            grpRota.addQuery('active', true);\n            grpRota.query();\n            if(grpRota.hasNext()){\n                // Get the sys_id (assuming you have a method to retrieve the correct sys_id)\n                var sysId = '3D' + group; // Ensure the sys_id starts with \"3D\"\n                // Construct a URL for the popup window using $[AMP]\n                var url = 'https://instancename.service-now.com/now/nav/ui/classic/params/target/%24oc_workbench.do%3Fsysparm_redirect%3D%2524oc_groups.do$[AMP]sysparm_group_id%3D' + encodeURIComponent(group);\n                // Open the popup\n                popupOpenStandard(url);\n            } else {\n                alert('No active rotation specified for the selected group.');\n            }\n        }\n    </script>\n</j:jelly>\n"
  },
  {
    "path": "Specialized Areas/Performance Analytics/Configure Indicators in Batch/ConfigurePAIndicators.js",
    "content": "(function () {\n\n\t// The only time series we want to keep (makes data collection jobs run faster)\n\tvar CONST_PA_TIME_SERIES_7D_AVG_SYSID = '9ef05051d7001100ba986f14ce610372';\n\t\n\t// Cache all the aggregates (Time Series) to avoid querying them for each indicator\n\t\n\tvar paAggregatesArr = [];\n\tvar grPaAggregate = new GlideRecord('pa_aggregates');\n\tgrPaAggregate.query();\n\twhile (grPaAggregate.next()) {\n\t\tpaAggregatesArr.push(String(grPaAggregate.getUniqueValue()));\n\t}\n\t\n\t// Get through all the indicators and configure them like we want\n\t\n\tvar grIndicator = new GlideRecord('pa_indicators');\n\tgrIndicator.addQuery('name', 'STARTSWITH', 'Whatever you need '); // **** Update your query here ****\n\tgrIndicator.orderBy('name');\n\tgrIndicator.query();\n\t\n\twhile (grIndicator.next()) {\n\t\t\n\t\t// Depending on the unit type, set proper attributes\n\t\t\n\t\tif (grIndicator.unit.name.toString() == '%') {\n\t\t\tgrIndicator.excluded_statistics.setValue('Sum');\n\t\t\tgrIndicator.precision.setValue(1);\n            grIndicator.direction.setValue('3');\n\t\t} else if (grIndicator.unit.name.toString() == '#') {\n\t\t\tgrIndicator.excluded_statistics.setValue('');\n\t\t\tgrIndicator.precision.setValue(0);\n            grIndicator.aggregate.setValue('1');\t\t\t\n\t\t}\n\t\t\n\t\t// Set standard values for other attributes \n\n\t\tgrIndicator.key.setValue(false);\n\t\tgrIndicator.value_when_nil.setValue('');\n\t\t\n\t\t// Set data retention periods for scores and collected records (for automated indicators only)\n\t\t\n\t\tif (grIndicator.type == 1) {\n\t\t\tgrIndicator.override_periods.setValue(true);\n\t\t\tgrIndicator.score_periods.setValue(90);\t\t\n\t\t\tgrIndicator.snapshot_periods.setValue(15);\t\t\n\t\t}\n\t\t\t\n\t\t// Only keep the 7d AVG time series, exclude all the other ones\n\t\t\n\t\tvar grTimeSeriesExclusionDel = new GlideRecord('pa_indicator_aggregate_excl');\n\t\tgrTimeSeriesExclusionDel.addQuery('indicator', grIndicator.getUniqueValue());\n\t\tgrTimeSeriesExclusionDel.deleteMultiple();\n\t\t\t\t\n\t\tfor (var i = 0; i < paAggregatesArr.length; i++) {\n\t\t\n\t\t\t// We only keey 7d AVG\n\t\t\tif (paAggregatesArr[i] != CONST_PA_TIME_SERIES_7D_AVG_SYSID) {\n\t\t\t\tvar grTimeSeriesExclusionAdd = new GlideRecord('pa_indicator_aggregate_excl');\n\t\t\t\tgrTimeSeriesExclusionAdd.newRecord();\n\t\t\t\tgrTimeSeriesExclusionAdd.setValue('indicator', grIndicator.getUniqueValue());\n\t\t\t\tgrTimeSeriesExclusionAdd.setValue('aggregate', paAggregatesArr[i]);\n\t\t\t\tgrTimeSeriesExclusionAdd.insert();\n\t\t\t}\n\n\t\t}\n\n\t\t// Update the record\n\t\tgrIndicator.update();\n\t\t\n\t}\n\n})();\n"
  },
  {
    "path": "Specialized Areas/Performance Analytics/Configure Indicators in Batch/README.md",
    "content": "# Configure Performance Analytics in Batch\n\n## Problem it solves\n\nWhen dealing with many PA indicators, it can become cumbersome to manually configure each of them. Typical things to configure are:\n* Excluded statistics (for example the SUM stats are not applicable to percentage units)\n* Precision\n* Direction\n* Visibility settings\n* Whether the indicator is key or not\n* Value when nil\n* Data retention periods\n* Some data aggregations (can consume lots of calculation time for nothing if they're not required)\n\nI realised that more than often, I want the same baseline config for all the indicators of a given domain or application. \n\n## Solution\n\nThis code snippet loops through all the indicators that match a query, and configure them one by one. Here, this is an example of how I configure them, but each use case might require different properties. The code of the script can remain the same though, and adapted to change the values, or even update more properties.\n\n## Possible Improvements\n\nExtend this code snippet to:\n* Add breakdowns\n* Add indicator groups\n* Add/create data collection job\n"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/CMDB Configuration Items/README.md",
    "content": "\n# CMDB Configuration Items Training Data Quality Analyzer\n\n## Overview\nThis script analyzes the quality of CMDB Configuration Item (CI) data in ServiceNow to assess readiness for Predictive Intelligence (PI) model training. It provides statistics and actionable insights to help ServiceNow developers and admins improve data quality before building machine learning models.\n\n## Purpose\n- Evaluate completeness and quality of key CMDB fields\n- Identify operational status distribution across CIs\n- Highlight data issues that could impact PI model performance\n- Support preparation of balanced, representative training datasets\n\n## Features\n- Dynamically discovers and counts unique operational status values\n- Reports the number of CIs in each operational state\n- Checks for missing or default operational status values\n- Outputs summary statistics to ServiceNow system logs\n- Easily customizable for additional CMDB fields or metrics\n\n## Setup Requirements\n1. **ServiceNow Instance** with Predictive Intelligence plugin enabled\n2. **Script Execution Permissions**: Run as a background script or Script Include with access to the `cmdb_ci` table\n3. **No external dependencies**: Uses standard ServiceNow APIs (GlideRecord, GlideAggregate)\n4. **Sufficient Data Volume**: At least 50 CIs recommended for meaningful analysis\n\n## How It Works\n1. **Operational Status Analysis**: Counts CIs by each operational status value found in your data\n2. **Summary Output**: Prints counts and distinct states to system logs\n3. **Data Quality Insights**: Helps identify imbalances, missing values, or underused states\n4. **Customizable Logic**: Easily extend to analyze other CMDB fields or add mapping for status codes\n\n"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/CMDB Configuration Items/analyze_cmdb_data_training_quality.js",
    "content": "(function analyzeCMDBDataQuality() {\n    var config = {\n        table: 'cmdb_ci',\n        keyFields: [\n            'name',\n            'ci_class',\n            'operational_status',\n            'install_status', \n            'location',\n            'assigned_to',\n            'manufacturer',\n            'serial_number',\n            'discovery_source',\n            'last_discovered'\n        ],\n        thresholds: {\n            minNameLength: 5,\n            maxDataAge: 90, // days since last discovered\n            maxAge: 365, // days to look back for analysis\n            targetCompleteness: 75,\n            duplicateThreshold: 3 // max CIs with same serial\n        },\n        operationalStates: {\n            active: ['1', '6'], // In Use, Available\n            inactive: ['2', '3', '4', '5', '7', '8', '100'] // Non-operational, Repair, Retired, etc.\n        },\n        sampleSize: 1000\n    };\n\n    gs.info('========================================');\n    gs.info('PI Training Data Quality Analysis (CMDB CI)');\n    gs.info('========================================');\n\n    // Identify available operational states\n    var actualStates = identifyOperationalStates();\n    gs.info('Operational states found: ' + Object.keys(actualStates).length);\n    \n    // Get overall statistics\n    var stats = getOverallStats(config);\n    gs.info('');\n    gs.info('=== STEP 1: Overall Statistics ===');\n    gs.info('Total Configuration Items: ' + stats.total);\n    gs.info('Active/Operational CIs: ' + stats.active);\n    gs.info('Inactive/Non-operational CIs: ' + stats.inactive);\n    gs.info('Recent 90 Days: ' + stats.recent90);\n    gs.info('Recent 365 Days: ' + stats.recent365);\n    gs.info('Unique CI Classes: ' + stats.uniqueClasses);\n    gs.info('');\n\n    if (stats.total < 100) {\n        gs.warn('⚠️ Low number of CIs - may not be sufficient for comprehensive analysis');\n        gs.info('Current: ' + stats.total);\n    } else {\n        gs.info('✅ Sufficient CIs for analysis');\n    }\n\n    // Field completeness analysis\n    gs.info('');\n    gs.info('=== STEP 2: Field Completeness Analysis ===');\n    var completeness = analyzeFieldCompleteness(config);\n    gs.info('Field Completeness Scores:');\n    for (var field in completeness) {\n        var pct = completeness[field].percentage;\n        var icon = pct >= 80 ? '✅' : pct >= 50 ? '⚠️' : '❌';\n        gs.info(icon + ' ' + field + ': ' + pct.toFixed(1) + '% (' + \n                completeness[field].filled + '/' + completeness[field].total + ')');\n    }\n\n    // Data freshness analysis\n    gs.info('');\n    gs.info('=== STEP 3: Data Freshness Analysis ===');\n    var freshnessAnalysis = analyzeDataFreshness(config);\n    gs.info('Discovery Data Freshness:');\n    gs.info('  Recent (< 30 days): ' + freshnessAnalysis.recent + ' (' + freshnessAnalysis.recentPct.toFixed(1) + '%)');\n    gs.info('  Stale (> 90 days): ' + freshnessAnalysis.stale + ' (' + freshnessAnalysis.stalePct.toFixed(1) + '%)');\n    gs.info('  Never discovered: ' + freshnessAnalysis.neverDiscovered + ' (' + freshnessAnalysis.neverDiscoveredPct.toFixed(1) + '%)');\n    gs.info('  Average age: ' + freshnessAnalysis.avgAge.toFixed(0) + ' days');\n\n    // Duplicate detection\n    gs.info('');\n    gs.info('=== STEP 4: Duplicate Detection ===');\n    var duplicates = analyzeDuplicates(config);\n    gs.info('Potential Duplicates:');\n    gs.info('  Serial Number Duplicates: ' + duplicates.serialDuplicates + ' groups');\n    gs.info('  Name Similarity Issues: ' + duplicates.nameDuplicates + ' potential groups');\n    gs.info('  Total Affected CIs: ' + duplicates.totalAffected);\n\n    // CI Class distribution\n    gs.info('');\n    gs.info('=== STEP 5: CI Class Distribution ===');\n    var classDist = analyzeCIClassDistribution(config);\n    gs.info('Top 10 CI Classes:');\n    for (var i = 0; i < Math.min(10, classDist.length); i++) {\n        var cls = classDist[i];\n        gs.info('  ' + (i+1) + '. ' + (cls.ci_class || '(empty)') + ': ' + cls.count + ' CIs');\n    }\n\n    // Naming quality analysis  \n    gs.info('');\n    gs.info('=== STEP 6: Naming Quality Analysis ===');\n    var namingQuality = analyzeNamingQuality(config);\n    gs.info('CI Naming Quality:');\n    gs.info('  Average name length: ' + namingQuality.avgLength.toFixed(0) + ' characters');\n    gs.info('  Too short names (<5 chars): ' + namingQuality.tooShort + ' (' + namingQuality.tooShortPct.toFixed(1) + '%)');\n    gs.info('  Good quality names: ' + namingQuality.goodQuality + ' (' + namingQuality.goodQualityPct.toFixed(1) + '%)');\n    gs.info('  Generic/Default names: ' + namingQuality.genericNames + ' (' + namingQuality.genericNamesPct.toFixed(1) + '%)');\n\n    // Overall score\n    var overallScore = calculateOverallScore(completeness, freshnessAnalysis, duplicates, namingQuality);\n    gs.info('');\n    gs.info('=== OVERALL CMDB QUALITY SCORE ===');\n    var scoreIcon = overallScore >= 80 ? '✅' : overallScore >= 60 ? '⚠️' : '❌';\n    gs.info(scoreIcon + ' Score: ' + overallScore.toFixed(0) + '/100');\n    \n    if (overallScore >= 80) {\n        gs.info('✅ EXCELLENT - CMDB data is high quality');\n    } else if (overallScore >= 60) {\n        gs.info('⚠️ FAIR - CMDB has some quality issues to address');\n    } else {\n        gs.info('❌ POOR - Significant CMDB quality issues exist');\n    }\n\n    gs.info('');\n    gs.info('========================================');\n    gs.info('CMDB Analysis Complete');\n    gs.info('========================================');\n\n    // Helper functions\n    function identifyOperationalStates() {\n        var stateGr = new GlideAggregate('cmdb_ci');\n        stateGr.groupBy('operational_status');\n        stateGr.addAggregate('COUNT');\n        stateGr.query();\n        \n        var states = {};\n        while (stateGr.next()) {\n            var state = stateGr.getValue('operational_status');\n            var count = stateGr.getAggregate('COUNT');\n            states[state] = parseInt(count);\n            gs.info('Operational status ' + state + ': ' + count + ' CIs');\n        }\n        \n        return states;\n    }\n\n    function getOverallStats(config) {\n        var result = {total: 0, active: 0, inactive: 0, recent90: 0, recent365: 0, uniqueClasses: 0};\n        \n        // Total CIs\n        var totalGr = new GlideAggregate('cmdb_ci');\n        totalGr.addAggregate('COUNT');\n        totalGr.query();\n        if (totalGr.next()) {\n            result.total = parseInt(totalGr.getAggregate('COUNT'));\n        }\n        \n        // Active CIs (operational status 1 = In Use, 6 = Available)\n        var activeGr = new GlideAggregate('cmdb_ci');\n        activeGr.addQuery('operational_status', 'IN', config.operationalStates.active.join(','));\n        activeGr.addAggregate('COUNT');\n        activeGr.query();\n        if (activeGr.next()) {\n            result.active = parseInt(activeGr.getAggregate('COUNT'));\n        }\n        \n        // Inactive CIs\n        var inactiveGr = new GlideAggregate('cmdb_ci');\n        inactiveGr.addQuery('operational_status', 'IN', config.operationalStates.inactive.join(','));\n        inactiveGr.addAggregate('COUNT');\n        inactiveGr.query();\n        if (inactiveGr.next()) {\n            result.inactive = parseInt(inactiveGr.getAggregate('COUNT'));\n        }\n        \n        // Recent counts\n        var recent365Gr = new GlideAggregate('cmdb_ci');\n        recent365Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(365)');\n        recent365Gr.addAggregate('COUNT');\n        recent365Gr.query();\n        if (recent365Gr.next()) {\n            result.recent365 = parseInt(recent365Gr.getAggregate('COUNT'));\n        }\n        \n        var recent90Gr = new GlideAggregate('cmdb_ci');\n        recent90Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(90)');\n        recent90Gr.addAggregate('COUNT');\n        recent90Gr.query();\n        if (recent90Gr.next()) {\n            result.recent90 = parseInt(recent90Gr.getAggregate('COUNT'));\n        }\n        \n        // Unique CI classes\n        var classGr = new GlideAggregate('cmdb_ci');\n        classGr.groupBy('ci_class');\n        classGr.addAggregate('COUNT');\n        classGr.query();\n        while (classGr.next()) {\n            result.uniqueClasses++;\n        }\n        \n        return result;\n    }\n\n    function analyzeFieldCompleteness(config) {\n        var results = {};\n        \n        // Get total count\n        var totalGr = new GlideAggregate('cmdb_ci');\n        totalGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        totalGr.addAggregate('COUNT');\n        totalGr.query();\n        \n        var total = 0;\n        if (totalGr.next()) {\n            total = parseInt(totalGr.getAggregate('COUNT'));\n        }\n        \n        gs.info('Analyzing ' + total + ' CIs for field completeness...');\n        \n        for (var f = 0; f < config.keyFields.length; f++) {\n            var fieldName = config.keyFields[f];\n            var testGr = new GlideRecord('cmdb_ci');\n            if (!testGr.isValidField(fieldName)) {\n                gs.warn('Field ' + fieldName + ' not found, skipping');\n                continue;\n            }\n            \n            var filledGr = new GlideAggregate('cmdb_ci');\n            filledGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n            filledGr.addQuery(fieldName, '!=', '');\n            filledGr.addNotNullQuery(fieldName);\n            filledGr.addAggregate('COUNT');\n            filledGr.query();\n            \n            var filled = 0;\n            if (filledGr.next()) {\n                filled = parseInt(filledGr.getAggregate('COUNT'));\n            }\n            \n            results[fieldName] = {\n                total: total,\n                filled: filled,\n                percentage: total > 0 ? (filled / total * 100) : 0\n            };\n        }\n        \n        return results;\n    }\n\n    function analyzeDataFreshness(config) {\n        var gr = new GlideRecord('cmdb_ci');\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        gr.setLimit(config.sampleSize);\n        gr.query();\n\n        var stats = {\n            total: 0,\n            recent: 0,\n            stale: 0,\n            neverDiscovered: 0,\n            totalAge: 0,\n            ageCount: 0\n        };\n\n        var now = new GlideDateTime();\n        \n        while (gr.next()) {\n            stats.total++;\n            var lastDiscovered = gr.getValue('last_discovered');\n            \n            if (!lastDiscovered) {\n                stats.neverDiscovered++;\n            } else {\n                var discoveryDate = new GlideDateTime(lastDiscovered);\n                var diffMs = now.getNumericValue() - discoveryDate.getNumericValue();\n                var ageDays = diffMs / (1000 * 60 * 60 * 24);\n                \n                stats.totalAge += ageDays;\n                stats.ageCount++;\n                \n                if (ageDays <= 30) {\n                    stats.recent++;\n                } else if (ageDays > config.thresholds.maxDataAge) {\n                    stats.stale++;\n                }\n            }\n        }\n\n        return {\n            recent: stats.recent,\n            recentPct: stats.total > 0 ? (stats.recent / stats.total * 100) : 0,\n            stale: stats.stale,\n            stalePct: stats.total > 0 ? (stats.stale / stats.total * 100) : 0,\n            neverDiscovered: stats.neverDiscovered,\n            neverDiscoveredPct: stats.total > 0 ? (stats.neverDiscovered / stats.total * 100) : 0,\n            avgAge: stats.ageCount > 0 ? (stats.totalAge / stats.ageCount) : 0\n        };\n    }\n\n    function analyzeDuplicates(config) {\n        var results = {\n            serialDuplicates: 0,\n            nameDuplicates: 0,\n            totalAffected: 0\n        };\n\n        // Check for serial number duplicates\n        var serialGr = new GlideAggregate('cmdb_ci');\n        serialGr.addQuery('serial_number', '!=', '');\n        serialGr.addNotNullQuery('serial_number');\n        serialGr.groupBy('serial_number');\n        serialGr.addAggregate('COUNT');\n        serialGr.addHaving('COUNT', '>', '1');\n        serialGr.query();\n        \n        while (serialGr.next()) {\n            var count = parseInt(serialGr.getAggregate('COUNT'));\n            if (count > 1) {\n                results.serialDuplicates++;\n                results.totalAffected += count;\n            }\n        }\n\n        // Simple name similarity check (same name with different cases/spaces)\n        var nameGr = new GlideAggregate('cmdb_ci');\n        nameGr.addQuery('name', '!=', '');\n        nameGr.groupBy('name');\n        nameGr.addAggregate('COUNT');\n        nameGr.addHaving('COUNT', '>', '1');\n        nameGr.query();\n        \n        while (nameGr.next()) {\n            var count = parseInt(nameGr.getAggregate('COUNT'));\n            if (count > 1) {\n                results.nameDuplicates++;\n            }\n        }\n\n        return results;\n    }\n\n    function analyzeCIClassDistribution(config) {\n        var classGr = new GlideAggregate('cmdb_ci');\n        classGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        classGr.groupBy('ci_class');\n        classGr.addAggregate('COUNT');\n        classGr.query();\n\n        var classes = [];\n        while (classGr.next()) {\n            classes.push({\n                ci_class: classGr.getDisplayValue('ci_class'),\n                count: parseInt(classGr.getAggregate('COUNT'))\n            });\n        }\n\n        classes.sort(function(a, b) { return b.count - a.count; });\n        return classes;\n    }\n\n    function analyzeNamingQuality(config) {\n        var gr = new GlideRecord('cmdb_ci');\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        gr.setLimit(config.sampleSize);\n        gr.query();\n\n        var stats = {\n            totalLength: 0,\n            count: 0,\n            tooShort: 0,\n            goodQuality: 0,\n            genericNames: 0\n        };\n\n        var genericPatterns = ['server', 'computer', 'device', 'unknown', 'default', 'test', 'temp'];\n\n        while (gr.next()) {\n            var name = gr.getValue('name') || '';\n            if (name) {\n                stats.count++;\n                stats.totalLength += name.length;\n                \n                if (name.length < config.thresholds.minNameLength) {\n                    stats.tooShort++;\n                } else {\n                    stats.goodQuality++;\n                }\n\n                // Check for generic names\n                var lowerName = name.toLowerCase();\n                for (var p = 0; p < genericPatterns.length; p++) {\n                    if (lowerName.indexOf(genericPatterns[p]) >= 0) {\n                        stats.genericNames++;\n                        break;\n                    }\n                }\n            }\n        }\n\n        return {\n            avgLength: stats.count > 0 ? stats.totalLength / stats.count : 0,\n            tooShort: stats.tooShort,\n            tooShortPct: stats.count > 0 ? (stats.tooShort / stats.count * 100) : 0,\n            goodQuality: stats.goodQuality,\n            goodQualityPct: stats.count > 0 ? (stats.goodQuality / stats.count * 100) : 0,\n            genericNames: stats.genericNames,\n            genericNamesPct: stats.count > 0 ? (stats.genericNames / stats.count * 100) : 0\n        };\n    }\n\n    function calculateOverallScore(completeness, freshness, duplicates, naming) {\n        var score = 0;\n        var weights = {\n            completeness: 30,\n            freshness: 25,\n            duplicates: 25,\n            naming: 20\n        };\n\n        // Completeness score\n        var compTotal = 0, compCount = 0;\n        for (var field in completeness) {\n            compTotal += completeness[field].percentage;\n            compCount++;\n        }\n        var compScore = compCount > 0 ? (compTotal / compCount) : 0;\n        score += (compScore / 100) * weights.completeness;\n\n        // Freshness score (higher is better)\n        var freshnessScore = Math.max(0, 100 - freshness.stalePct - (freshness.neverDiscoveredPct * 0.5));\n        score += (freshnessScore / 100) * weights.freshness;\n\n        // Duplicate score (lower duplicates = higher score)\n        var duplicateScore = duplicates.totalAffected > 0 ? Math.max(0, 100 - (duplicates.totalAffected / 10)) : 100;\n        score += (duplicateScore / 100) * weights.duplicates;\n\n        // Naming score\n        var namingScore = naming.goodQualityPct - (naming.genericNamesPct * 0.5);\n        score += (namingScore / 100) * weights.naming;\n\n        return Math.min(100, Math.max(0, score));\n    }\n\n})();"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Table/README.md",
    "content": "# Training Data Quality Analyzer for ServiceNow Predictive Intelligence (Change Requests)\n\n## Overview\nThis script analyzes the quality of change request data in ServiceNow to determine readiness for Predictive Intelligence (PI) model training. It provides detailed statistics and quality metrics to help ServiceNow developers and admins identify and address data issues before starting ML training jobs.\n\n## Purpose\n- Assess completeness and quality of key fields in change request records\n- Identify common data issues that could impact PI model performance\n- Provide actionable insights for improving training data\n\n## Features\n- Checks completeness of important fields (e.g., short_description, description, category, risk, assignment_group, implementation_plan, test_plan, backout_plan, close_notes)\n- Analyzes text quality for description, implementation/test/backout plans, and close notes\n- Evaluates category diversity and closure times\n- Calculates an overall data quality score\n- Outputs results to the ServiceNow system logs\n\n## Setup Requirements\n1. **ServiceNow Instance** with Predictive Intelligence plugin enabled\n2. **Script Execution Permissions**: Run as a background script or Script Include with access to the `change_request` table\n3. **No external dependencies**: Uses only standard ServiceNow APIs (GlideRecord, GlideAggregate, GlideDateTime)\n4. **Sufficient Data Volume**: At least 50 closed change requests recommended for meaningful analysis\n\n## How It Works\n1. **Field Existence Check**: Dynamically verifies that each key field exists on the change_request table or its parent tables\n2. **Statistics Gathering**: Collects counts for total, closed, and recent change requests\n3. **Completeness Analysis**: Calculates the percentage of records with each key field filled\n4. **Text Quality Analysis**: Measures average length and quality of description, implementation/test/backout plans, and close notes\n5. **Category Distribution**: Reports on the spread and diversity of change request categories\n6. **Closure Time Analysis**: Evaluates how quickly change requests are closed\n7. **Quality Scoring**: Combines all metrics into a single overall score\n8. **Log Output**: Prints all results and warnings to the ServiceNow logs for review\n\n## Customization\n- Adjust the `keyFields` array in the config section to match your organization's data requirements\n- Modify thresholds for text length, closure time, and completeness as needed\n- Increase `sampleSize` for more detailed analysis if you have a large dataset"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/Change Table/analyze_change_data_training_quality.js",
    "content": "// ========================================\n// PI Training Data Quality Analyzer\n// ========================================\n// Purpose: Analyze incident data quality for Predictive Intelligence training\n// Use Case: Identify data quality issues before training ML models\n// No Training Required: Analyzes existing data without ML\n// ========================================\n\n// ========================================\n// PI Training Data Quality Analyzer (Simplified)\n// ========================================\n// Purpose: Analyze incident data quality for Predictive Intelligence training\n// Use Case: Identify data quality issues before training ML models\n// No Training Required: Analyzes existing data without ML\n// ========================================\n\n(function analyzeTrainingDataQuality() {\n    // Print all fields that exist on the incident table and its parents (simplified)\n    function printAllFields(tableName) {\n        var gr = new GlideRecord(tableName);\n        var elements = gr.getElements();\n        gs.info('Fields for table: ' + tableName);\n        for (var i = 0; i < elements.size(); i++) {\n            gs.info(elements.get(i).getName());\n        }\n    }\n    // printAllFields('incident');\n\n    // Helper: check if field exists in table hierarchy (simplified)\n    function fieldExists(tableName, fieldName) {\n        var gr = new GlideRecord(tableName);\n        return gr.isValidField(fieldName);\n    }\n\n    // Print table ancestors (if SNC.TableEditor available)\n    if (typeof SNC !== 'undefined' && SNC.TableEditor && SNC.TableEditor.getTableAncestors) {\n        gs.info('Ancestors of incident: ' + SNC.TableEditor.getTableAncestors('incident'));\n    }\n    \n    // ============================================\n    // CONFIGURATION\n    // ============================================\n    var config = {\n        table: 'incident',\n        \n        // Fields to analyze for completeness\n        keyFields: [\n            'short_description',\n            'description',\n            'category',\n            'subcategory',\n            'close_notes',\n            'assignment_group'\n        ],\n        \n        // Quality thresholds\n        thresholds: {\n            minDescriptionLength: 20,      // Characters\n            minCloseNotesLength: 50,       // Characters\n            minResolutionTime: 5,          // Minutes\n            maxAge: 365,                   // Days - only analyze recent data\n            targetCompleteness: 80         // Percent of fields filled\n        },\n        \n        // States to analyze\n        states: {\n            resolved: 6,\n            closed: 7\n        },\n        \n        sampleSize: 500  // Max records to analyze in detail\n    };\n    \n    gs.info('========================================');\n    gs.info('PI Training Data Quality Analysis');\n    gs.info('========================================');\n    gs.info('Table: ' + config.table);\n    gs.info('Sample Size: Up to ' + config.sampleSize + ' records');\n    gs.info('');\n    \n    // ============================================\n    // STEP 1: Overall Data Statistics\n    // ============================================\n    gs.info('=== STEP 1: Overall Statistics ===');\n    gs.info('');\n    \n    var stats = getOverallStats();\n    \n    gs.info('Total Incidents:');\n    gs.info('  All States: ' + stats.total);\n    gs.info('  Resolved/Closed: ' + stats.resolved);\n    gs.info('  Last 90 Days: ' + stats.recent90);\n    gs.info('  Last 365 Days: ' + stats.recent365);\n    gs.info('');\n    \n    if (stats.resolved < 50) {\n        gs.warn('⚠️ Low number of resolved incidents - need at least 50 for training');\n        gs.info('Current: ' + stats.resolved);\n    } else {\n        gs.info('✅ Sufficient resolved incidents for training');\n    }\n    \n    // ============================================\n    // STEP 2: Field Completeness Analysis\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 2: Field Completeness Analysis ===');\n    gs.info('Analyzing resolved/closed incidents from last ' + config.thresholds.maxAge + ' days');\n    gs.info('');\n    \n    var completeness = analyzeFieldCompleteness();\n    \n    gs.info('Field Completeness Scores:');\n    gs.info('');\n    \n    for (var field in completeness) {\n        var pct = completeness[field].percentage;\n        var icon = pct >= 80 ? '✅' : pct >= 50 ? '⚠️' : '❌';\n        \n        gs.info(icon + ' ' + field + ': ' + pct.toFixed(1) + '%');\n        gs.info('   Filled: ' + completeness[field].filled + ' / ' + completeness[field].total);\n        \n        if (pct < 50) {\n            gs.info('   ⚠️ LOW - This field may not be useful for training');\n        }\n        gs.info('');\n    }\n    \n    // ============================================\n    // STEP 3: Text Quality Analysis\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 3: Text Quality Analysis ===');\n    gs.info('Analyzing text field content quality');\n    gs.info('');\n    \n    var textQuality = analyzeTextQuality();\n    \n    gs.info('Description Quality:');\n    gs.info('  Average Length: ' + textQuality.description.avgLength.toFixed(0) + ' characters');\n    gs.info('  Too Short (<20 chars): ' + textQuality.description.tooShort + ' (' + \n            (textQuality.description.tooShortPct).toFixed(1) + '%)');\n    gs.info('  Good Quality: ' + textQuality.description.goodQuality + ' (' + \n            (textQuality.description.goodQualityPct).toFixed(1) + '%)');\n    gs.info('');\n    \n    gs.info('Close Notes Quality:');\n    gs.info('  Average Length: ' + textQuality.closeNotes.avgLength.toFixed(0) + ' characters');\n    gs.info('  Too Short (<50 chars): ' + textQuality.closeNotes.tooShort + ' (' + \n            (textQuality.closeNotes.tooShortPct).toFixed(1) + '%)');\n    gs.info('  Good Quality: ' + textQuality.closeNotes.goodQuality + ' (' + \n            (textQuality.closeNotes.goodQualityPct).toFixed(1) + '%)');\n    gs.info('');\n    \n    if (textQuality.description.goodQualityPct < 70) {\n        gs.warn('⚠️ Many incidents have short/poor descriptions');\n        gs.info('   Consider filtering for better quality data');\n    }\n    \n    if (textQuality.closeNotes.goodQualityPct < 70) {\n        gs.warn('⚠️ Many incidents have short/poor close notes');\n        gs.info('   This will impact solution recommendation quality');\n    }\n    \n    // ============================================\n    // STEP 4: Category Distribution\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 4: Category Distribution ===');\n    gs.info('Analyzing incident category spread');\n    gs.info('');\n    \n    var categoryDist = analyzeCategoryDistribution();\n    \n    gs.info('Top 10 Categories:');\n    for (var i = 0; i < Math.min(10, categoryDist.length); i++) {\n        var cat = categoryDist[i];\n        gs.info('  ' + (i+1) + '. ' + (cat.category || '(empty)') + ': ' + cat.count + ' incidents');\n    }\n    gs.info('');\n    \n    if (categoryDist.length < 5) {\n        gs.warn('⚠️ Low category diversity - model may not generalize well');\n    } else {\n        gs.info('✅ Good category diversity for training');\n    }\n    \n    // ============================================\n    // STEP 5: Resolution Time Analysis\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 5: Resolution Time Analysis ===');\n    gs.info('');\n    \n    var timeAnalysis = analyzeResolutionTimes();\n    \n    gs.info('Resolution Times:');\n    gs.info('  Average: ' + timeAnalysis.avgMinutes.toFixed(0) + ' minutes');\n    gs.info('  Median: ' + timeAnalysis.medianMinutes.toFixed(0) + ' minutes');\n    gs.info('  Too Quick (<5 min): ' + timeAnalysis.tooQuick + ' (' + \n            (timeAnalysis.tooQuickPct).toFixed(1) + '%)');\n    gs.info('');\n    \n    if (timeAnalysis.tooQuickPct > 30) {\n        gs.warn('⚠️ Many incidents resolved very quickly');\n        gs.info('   These may be duplicates or low-quality data');\n        gs.info('   Consider filtering: resolved_at > opened_at + 5 minutes');\n    }\n    \n    // ============================================\n    // STEP 6: Overall Quality Score\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 6: Overall Data Quality Score ===');\n    gs.info('');\n    \n    var overallScore = calculateOverallScore(completeness, textQuality, timeAnalysis);\n    \n    var scoreIcon = overallScore >= 80 ? '✅' : overallScore >= 60 ? '⚠️' : '❌';\n    gs.info(scoreIcon + ' Overall Quality Score: ' + overallScore.toFixed(0) + '/100');\n    gs.info('');\n    \n    if (overallScore >= 80) {\n        gs.info('✅ EXCELLENT - Data is ready for high-quality training');\n    } else if (overallScore >= 60) {\n        gs.info('⚠️ FAIR - Data can be used but consider improvements');\n    } else {\n        gs.info('❌ POOR - Significant data quality issues exist');\n    }\n    \n    // ============================================\n    // STEP 7: Recommendations\n    // ============================================\n    gs.info('');\n    gs.info('========================================');\n    gs.info('Analysis Complete');\n    gs.info('========================================');\n    \n    // ============================================\n    // HELPER FUNCTIONS\n    // ============================================\n    \n    function getOverallStats() {\n        var result = {\n            total: 0,\n            resolved: 0,\n            recent90: 0,\n            recent365: 0\n        };\n        \n        // Total incidents\n        var totalGr = new GlideAggregate(config.table);\n        totalGr.addAggregate('COUNT');\n        totalGr.query();\n        if (totalGr.next()) {\n            result.total = parseInt(totalGr.getAggregate('COUNT'));\n        }\n        \n        // Resolved/closed\n        var resolvedGr = new GlideAggregate(config.table);\n        resolvedGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        resolvedGr.addAggregate('COUNT');\n        resolvedGr.query();\n        if (resolvedGr.next()) {\n            result.resolved = parseInt(resolvedGr.getAggregate('COUNT'));\n        }\n        \n        // Recent 90 days\n        var recent90Gr = new GlideAggregate(config.table);\n        recent90Gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        recent90Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(90)');\n        recent90Gr.addAggregate('COUNT');\n        recent90Gr.query();\n        if (recent90Gr.next()) {\n            result.recent90 = parseInt(recent90Gr.getAggregate('COUNT'));\n        }\n        \n        // Recent 365 days\n        var recent365Gr = new GlideAggregate(config.table);\n        recent365Gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        recent365Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(365)');\n        recent365Gr.addAggregate('COUNT');\n        recent365Gr.query();\n        if (recent365Gr.next()) {\n            result.recent365 = parseInt(recent365Gr.getAggregate('COUNT'));\n        }\n        \n        return result;\n    }\n    \n    function analyzeFieldCompleteness() {\n        var results = {};\n        \n        // Get total count first\n        var totalGr = new GlideAggregate(config.table);\n        totalGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        totalGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        totalGr.addAggregate('COUNT');\n        totalGr.query();\n        \n        var total = 0;\n        if (totalGr.next()) {\n            total = parseInt(totalGr.getAggregate('COUNT'));\n        }\n        \n        // ...existing code...\n\n        // Check each field, skip if not present\n        for (var f = 0; f < config.keyFields.length; f++) {\n            var fieldName = config.keyFields[f];\n            if (!fieldExists(config.table, fieldName)) {\n                gs.warn('Field does not exist: ' + fieldName + ' - skipping completeness analysis for this field');\n                continue;\n            }\n            var filledGr = new GlideAggregate(config.table);\n            filledGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n            filledGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n            filledGr.addQuery(fieldName, '!=', '');\n            filledGr.addNotNullQuery(fieldName);\n            filledGr.addAggregate('COUNT');\n            filledGr.query();\n            var filled = 0;\n            if (filledGr.next()) {\n                filled = parseInt(filledGr.getAggregate('COUNT'));\n            }\n            results[fieldName] = {\n                total: total,\n                filled: filled,\n                percentage: total > 0 ? (filled / total * 100) : 0\n            };\n        }\n        return results;\n    }\n    \n    function analyzeTextQuality() {\n        var gr = new GlideRecord(config.table);\n        gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        gr.setLimit(config.sampleSize);\n        gr.query();\n        \n        var descStats = {\n            totalLength: 0,\n            count: 0,\n            tooShort: 0,\n            goodQuality: 0\n        };\n        \n        var closeNotesStats = {\n            totalLength: 0,\n            count: 0,\n            tooShort: 0,\n            goodQuality: 0\n        };\n        \n        while (gr.next()) {\n            // Analyze description\n            var desc = gr.getValue('description') || '';\n            if (desc) {\n                descStats.count++;\n                descStats.totalLength += desc.length;\n                \n                if (desc.length < config.thresholds.minDescriptionLength) {\n                    descStats.tooShort++;\n                } else {\n                    descStats.goodQuality++;\n                }\n            }\n            \n            // Analyze close notes\n            var closeNotes = gr.getValue('close_notes') || '';\n            if (closeNotes) {\n                closeNotesStats.count++;\n                closeNotesStats.totalLength += closeNotes.length;\n                \n                if (closeNotes.length < config.thresholds.minCloseNotesLength) {\n                    closeNotesStats.tooShort++;\n                } else {\n                    closeNotesStats.goodQuality++;\n                }\n            }\n        }\n        \n        return {\n            description: {\n                avgLength: descStats.count > 0 ? descStats.totalLength / descStats.count : 0,\n                tooShort: descStats.tooShort,\n                tooShortPct: descStats.count > 0 ? (descStats.tooShort / descStats.count * 100) : 0,\n                goodQuality: descStats.goodQuality,\n                goodQualityPct: descStats.count > 0 ? (descStats.goodQuality / descStats.count * 100) : 0\n            },\n            closeNotes: {\n                avgLength: closeNotesStats.count > 0 ? closeNotesStats.totalLength / closeNotesStats.count : 0,\n                tooShort: closeNotesStats.tooShort,\n                tooShortPct: closeNotesStats.count > 0 ? (closeNotesStats.tooShort / closeNotesStats.count * 100) : 0,\n                goodQuality: closeNotesStats.goodQuality,\n                goodQualityPct: closeNotesStats.count > 0 ? (closeNotesStats.goodQuality / closeNotesStats.count * 100) : 0\n            }\n        };\n    }\n    \n    function analyzeCategoryDistribution() {\n        var catGr = new GlideAggregate(config.table);\n        catGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        catGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        catGr.groupBy('category');\n        catGr.addAggregate('COUNT');\n        catGr.orderByAggregate('COUNT');\n        catGr.query();\n        \n        var categories = [];\n        while (catGr.next()) {\n            categories.push({\n                category: catGr.getValue('category'),\n                count: parseInt(catGr.getAggregate('COUNT'))\n            });\n        }\n        \n        // Sort descending\n        categories.sort(function(a, b) { return b.count - a.count; });\n        \n        return categories;\n    }\n    \n    function analyzeResolutionTimes() {\n        var gr = new GlideRecord(config.table);\n        gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        gr.addNotNullQuery('opened_at');\n        gr.addNotNullQuery('resolved_at');\n        gr.setLimit(config.sampleSize);\n        gr.query();\n        \n        var times = [];\n        var tooQuick = 0;\n        \n        while (gr.next()) {\n            var opened = new GlideDateTime(gr.getValue('opened_at'));\n            var resolved = new GlideDateTime(gr.getValue('resolved_at'));\n            \n            var diff = GlideDateTime.subtract(opened, resolved);\n            var minutes = diff.getNumericValue() / 1000 / 60;\n            \n            if (minutes > 0) {\n                times.push(minutes);\n                \n                if (minutes < config.thresholds.minResolutionTime) {\n                    tooQuick++;\n                }\n            }\n        }\n        \n        times.sort(function(a, b) { return a - b; });\n        \n        var avgMinutes = 0;\n        if (times.length > 0) {\n            var sum = 0;\n            for (var t = 0; t < times.length; t++) {\n                sum += times[t];\n            }\n            avgMinutes = sum / times.length;\n        }\n        \n        var medianMinutes = 0;\n        if (times.length > 0) {\n            var midIdx = Math.floor(times.length / 2);\n            medianMinutes = times[midIdx];\n        }\n        \n        return {\n            avgMinutes: avgMinutes,\n            medianMinutes: medianMinutes,\n            tooQuick: tooQuick,\n            tooQuickPct: times.length > 0 ? (tooQuick / times.length * 100) : 0,\n            sampleSize: times.length\n        };\n    }\n    \n    function calculateOverallScore(completeness, textQuality, timeAnalysis) {\n        var score = 0;\n        var weights = {\n            completeness: 40,\n            textQuality: 40,\n            timeQuality: 20\n        };\n        \n        // Completeness score (average of all fields)\n        var compTotal = 0;\n        var compCount = 0;\n        for (var field in completeness) {\n            compTotal += completeness[field].percentage;\n            compCount++;\n        }\n        var compScore = compCount > 0 ? (compTotal / compCount) : 0;\n        score += (compScore / 100) * weights.completeness;\n        \n        // Text quality score (average of description and close notes)\n        var textScore = (textQuality.description.goodQualityPct + textQuality.closeNotes.goodQualityPct) / 2;\n        score += (textScore / 100) * weights.textQuality;\n        \n        // Time quality score (inverse of too-quick percentage)\n        var timeScore = 100 - timeAnalysis.tooQuickPct;\n        score += (timeScore / 100) * weights.timeQuality;\n        \n        return score;\n    }\n    \n    \n    \n})();"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Table/README.md",
    "content": "# Training Data Quality Analyzer for ServiceNow Predictive Intelligence\n\n## Overview\nThis script analyzes the quality of incident data in ServiceNow to determine readiness for Predictive Intelligence (PI) model training. It provides detailed statistics and quality metrics to help ServiceNow developers and admins identify and address data issues before starting ML training jobs.\n\n## Purpose\n- Assess completeness and quality of key fields in incident records\n- Identify common data issues that could impact PI model performance\n- Provide actionable insights for improving training data\n\n## Features\n- Checks completeness of important fields (e.g., short_description, description, category, subcategory, close_notes, assignment_group)\n- Analyzes text quality for description and close notes\n- Evaluates category diversity and resolution times\n- Calculates an overall data quality score\n- Outputs results to the ServiceNow system logs\n\n## Setup Requirements\n1. **ServiceNow Instance** with Predictive Intelligence plugin enabled\n2. **Script Execution Permissions**: Run as a background script or Script Include with access to the `incident` table\n3. **No external dependencies**: Uses only standard ServiceNow APIs (GlideRecord, GlideAggregate, GlideDateTime)\n4. **Sufficient Data Volume**: At least 50 resolved/closed incidents recommended for meaningful analysis\n\n## How It Works\n1. **Field Existence Check**: Dynamically verifies that each key field exists on the incident table or its parent tables\n2. **Statistics Gathering**: Collects counts for total, resolved, and recent incidents\n3. **Completeness Analysis**: Calculates the percentage of records with each key field filled\n4. **Text Quality Analysis**: Measures average length and quality of description and close notes\n5. **Category Distribution**: Reports on the spread and diversity of incident categories\n6. **Resolution Time Analysis**: Evaluates how quickly incidents are resolved\n7. **Quality Scoring**: Combines all metrics into a single overall score\n8. **Log Output**: Prints all results and warnings to the ServiceNow logs for review\n\n## Customization\n- Adjust the `keyFields` array in the config section to match your organization's data requirements\n- Modify thresholds for text length, resolution time, and completeness as needed\n- Increase `sampleSize` for more detailed analysis if you have a large dataset\n\n## Security & Best Practices\n- Do not run in production without review\n- Ensure no sensitive data is exposed in logs\n- Validate script results in a sub-production environment before using for model training\n"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/Incident Table/analyze_incident_data_training_quality.js",
    "content": "// ========================================\n// PI Training Data Quality Analyzer\n// ========================================\n// Purpose: Analyze incident data quality for Predictive Intelligence training\n// Use Case: Identify data quality issues before training ML models\n// No Training Required: Analyzes existing data without ML\n// ========================================\n\n// ========================================\n// PI Training Data Quality Analyzer (Simplified)\n// ========================================\n// Purpose: Analyze incident data quality for Predictive Intelligence training\n// Use Case: Identify data quality issues before training ML models\n// No Training Required: Analyzes existing data without ML\n// ========================================\n\n(function analyzeTrainingDataQuality() {\n    // Print all fields that exist on the incident table and its parents (simplified)\n    function printAllFields(tableName) {\n        var gr = new GlideRecord(tableName);\n        var elements = gr.getElements();\n        gs.info('Fields for table: ' + tableName);\n        for (var i = 0; i < elements.size(); i++) {\n            gs.info(elements.get(i).getName());\n        }\n    }\n\n    printAllFields('incident');\n\n    // Helper: check if field exists in table hierarchy (simplified)\n    function fieldExists(tableName, fieldName) {\n        var gr = new GlideRecord(tableName);\n        return gr.isValidField(fieldName);\n    }\n\n    // Print table ancestors (if SNC.TableEditor available)\n    if (typeof SNC !== 'undefined' && SNC.TableEditor && SNC.TableEditor.getTableAncestors) {\n        gs.info('Ancestors of incident: ' + SNC.TableEditor.getTableAncestors('incident'));\n    }\n    \n    // ============================================\n    // CONFIGURATION\n    // ============================================\n    var config = {\n        table: 'incident',\n        \n        // Fields to analyze for completeness\n        keyFields: [\n            'short_description',\n            'description',\n            'category',\n            'subcategory',\n            'close_notes',\n            'assignment_group'\n        ],\n        \n        // Quality thresholds\n        thresholds: {\n            minDescriptionLength: 20,      // Characters\n            minCloseNotesLength: 50,       // Characters\n            minResolutionTime: 5,          // Minutes\n            maxAge: 365,                   // Days - only analyze recent data\n            targetCompleteness: 80         // Percent of fields filled\n        },\n        \n        // States to analyze\n        states: {\n            resolved: 6,\n            closed: 7\n        },\n        \n        sampleSize: 500  // Max records to analyze in detail\n    };\n    \n    gs.info('========================================');\n    gs.info('PI Training Data Quality Analysis');\n    gs.info('========================================');\n    gs.info('Table: ' + config.table);\n    gs.info('Sample Size: Up to ' + config.sampleSize + ' records');\n    gs.info('');\n    \n    // ============================================\n    // STEP 1: Overall Data Statistics\n    // ============================================\n    gs.info('=== STEP 1: Overall Statistics ===');\n    gs.info('');\n    \n    var stats = getOverallStats();\n    \n    gs.info('Total Incidents:');\n    gs.info('  All States: ' + stats.total);\n    gs.info('  Resolved/Closed: ' + stats.resolved);\n    gs.info('  Last 90 Days: ' + stats.recent90);\n    gs.info('  Last 365 Days: ' + stats.recent365);\n    gs.info('');\n    \n    if (stats.resolved < 50) {\n        gs.warn('⚠️ Low number of resolved incidents - need at least 50 for training');\n        gs.info('Current: ' + stats.resolved);\n    } else {\n        gs.info('✅ Sufficient resolved incidents for training');\n    }\n    \n    // ============================================\n    // STEP 2: Field Completeness Analysis\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 2: Field Completeness Analysis ===');\n    gs.info('Analyzing resolved/closed incidents from last ' + config.thresholds.maxAge + ' days');\n    gs.info('');\n    \n    var completeness = analyzeFieldCompleteness();\n    \n    gs.info('Field Completeness Scores:');\n    gs.info('');\n    \n    for (var field in completeness) {\n        var pct = completeness[field].percentage;\n        var icon = pct >= 80 ? '✅' : pct >= 50 ? '⚠️' : '❌';\n        \n        gs.info(icon + ' ' + field + ': ' + pct.toFixed(1) + '%');\n        gs.info('   Filled: ' + completeness[field].filled + ' / ' + completeness[field].total);\n        \n        if (pct < 50) {\n            gs.info('   ⚠️ LOW - This field may not be useful for training');\n        }\n        gs.info('');\n    }\n    \n    // ============================================\n    // STEP 3: Text Quality Analysis\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 3: Text Quality Analysis ===');\n    gs.info('Analyzing text field content quality');\n    gs.info('');\n    \n    var textQuality = analyzeTextQuality();\n    \n    gs.info('Description Quality:');\n    gs.info('  Average Length: ' + textQuality.description.avgLength.toFixed(0) + ' characters');\n    gs.info('  Too Short (<20 chars): ' + textQuality.description.tooShort + ' (' + \n            (textQuality.description.tooShortPct).toFixed(1) + '%)');\n    gs.info('  Good Quality: ' + textQuality.description.goodQuality + ' (' + \n            (textQuality.description.goodQualityPct).toFixed(1) + '%)');\n    gs.info('');\n    \n    gs.info('Close Notes Quality:');\n    gs.info('  Average Length: ' + textQuality.closeNotes.avgLength.toFixed(0) + ' characters');\n    gs.info('  Too Short (<50 chars): ' + textQuality.closeNotes.tooShort + ' (' + \n            (textQuality.closeNotes.tooShortPct).toFixed(1) + '%)');\n    gs.info('  Good Quality: ' + textQuality.closeNotes.goodQuality + ' (' + \n            (textQuality.closeNotes.goodQualityPct).toFixed(1) + '%)');\n    gs.info('');\n    \n    if (textQuality.description.goodQualityPct < 70) {\n        gs.warn('⚠️ Many incidents have short/poor descriptions');\n        gs.info('   Consider filtering for better quality data');\n    }\n    \n    if (textQuality.closeNotes.goodQualityPct < 70) {\n        gs.warn('⚠️ Many incidents have short/poor close notes');\n        gs.info('   This will impact solution recommendation quality');\n    }\n    \n    // ============================================\n    // STEP 4: Category Distribution\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 4: Category Distribution ===');\n    gs.info('Analyzing incident category spread');\n    gs.info('');\n    \n    var categoryDist = analyzeCategoryDistribution();\n    \n    gs.info('Top 10 Categories:');\n    for (var i = 0; i < Math.min(10, categoryDist.length); i++) {\n        var cat = categoryDist[i];\n        gs.info('  ' + (i+1) + '. ' + (cat.category || '(empty)') + ': ' + cat.count + ' incidents');\n    }\n    gs.info('');\n    \n    if (categoryDist.length < 5) {\n        gs.warn('⚠️ Low category diversity - model may not generalize well');\n    } else {\n        gs.info('✅ Good category diversity for training');\n    }\n    \n    // ============================================\n    // STEP 5: Resolution Time Analysis\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 5: Resolution Time Analysis ===');\n    gs.info('');\n    \n    var timeAnalysis = analyzeResolutionTimes();\n    \n    gs.info('Resolution Times:');\n    gs.info('  Average: ' + timeAnalysis.avgMinutes.toFixed(0) + ' minutes');\n    gs.info('  Median: ' + timeAnalysis.medianMinutes.toFixed(0) + ' minutes');\n    gs.info('  Too Quick (<5 min): ' + timeAnalysis.tooQuick + ' (' + \n            (timeAnalysis.tooQuickPct).toFixed(1) + '%)');\n    gs.info('');\n    \n    if (timeAnalysis.tooQuickPct > 30) {\n        gs.warn('⚠️ Many incidents resolved very quickly');\n        gs.info('   These may be duplicates or low-quality data');\n        gs.info('   Consider filtering: resolved_at > opened_at + 5 minutes');\n    }\n    \n    // ============================================\n    // STEP 6: Overall Quality Score\n    // ============================================\n    gs.info('');\n    gs.info('=== STEP 6: Overall Data Quality Score ===');\n    gs.info('');\n    \n    var overallScore = calculateOverallScore(completeness, textQuality, timeAnalysis);\n    \n    var scoreIcon = overallScore >= 80 ? '✅' : overallScore >= 60 ? '⚠️' : '❌';\n    gs.info(scoreIcon + ' Overall Quality Score: ' + overallScore.toFixed(0) + '/100');\n    gs.info('');\n    \n    if (overallScore >= 80) {\n        gs.info('✅ EXCELLENT - Data is ready for high-quality training');\n    } else if (overallScore >= 60) {\n        gs.info('⚠️ FAIR - Data can be used but consider improvements');\n    } else {\n        gs.info('❌ POOR - Significant data quality issues exist');\n    }\n    \n    // ============================================\n    // STEP 7: Recommendations\n    // ============================================\n    gs.info('');\n    gs.info('========================================');\n    gs.info('Analysis Complete');\n    gs.info('========================================');\n    \n    // ============================================\n    // HELPER FUNCTIONS\n    // ============================================\n    \n    function getOverallStats() {\n        var result = {\n            total: 0,\n            resolved: 0,\n            recent90: 0,\n            recent365: 0\n        };\n        \n        // Total incidents\n        var totalGr = new GlideAggregate(config.table);\n        totalGr.addAggregate('COUNT');\n        totalGr.query();\n        if (totalGr.next()) {\n            result.total = parseInt(totalGr.getAggregate('COUNT'));\n        }\n        \n        // Resolved/closed\n        var resolvedGr = new GlideAggregate(config.table);\n        resolvedGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        resolvedGr.addAggregate('COUNT');\n        resolvedGr.query();\n        if (resolvedGr.next()) {\n            result.resolved = parseInt(resolvedGr.getAggregate('COUNT'));\n        }\n        \n        // Recent 90 days\n        var recent90Gr = new GlideAggregate(config.table);\n        recent90Gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        recent90Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(90)');\n        recent90Gr.addAggregate('COUNT');\n        recent90Gr.query();\n        if (recent90Gr.next()) {\n            result.recent90 = parseInt(recent90Gr.getAggregate('COUNT'));\n        }\n        \n        // Recent 365 days\n        var recent365Gr = new GlideAggregate(config.table);\n        recent365Gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        recent365Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(365)');\n        recent365Gr.addAggregate('COUNT');\n        recent365Gr.query();\n        if (recent365Gr.next()) {\n            result.recent365 = parseInt(recent365Gr.getAggregate('COUNT'));\n        }\n        \n        return result;\n    }\n    \n    function analyzeFieldCompleteness() {\n        var results = {};\n        \n        // Get total count first\n        var totalGr = new GlideAggregate(config.table);\n        totalGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        totalGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        totalGr.addAggregate('COUNT');\n        totalGr.query();\n        \n        var total = 0;\n        if (totalGr.next()) {\n            total = parseInt(totalGr.getAggregate('COUNT'));\n        }\n        \n        // ...existing code...\n\n        // Check each field, skip if not present\n        for (var f = 0; f < config.keyFields.length; f++) {\n            var fieldName = config.keyFields[f];\n            if (!fieldExists(config.table, fieldName)) {\n                gs.warn('Field does not exist: ' + fieldName + ' - skipping completeness analysis for this field');\n                continue;\n            }\n            var filledGr = new GlideAggregate(config.table);\n            filledGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n            filledGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n            filledGr.addQuery(fieldName, '!=', '');\n            filledGr.addNotNullQuery(fieldName);\n            filledGr.addAggregate('COUNT');\n            filledGr.query();\n            var filled = 0;\n            if (filledGr.next()) {\n                filled = parseInt(filledGr.getAggregate('COUNT'));\n            }\n            results[fieldName] = {\n                total: total,\n                filled: filled,\n                percentage: total > 0 ? (filled / total * 100) : 0\n            };\n        }\n        return results;\n    }\n    \n    function analyzeTextQuality() {\n        var gr = new GlideRecord(config.table);\n        gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        gr.setLimit(config.sampleSize);\n        gr.query();\n        \n        var descStats = {\n            totalLength: 0,\n            count: 0,\n            tooShort: 0,\n            goodQuality: 0\n        };\n        \n        var closeNotesStats = {\n            totalLength: 0,\n            count: 0,\n            tooShort: 0,\n            goodQuality: 0\n        };\n        \n        while (gr.next()) {\n            // Analyze description\n            var desc = gr.getValue('description') || '';\n            if (desc) {\n                descStats.count++;\n                descStats.totalLength += desc.length;\n                \n                if (desc.length < config.thresholds.minDescriptionLength) {\n                    descStats.tooShort++;\n                } else {\n                    descStats.goodQuality++;\n                }\n            }\n            \n            // Analyze close notes\n            var closeNotes = gr.getValue('close_notes') || '';\n            if (closeNotes) {\n                closeNotesStats.count++;\n                closeNotesStats.totalLength += closeNotes.length;\n                \n                if (closeNotes.length < config.thresholds.minCloseNotesLength) {\n                    closeNotesStats.tooShort++;\n                } else {\n                    closeNotesStats.goodQuality++;\n                }\n            }\n        }\n        \n        return {\n            description: {\n                avgLength: descStats.count > 0 ? descStats.totalLength / descStats.count : 0,\n                tooShort: descStats.tooShort,\n                tooShortPct: descStats.count > 0 ? (descStats.tooShort / descStats.count * 100) : 0,\n                goodQuality: descStats.goodQuality,\n                goodQualityPct: descStats.count > 0 ? (descStats.goodQuality / descStats.count * 100) : 0\n            },\n            closeNotes: {\n                avgLength: closeNotesStats.count > 0 ? closeNotesStats.totalLength / closeNotesStats.count : 0,\n                tooShort: closeNotesStats.tooShort,\n                tooShortPct: closeNotesStats.count > 0 ? (closeNotesStats.tooShort / closeNotesStats.count * 100) : 0,\n                goodQuality: closeNotesStats.goodQuality,\n                goodQualityPct: closeNotesStats.count > 0 ? (closeNotesStats.goodQuality / closeNotesStats.count * 100) : 0\n            }\n        };\n    }\n    \n    function analyzeCategoryDistribution() {\n        var catGr = new GlideAggregate(config.table);\n        catGr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        catGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        catGr.groupBy('category');\n        catGr.addAggregate('COUNT');\n        catGr.orderByAggregate('COUNT');\n        catGr.query();\n        \n        var categories = [];\n        while (catGr.next()) {\n            categories.push({\n                category: catGr.getValue('category'),\n                count: parseInt(catGr.getAggregate('COUNT'))\n            });\n        }\n        \n        // Sort descending\n        categories.sort(function(a, b) { return b.count - a.count; });\n        \n        return categories;\n    }\n    \n    function analyzeResolutionTimes() {\n        var gr = new GlideRecord(config.table);\n        gr.addQuery('state', 'IN', [config.states.resolved, config.states.closed].join(','));\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\n        gr.addNotNullQuery('opened_at');\n        gr.addNotNullQuery('resolved_at');\n        gr.setLimit(config.sampleSize);\n        gr.query();\n        \n        var times = [];\n        var tooQuick = 0;\n        \n        while (gr.next()) {\n            var opened = new GlideDateTime(gr.getValue('opened_at'));\n            var resolved = new GlideDateTime(gr.getValue('resolved_at'));\n            \n            var diff = GlideDateTime.subtract(opened, resolved);\n            var minutes = diff.getNumericValue() / 1000 / 60;\n            \n            if (minutes > 0) {\n                times.push(minutes);\n                \n                if (minutes < config.thresholds.minResolutionTime) {\n                    tooQuick++;\n                }\n            }\n        }\n        \n        times.sort(function(a, b) { return a - b; });\n        \n        var avgMinutes = 0;\n        if (times.length > 0) {\n            var sum = 0;\n            for (var t = 0; t < times.length; t++) {\n                sum += times[t];\n            }\n            avgMinutes = sum / times.length;\n        }\n        \n        var medianMinutes = 0;\n        if (times.length > 0) {\n            var midIdx = Math.floor(times.length / 2);\n            medianMinutes = times[midIdx];\n        }\n        \n        return {\n            avgMinutes: avgMinutes,\n            medianMinutes: medianMinutes,\n            tooQuick: tooQuick,\n            tooQuickPct: times.length > 0 ? (tooQuick / times.length * 100) : 0,\n            sampleSize: times.length\n        };\n    }\n    \n    function calculateOverallScore(completeness, textQuality, timeAnalysis) {\n        var score = 0;\n        var weights = {\n            completeness: 40,\n            textQuality: 40,\n            timeQuality: 20\n        };\n        \n        // Completeness score (average of all fields)\n        var compTotal = 0;\n        var compCount = 0;\n        for (var field in completeness) {\n            compTotal += completeness[field].percentage;\n            compCount++;\n        }\n        var compScore = compCount > 0 ? (compTotal / compCount) : 0;\n        score += (compScore / 100) * weights.completeness;\n        \n        // Text quality score (average of description and close notes)\n        var textScore = (textQuality.description.goodQualityPct + textQuality.closeNotes.goodQualityPct) / 2;\n        score += (textScore / 100) * weights.textQuality;\n        \n        // Time quality score (inverse of too-quick percentage)\n        var timeScore = 100 - timeAnalysis.tooQuickPct;\n        score += (timeScore / 100) * weights.timeQuality;\n        \n        return score;\n    }\n    \n    function generateRecommendations(stats, completeness, textQuality, timeAnalysis) {\n        var recs = [];\n        \n        // Check volume\n        if (stats.resolved < 100) {\n            recs.push('Increase training data volume - aim for 100+ resolved incidents');\n        }\n        \n        // Check field completeness\n        for (var field in completeness) {\n            if (completeness[field].percentage < 50) {\n                recs.push('Improve ' + field + ' completeness (currently ' + \n                         completeness[field].percentage.toFixed(0) + '%)');\n            }\n        }\n        \n        // Check text quality\n        if (textQuality.description.goodQualityPct < 70) {\n            recs.push('Encourage more detailed incident descriptions (20+ characters)');\n        }\n        \n        if (textQuality.closeNotes.goodQualityPct < 70) {\n            recs.push('Improve close notes quality - require detailed resolution steps (50+ characters)');\n        }\n        \n        // Check resolution times\n        if (timeAnalysis.tooQuickPct > 30) {\n            recs.push('Filter out quick resolutions (<5 min) - may be duplicates or invalid data');\n            recs.push('Add filter: resolved_at > opened_at + 5 minutes');\n        }\n        \n        // Check category diversity\n        if (stats.resolved > 0 && stats.resolved < 50) {\n            recs.push('Collect more diverse incident data across different categories');\n        }\n        \n        return recs;\n    }\n    \n})();\n"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/Problem Table/README.md",
    "content": "# Training Data Quality Analyzer for ServiceNow Predictive Intelligence (Problem)\r\n\r\n## Overview\r\nThis script analyzes the quality of problem data in ServiceNow to determine readiness for Predictive Intelligence (PI) model training. It provides detailed statistics and quality metrics to help ServiceNow developers and admins identify and address data issues before starting ML training jobs.\r\n\r\n## Purpose\r\n- Assess completeness and quality of key fields in problem records\r\n- Identify common data issues that could impact PI model performance\r\n- Provide actionable insights for improving training data\r\n\r\n## Features\r\n- Checks completeness of important fields (e.g., short_description, description, category, assignment_group, close_notes, state)\r\n- Lists all fields and ancestor tables for the problem table\r\n- Outputs results to the ServiceNow system logs\r\n\r\n## Setup Requirements\r\n1. **ServiceNow Instance** with Predictive Intelligence plugin enabled\r\n2. **Script Execution Permissions**: Run as a background script or Script Include with access to the `problem` table\r\n3. **No external dependencies**: Uses only standard ServiceNow APIs (GlideRecord, GlideAggregate)\r\n4. **Sufficient Data Volume**: At least 50 closed problems recommended for meaningful analysis\r\n\r\n## How It Works\r\n1. **Field Existence Check**: Dynamically verifies that each key field exists on the problem table or its parent tables\r\n2. **Statistics Gathering**: Collects counts for total and filled key fields\r\n3. **Field Listing**: Lists all fields (including inherited fields) and ancestor tables\r\n4. **Log Output**: Prints all results and warnings to the ServiceNow logs for review\r\n\r\n## Customization\r\n- Adjust the `keyFields` array in the script to match your organization's data requirements\r\n- Add or remove fields and statistics as needed\r\n\r\n## Security & Best Practices\r\n- Do not run in production without review\r\n- Ensure no sensitive data is exposed in logs\r\n- Validate script results in a sub-production environment before using for model training\r\n"
  },
  {
    "path": "Specialized Areas/Predictive Intelligence/Training Data Preparer/Problem Table/analyze_problem_data_training_quality.js",
    "content": "(function analyzeProblemDataQuality() {\r\n    var config = {\r\n        table: 'problem',\r\n        keyFields: [\r\n            'short_description',\r\n            'description', \r\n            'category',\r\n            'assignment_group',\r\n            'close_notes',\r\n            'state'\r\n        ],\r\n        thresholds: {\r\n            minDescriptionLength: 20,\r\n            minCloseNotesLength: 50,\r\n            minResolutionTime: 5,\r\n            maxAge: 365,\r\n            targetCompleteness: 80\r\n        },\r\n        states: {\r\n            // Try multiple possible closed state values\r\n            closedStates: ['3', '4', '9', '10'] // Common closed/resolved states\r\n        },\r\n        sampleSize: 500\r\n    };\r\n\r\n    gs.info('========================================');\r\n    gs.info('PI Training Data Quality Analysis (Problem)');\r\n    gs.info('========================================');\r\n\r\n    // identify what closed states we have\r\n    var actualClosedStates = identifyClosedStates();\r\n    if (actualClosedStates.length === 0) {\r\n        gs.warn('⚠️ No closed states identified. Using all records for analysis.');\r\n        config.useAllRecords = true;\r\n    } else {\r\n        gs.info('Using closed states: ' + actualClosedStates.join(', '));\r\n        config.states.closedStates = actualClosedStates;\r\n    }\r\n\r\n    // Get overall statistics\r\n    var stats = getOverallStats(config);\r\n    gs.info('');\r\n    gs.info('=== STEP 1: Overall Statistics ===');\r\n    gs.info('Total Problems: ' + stats.total);\r\n    gs.info('Closed Problems: ' + stats.closed);\r\n    gs.info('Recent 90 Days: ' + stats.recent90);\r\n    gs.info('Recent 365 Days: ' + stats.recent365);\r\n    gs.info('');\r\n\r\n    if (stats.closed < 50) {\r\n        gs.warn('⚠️ Low number of closed problems - need at least 50 for training');\r\n        gs.info('Current: ' + stats.closed);\r\n    } else {\r\n        gs.info('✅ Sufficient closed problems for training');\r\n    }\r\n\r\n    // Field completeness analysis\r\n    gs.info('');\r\n    gs.info('=== STEP 2: Field Completeness Analysis ===');\r\n    var completeness = analyzeFieldCompleteness(config);\r\n    gs.info('Field Completeness Scores:');\r\n    for (var field in completeness) {\r\n        var pct = completeness[field].percentage;\r\n        var icon = pct >= 80 ? '✅' : pct >= 50 ? '⚠️' : '❌';\r\n        gs.info(icon + ' ' + field + ': ' + pct.toFixed(1) + '% (' + \r\n                completeness[field].filled + '/' + completeness[field].total + ')');\r\n    }\r\n\r\n    // Text quality analysis\r\n    gs.info('');\r\n    gs.info('=== STEP 3: Text Quality Analysis ===');\r\n    var textQuality = analyzeTextQuality(config);\r\n    gs.info('Description Quality: Avg ' + textQuality.description.avgLength.toFixed(0) + \r\n            ' chars, ' + textQuality.description.goodQualityPct.toFixed(1) + '% good quality');\r\n    gs.info('Close Notes Quality: Avg ' + textQuality.closeNotes.avgLength.toFixed(0) + \r\n            ' chars, ' + textQuality.closeNotes.goodQualityPct.toFixed(1) + '% good quality');\r\n\r\n    // Category distribution\r\n    gs.info('');\r\n    gs.info('=== STEP 4: Category Distribution ===');\r\n    var categoryDist = analyzeCategoryDistribution(config);\r\n    gs.info('Categories found: ' + categoryDist.length);\r\n    for (var i = 0; i < Math.min(5, categoryDist.length); i++) {\r\n        var cat = categoryDist[i];\r\n        gs.info('  ' + (cat.category || '(empty)') + ': ' + cat.count);\r\n    }\r\n\r\n    // Overall score\r\n    var overallScore = calculateOverallScore(completeness, textQuality, {tooQuickPct: 15});\r\n    gs.info('');\r\n    gs.info('=== OVERALL QUALITY SCORE ===');\r\n    gs.info('Score: ' + overallScore.toFixed(0) + '/100');\r\n\r\n    gs.info('');\r\n    gs.info('========================================');\r\n    gs.info('Analysis Complete');\r\n    gs.info('========================================');\r\n\r\n    // Helper functions\r\n    function identifyClosedStates() {\r\n        var stateGr = new GlideAggregate('problem');\r\n        stateGr.groupBy('state');\r\n        stateGr.addAggregate('COUNT');\r\n        stateGr.query();\r\n        \r\n        var states = [];\r\n        var stateInfo = [];\r\n        \r\n        while (stateGr.next()) {\r\n            var state = stateGr.getValue('state');\r\n            var count = stateGr.getAggregate('COUNT');\r\n            stateInfo.push({state: state, count: parseInt(count)});\r\n        }\r\n        \r\n        // Look for states that might be \"closed\" - typically higher numbers with reasonable counts\r\n        for (var i = 0; i < stateInfo.length; i++) {\r\n            var info = stateInfo[i];\r\n            // Include states that are likely closed (3, 4, 9, 10) or have significant counts\r\n            if (['3', '4', '9', '10'].indexOf(info.state) >= 0 || info.count > 10) {\r\n                states.push(info.state);\r\n                gs.info('Including state ' + info.state + ' (' + info.count + ' records)');\r\n            }\r\n        }\r\n        \r\n        return states;\r\n    }\r\n\r\n    function getOverallStats(config) {\r\n        var result = {total: 0, closed: 0, recent90: 0, recent365: 0};\r\n        \r\n        // Total\r\n        var totalGr = new GlideAggregate('problem');\r\n        totalGr.addAggregate('COUNT');\r\n        totalGr.query();\r\n        if (totalGr.next()) {\r\n            result.total = parseInt(totalGr.getAggregate('COUNT'));\r\n        }\r\n        \r\n        // Closed (use identified states or all if none found)\r\n        if (!config.useAllRecords && config.states.closedStates.length > 0) {\r\n            var closedGr = new GlideAggregate('problem');\r\n            closedGr.addQuery('state', 'IN', config.states.closedStates.join(','));\r\n            closedGr.addAggregate('COUNT');\r\n            closedGr.query();\r\n            if (closedGr.next()) {\r\n                result.closed = parseInt(closedGr.getAggregate('COUNT'));\r\n            }\r\n        } else {\r\n            result.closed = result.total; // Use all records if no closed states identified\r\n        }\r\n        \r\n        // Recent counts - use broader criteria\r\n        var recent365Gr = new GlideAggregate('problem');\r\n        recent365Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(365)');\r\n        recent365Gr.addAggregate('COUNT');\r\n        recent365Gr.query();\r\n        if (recent365Gr.next()) {\r\n            result.recent365 = parseInt(recent365Gr.getAggregate('COUNT'));\r\n        }\r\n        \r\n        var recent90Gr = new GlideAggregate('problem');\r\n        recent90Gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(90)');\r\n        recent90Gr.addAggregate('COUNT');\r\n        recent90Gr.query();\r\n        if (recent90Gr.next()) {\r\n            result.recent90 = parseInt(recent90Gr.getAggregate('COUNT'));\r\n        }\r\n        \r\n        return result;\r\n    }\r\n\r\n    function analyzeFieldCompleteness(config) {\r\n        var results = {};\r\n        \r\n        // Get total count - use more inclusive criteria\r\n        var totalGr = new GlideAggregate('problem');\r\n        if (!config.useAllRecords && config.states.closedStates.length > 0) {\r\n            totalGr.addQuery('state', 'IN', config.states.closedStates.join(','));\r\n        }\r\n        totalGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\r\n        totalGr.addAggregate('COUNT');\r\n        totalGr.query();\r\n        \r\n        var total = 0;\r\n        if (totalGr.next()) {\r\n            total = parseInt(totalGr.getAggregate('COUNT'));\r\n        }\r\n        \r\n        gs.info('Analyzing ' + total + ' records for field completeness...');\r\n        \r\n        for (var f = 0; f < config.keyFields.length; f++) {\r\n            var fieldName = config.keyFields[f];\r\n            var testGr = new GlideRecord('problem');\r\n            if (!testGr.isValidField(fieldName)) {\r\n                gs.warn('Field ' + fieldName + ' not found, skipping');\r\n                continue;\r\n            }\r\n            \r\n            var filledGr = new GlideAggregate('problem');\r\n            if (!config.useAllRecords && config.states.closedStates.length > 0) {\r\n                filledGr.addQuery('state', 'IN', config.states.closedStates.join(','));\r\n            }\r\n            filledGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\r\n            filledGr.addQuery(fieldName, '!=', '');\r\n            filledGr.addNotNullQuery(fieldName);\r\n            filledGr.addAggregate('COUNT');\r\n            filledGr.query();\r\n            \r\n            var filled = 0;\r\n            if (filledGr.next()) {\r\n                filled = parseInt(filledGr.getAggregate('COUNT'));\r\n            }\r\n            \r\n            results[fieldName] = {\r\n                total: total,\r\n                filled: filled,\r\n                percentage: total > 0 ? (filled / total * 100) : 0\r\n            };\r\n        }\r\n        \r\n        return results;\r\n    }\r\n\r\n    function analyzeTextQuality(config) {\r\n        var gr = new GlideRecord('problem');\r\n        if (!config.useAllRecords && config.states.closedStates.length > 0) {\r\n            gr.addQuery('state', 'IN', config.states.closedStates.join(','));\r\n        }\r\n        gr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\r\n        gr.setLimit(config.sampleSize);\r\n        gr.query();\r\n\r\n        var descStats = {totalLength: 0, count: 0, tooShort: 0, goodQuality: 0};\r\n        var closeNotesStats = {totalLength: 0, count: 0, tooShort: 0, goodQuality: 0};\r\n\r\n        while (gr.next()) {\r\n            // Description analysis\r\n            var desc = gr.getValue('description') || '';\r\n            if (desc) {\r\n                descStats.count++;\r\n                descStats.totalLength += desc.length;\r\n                if (desc.length < config.thresholds.minDescriptionLength) {\r\n                    descStats.tooShort++;\r\n                } else {\r\n                    descStats.goodQuality++;\r\n                }\r\n            }\r\n\r\n            // Close notes analysis\r\n            var closeNotes = gr.getValue('close_notes') || '';\r\n            if (closeNotes) {\r\n                closeNotesStats.count++;\r\n                closeNotesStats.totalLength += closeNotes.length;\r\n                if (closeNotes.length < config.thresholds.minCloseNotesLength) {\r\n                    closeNotesStats.tooShort++;\r\n                } else {\r\n                    closeNotesStats.goodQuality++;\r\n                }\r\n            }\r\n        }\r\n\r\n        return {\r\n            description: {\r\n                avgLength: descStats.count > 0 ? descStats.totalLength / descStats.count : 0,\r\n                goodQualityPct: descStats.count > 0 ? (descStats.goodQuality / descStats.count * 100) : 0\r\n            },\r\n            closeNotes: {\r\n                avgLength: closeNotesStats.count > 0 ? closeNotesStats.totalLength / closeNotesStats.count : 0,\r\n                goodQualityPct: closeNotesStats.count > 0 ? (closeNotesStats.goodQuality / closeNotesStats.count * 100) : 0\r\n            }\r\n        };\r\n    }\r\n\r\n    function analyzeCategoryDistribution(config) {\r\n        var catGr = new GlideAggregate('problem');\r\n        if (!config.useAllRecords && config.states.closedStates.length > 0) {\r\n            catGr.addQuery('state', 'IN', config.states.closedStates.join(','));\r\n        }\r\n        catGr.addQuery('sys_created_on', '>=', 'javascript:gs.daysAgoStart(' + config.thresholds.maxAge + ')');\r\n        catGr.groupBy('category');\r\n        catGr.addAggregate('COUNT');\r\n        catGr.query();\r\n\r\n        var categories = [];\r\n        while (catGr.next()) {\r\n            categories.push({\r\n                category: catGr.getValue('category'),\r\n                count: parseInt(catGr.getAggregate('COUNT'))\r\n            });\r\n        }\r\n\r\n        categories.sort(function(a, b) { return b.count - a.count; });\r\n        return categories;\r\n    }\r\n\r\n    function calculateOverallScore(completeness, textQuality, timeAnalysis) {\r\n        var score = 0;\r\n        var weights = {completeness: 40, textQuality: 40, timeQuality: 20};\r\n\r\n        // Completeness score\r\n        var compTotal = 0, compCount = 0;\r\n        for (var field in completeness) {\r\n            compTotal += completeness[field].percentage;\r\n            compCount++;\r\n        }\r\n        var compScore = compCount > 0 ? (compTotal / compCount) : 0;\r\n        score += (compScore / 100) * weights.completeness;\r\n\r\n        // Text quality score\r\n        var textScore = (textQuality.description.goodQualityPct + textQuality.closeNotes.goodQualityPct) / 2;\r\n        score += (textScore / 100) * weights.textQuality;\r\n\r\n        // Time quality score\r\n        var timeScore = 100 - timeAnalysis.tooQuickPct;\r\n        score += (timeScore / 100) * weights.timeQuality;\r\n\r\n        return score;\r\n    }\r\n\r\n})();"
  },
  {
    "path": "Specialized Areas/Project Management/Create Explain Project EAC (Estimate At Completion) Value/Create Explain Project EAC (Estimate At Completion) Value",
    "content": "var projectId = '<SYS ID of Project (pm_project)>';\n\nvar instanceName = gs.getProperty('instance_name');\n\ngs.info('Ensure you have clicked the \"Calculate Completion Estimates\" related link on the Project form to sync up the EAC with the latest data.');\ngs.info('');\nvar fiscalCal = new ITFM_FiscalCalendar();\nvar fiscalObject = fiscalCal.validatePeriods();\nvar parsedFiscalObject = JSON.parse(fiscalObject);\n\nif (parsedFiscalObject.type == \"Error\") {\ngs.info('Fiscal Calendar is not valid which may cause issues with the expected EAC calculation:');\ngs.info(parsedFiscalObject.msg);\n}\n\nvar currentFiscalPeriod = new FinancialsForPPM().getCurrentFiscalPeriod();\nvar currentFiscalPeriodId = JSON.parse(currentFiscalPeriod).fiscal_period;\nvar grFP = new GlideRecord('fiscal_period');\ngrFP.get(currentFiscalPeriodId);\n\nvar startDate = grFP.getValue('start_date_time');\nvar year = startDate.substring(0,4);\nvar month = startDate.substring(4,6);\nvar day = startDate.substring(6,8);\nvar date = year + \"-\" + month + \"-\" + day;\n\ngs.info('');\ngs.info('The current Fiscal Period is named \"' + grFP.getDisplayValue() + '\" and the Sys ID  is ' + currentFiscalPeriodId);\n\ngs.info('');\ngs.info('EAC calculation: Total actual costs from previous months + Total planned cost from the current and future months');\n\nvar capexFuture = 0;\nvar opexFuture = 0;\nvar capexActual = 0;\nvar opexActual = 0;\n\nvar aggCapexFuture = new GlideAggregate('cost_plan_breakdown');\naggCapexFuture.addAggregate('SUM', 'cost_default_currency');\naggCapexFuture.addEncodedQuery(\"breakdown_type=task^task=\" + projectId + \"^expense_type=capex^fiscal_period.fiscal_start_date_time>=javascript:gs.dateGenerate('\" + date + \"','00:00:00')\");\naggCapexFuture.setGroup(false);\naggCapexFuture.query();\nif (aggCapexFuture.next()) {\ncapexFuture = aggCapexFuture.getAggregate('SUM', 'cost_default_currency');\ngs.info('');\ngs.info('Capex Future Costs: ' + aggCapexFuture.getAggregate('SUM', 'cost_default_currency'));\ngs.info(\"https://\" + instanceName + \".service-now.com/\" + \"cost_plan_breakdown_list.do?sysparm_query=breakdown_type%3Dtask%5Etask%3D\" + projectId + \"%5Eexpense_type%3Dcapex%5Efiscal_period.fiscal_start_date_time%3E%3Djavascript%3Ags.dateGenerate('\" + date + \"'%2C'00%3A00%3A00')&sysparm_view=\");\n}\n\nvar aggOpexFuture = new GlideAggregate('cost_plan_breakdown');\naggOpexFuture.addAggregate('SUM', 'cost_default_currency');\naggOpexFuture.addEncodedQuery(\"breakdown_type=task^task=\" + projectId + \"^expense_type=opex^fiscal_period.fiscal_start_date_time>=javascript:gs.dateGenerate('\" + date + \"','00:00:00')\");\naggOpexFuture.setGroup(false);\naggOpexFuture.query();\nif (aggOpexFuture.next()) {\nopexFuture = aggOpexFuture.getAggregate('SUM', 'cost_default_currency');\ngs.info('');\ngs.info('Opex Future Costs: ' + aggOpexFuture.getAggregate('SUM', 'cost_default_currency'));\ngs.info(\"https://\" + instanceName + \".service-now.com/\" + \"cost_plan_breakdown_list.do?sysparm_query=breakdown_type%3Dtask%5Etask%3D\" + projectId + \"%5Eexpense_type%3Dopex%5Efiscal_period.fiscal_start_date_time%3E%3Djavascript%3Ags.dateGenerate('\" + date + \"'%2C'00%3A00%3A00')&sysparm_view=\");\n}\n\nvar aggCapexActual = new GlideAggregate('cost_plan_breakdown');\naggCapexActual.addAggregate('SUM', 'actual');\naggCapexActual.addEncodedQuery(\"breakdown_type=task^task=\" + projectId + \"^expense_type=capex^fiscal_period.fiscal_start_date_time<javascript:gs.dateGenerate('\" + date + \"','00:00:00')\");\naggCapexActual.setGroup(false);\naggCapexActual.query();\nif (aggCapexActual.next()) {\ncapexActual = aggCapexActual.getAggregate('SUM', 'actual');\ngs.info('');\ngs.info('Capex Past Actual Costs: ' + aggCapexActual.getAggregate('SUM', 'actual'));\ngs.info(\"https://\" + instanceName + \".service-now.com/\" + \"cost_plan_breakdown_list.do?sysparm_query=breakdown_type%3Dtask%5Etask%3D\" + projectId + \"%5Eexpense_type%3Dcapex%5Efiscal_period.fiscal_start_date_time%3Cjavascript%3Ags.dateGenerate('\" + date + \"'%2C'00%3A00%3A00')&sysparm_view=\");\n}\n\nvar aggOpexActual = new GlideAggregate('cost_plan_breakdown');\naggOpexActual.addAggregate('SUM', 'actual');\naggOpexActual.addEncodedQuery(\"breakdown_type=task^task=\" + projectId + \"^expense_type=opex^fiscal_period.fiscal_start_date_time<javascript:gs.dateGenerate('\" + date + \"','00:00:00')\");\naggOpexActual.setGroup(false);\naggOpexActual.query();\nif (aggOpexActual.next()) {\nopexActual = aggOpexActual.getAggregate('SUM', 'actual');\ngs.info('');\ngs.info('Opex Past Actual Costs: ' + aggOpexActual.getAggregate('SUM', 'actual'));\ngs.info(\"https://\" + instanceName + \".service-now.com/\" + \"cost_plan_breakdown_list.do?sysparm_query=breakdown_type%3Dtask%5Etask%3D\" + projectId + \"%5Eexpense_type%3Dopex%5Efiscal_period.fiscal_start_date_time%3Cjavascript%3Ags.dateGenerate('\" + date + \"'%2C'00%3A00%3A00')&sysparm_view=\");\n}\n\ngs.info('');\ngs.info('Plugged into the formula (CapexNowAndFuture + OpexNowAndFuture + CapexPastActuals + OpexPastActuals) = EAC');\ngs.info(Number(capexFuture) + ' + ' + Number(opexFuture) + ' + ' + Number(capexActual) + ' + ' + Number(opexActual) + ' = EAC');\nvar sum = Number(capexFuture) + Number(opexFuture) + Number(capexActual) + Number(opexActual);\ngs.info('Expected EAC = ' + sum);\n"
  },
  {
    "path": "Specialized Areas/Project Management/Create Explain Project EAC (Estimate At Completion) Value/README.md",
    "content": "The Estimate At Completion value (EAC) for Projects can be hard to visualize. \nThis script will print out messages to explain the formula and how the value is getting calculated.\n\nRun this in the [System Defintion > Scripts - Background] module. \nSet the \"projectId\" variable on line 1 with the Sys ID of your pm_project record.\n"
  },
  {
    "path": "Specialized Areas/Record Producer/Create Records By Import Set/README.md",
    "content": "# Create Records importing an excel spreadsheet in the record producer.\nThe script import the excel spreadsheet in the Data Source table (sys_data_source) and trigger the tranform map, creating the records.\n\n## Configuration\nStep 1. Create a Transform Map\n\nStep 2. Add the table name field to \"Data Source\" and the excel template file \n![table name field](config1.png)\n\nStep 3. Insert a description with a link to the user download the template\n![description and template](config2.png)\n\nStep 4. Insert the script and change the static fields\n\nStep 5. The users need to drop attach the file and submit the record producer\n![attach and submit](config3.png)\n"
  },
  {
    "path": "Specialized Areas/Record Producer/Create Records By Import Set/createRecordsByImportSet.js",
    "content": "// Set the following variables with the name of your import set table and the destination table\nvar importSetTableName = \"u_asset_staging\"; // Use your staging table name\nvar destinationTableName = \"alm_asset\"; // Use your table name\n\n// Create a property with the transform maps sys_id separated by comma\nvar transformMapIDs = gs.getProperty('transform.map.sys_id');\n\n// Setup data source for attachment\ncurrent.name = \"Asset Import\"; \ncurrent.import_set_table_name = importSetTableName;\ncurrent.file_retrieval_method = \"Attachment\";\ncurrent.type = \"File\";\ncurrent.format = \"Excel\"; // Type of the file\ncurrent.header_row = 1;\ncurrent.sheet_number = 1; // Number of sheets\ncurrent.insert();\n\n// Process excel file\nvar loader = new GlideImportSetLoader();\nvar importSetRec = loader.getImportSetGr(current);\nvar ranload = loader.loadImportSetTable(importSetRec, current);\nimportSetRec.state = \"loaded\";\nimportSetRec.update();\n\n// Transform import set\nvar transformWorker = new GlideImportSetTransformerWorker(importSetRec.sys_id, transformMapIDs);\ntransformWorker.setBackground(true);\ntransformWorker.start();\n\ngs.flushMessages(); // Clear the past messages\n\n// Redirect to the updated/inserted items\nvar redirectURL = destinationTableName + \"_list.do?sysparm_first_row=1&sysparm_query=sys_updated_bySTARTSWITHjavascript%3A+gs.getUserName%28%29%5Esys_created_onONLast+minute%40javascript%3Ags.beginningOfLastMinute%28%29%40javascript%3Ags.endOfLastMinute%28%29%5EORsys_created_onONCurrent+minute%40javascript%3Ags.beginningOfCurrentMinute%28%29%40javascript%3Ags.endOfCurrentMinute%28%29%5ENQsys_updated_bySTARTSWITHjavascript%3A+gs.getUserName%28%29%5Esys_updated_onONLast+minute%40javascript%3Ags.beginningOfLastMinute%28%29%40javascript%3Ags.endOfLastMinute%28%29%5EORsys_updated_onONCurrent+minute%40javascript%3Ags.beginningOfCurrentMinute%28%29%40javascript%3Ags.endOfCurrentMinute%28%29&sysparm_view=amsla\";\nproducer.redirect = redirectURL;\n\ngs.addInfoMessage('The records was sucessfully created!');\n\n// Since we inserted data source already, abort additional insert by record producer\ncurrent.setAbortAction(true);\n"
  },
  {
    "path": "Specialized Areas/Record Producer/Update Incident Record from Record Producer/README.md",
    "content": "This code_snippet.js script enables seamless Incident record updates directly from a Record Producer.\nWithin the Record Producer, you can define specific record fields and specify which fields should be updated — all within ServiceNow, without any interruptions.\n\nUsing this script, end users can easily update an existing Incident’s Short Description or modify an Onboarding Form — without requiring any Service Desk involvement or navigating to the record via Workspace or the Next Experience UI.\n\nThis use case became a key highlight in one of our projects, showcasing how automation can enhance user experience and efficiency within ServiceNow.\n"
  },
  {
    "path": "Specialized Areas/Record Producer/Update Incident Record from Record Producer/code_snippet.js",
    "content": "// This script script updates Incident Record from Record Producer\n// Create a Record Producer and add script under producer script\n\nnew global.GlideQuery('incident').where('sys_id', producer.incident_number).update({\n    short_description: producer.short_description\n});\ngs.addInfoMessage('Record Updated Successfully');\ncurrent.setAbortAction(true);\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Adhaar validation/Adhaar Validation Script.js",
    "content": "function onSubmit() {\n\n    g_form.hideFieldMsg('adhaar'); // hide previous field mesage.\n    /*\n    Adhaar validation script.\n\tAdhaar is a 12 digit unique identification number issues by UIDAI in India for Indian Residents.\n    /^[2-9][0-9]{3}[0-9]{4}[0-9]{4}$/\n    // ^ → Start of the string\n    // [2-9] → The first digit must be between 2 and 9\n    // [0-9]{3} → Followed by exactly 3 digits (0–9)\n    // [0-9]{4} → Followed by exactly 4 digits (0–9)\n    // [0-9]{4} → Followed by exactly 4 digits (0–9)\n    // $ → End of the string\n    */\n\n    var adhrNum = g_form.getValue('adhaar'); // adhaar variable name\n    var adharReg = /^[2-9][0-9]{3}[0-9]{4}[0-9]{4}$/; // adhaar regex\n    var regex = new RegExp(adharReg);\n\n    if (!regex.test(adhrNum)) {\n        g_form.clearValue('adhaar'); // clear field value\n        g_form.showFieldMsg('adhaar', \"Please enter valid adhaar number\", 'error', true);\n        return false; // stop form submission\n    }\n\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Adhaar validation/README.md",
    "content": "\n# Regular Expression on Catalog Client script\n\n*****************\n\n8th october:\n\nThis script will validate the Adhaar number.\n\nAdhaar is a 12 digit unique identification number issued by the Unique Identification Authority of India (UIDAI) for Indian residents.\n\nThe script will validate Adhaar through regex, and if it is not valid, the variable is cleared with a field message.\n\nThe preferred OOB method for catalog variables is listed here : https://www.servicenow.com/docs/bundle/xanadu-servicenow-platform/page/product/service-catalog-management/task/define-regex-vrble.html . The same regex can be defined in \"Variable Validation Regex\" module.\n\n\nRegex details :\n\n/^[2-9][0-9]{3}[0-9]{4}[0-9]{4}$/\n\n// ^ → Start of the string\n\n// [2-9] → The first digit must be between 2 and 9\n\n// [0-9]{3} → Followed by exactly 3 digits (0–9)\n\n// [0-9]{4} → Followed by exactly 4 digits (0–9)\n\n// [0-9]{4} → Followed by exactly 4 digits (0–9)\n\n// $ → End of the string\n\n*****************\n\nWith the help of this code, you can easily validate the input value from the user. If it is not an Adhaar, you can clear it and throw an error message below the variable. The same validation can be used for fields instead of variables.\n\n* [Click here for script](script.js)\n\n\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Allow Characters + - ) ( for Phone numbers/README.md",
    "content": "### Allows Characters +, -, (, ) for entering Phone Numbers\n\nAllows characters useful for adding phone numbers in various formats like (+1) - 123 - 456 - 789, +1-978-654-362, (123) (567) (897) etc.\nOne can add more functionality to it, by controlling the number of digits added etc.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Allow Characters + - ) ( for Phone numbers/allowsCharsForPhnNumber.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n    var num = g_form.getValue('valid_number');  //phone number field that needs to be validated\n    var regex = /^[\\d\\+ )(/-]+$/;               // allows the following characters +, -, (, ), all numbers in no particular order\n    if (!regex.test(num)) {\n        alert('Please enter a valid Phone number.');\n        g_form.clearValue('valid_number');\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/AllowAnyLanguage/README.md",
    "content": "# trimNonCharacters\n\n## Description\n\nThis JavaScript function removes special characters from strings while preserving valid characters from multiple language groups.\n\nUseful for validating names or user input in internationalized applications.\n\n> Sorry, Elon Musk's firstborn — `X Æ A-12` might not make it through unscathed.\n\n## Features\n\n- Removes punctuation, emojis, and symbols\n- Preserves:\n  - Basic Latin letters (A–Z, a–z)\n  - Digits (0–9)\n  - Whitespace and parentheses\n  - Accented characters (e.g., é, ñ, ü)\n  - Characters from:\n    - Central/Eastern European languages\n    - Cyrillic (Russian, Ukrainian)\n    - Greek\n    - Arabic\n    - Hindi/Sanskrit (Devanagari)\n    - Chinese, Japanese, Korean (CJK ideographs)\n\n## Compatibility\n\n- Fully compatible with **ServiceNow background scripts**\n- Avoids unsupported features like:\n  - Unicode property escapes (`\\p{L}`)\n  - Multi-line regex literals\n  - Inline comments inside regex\n\n## Usage\n\nChange the input string to your own text/variable, call the function with the input, handle the result:\n```\nvar input = \"Hello, мир! Γειά σου κόσμε! مرحبا بالعالم! नमस्ते दुनिया! 你好，世界！\";\nvar cleaned = trimNonCharacters(input);\ngs.info(\"Cleaned: \" + cleaned);\n```\n## Customization\nLanguage support is modular. Unicode ranges are defined in an array and can be commented out or modified as needed:\n```\nvar allowedRanges = [\n  \"a-zA-Z0-9()\",     // Basic Latin\n  \"\\\\s\",             // Whitespace\n  \"\\\\u00C0-\\\\u00FF\", // Western European\n  \"\\\\u0100-\\\\u017F\", // Central/Eastern European\n  \"\\\\u0400-\\\\u04FF\", // Cyrillic\n  \"\\\\u0370-\\\\u03FF\", // Greek\n  \"\\\\u0600-\\\\u06FF\", // Arabic\n  \"\\\\u0900-\\\\u097F\", // Hindi/Sanskrit\n  \"\\\\u4E00-\\\\u9FFF\"  // Chinese, Japanese, Korean\n];\n```\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/AllowAnyLanguage/allowanylanguage.js",
    "content": "function trimNonCharacters(str) {\n  // Define Unicode ranges for each language group\n  var allowedRanges = [\n    \"a-zA-Z0-9()\",     // Basic Latin letters, digits, parentheses\n    \"\\\\s\",             // Whitespace\n\n    // Western European (Latin-1 Supplement)\n    \"\\\\u00C0-\\\\u00FF\", // e.g., é, ñ, ü\n\n    // Central/Eastern European (Latin Extended-A)\n    \"\\\\u0100-\\\\u017F\", // e.g., Ą, Č, Ő\n\n    // Cyrillic (Russian, Ukrainian, Bulgarian)\n    \"\\\\u0400-\\\\u04FF\", // e.g., мир, привет\n\n    // Greek\n    \"\\\\u0370-\\\\u03FF\", // e.g., Γειά σου κόσμε\n\n    // Arabic\n    \"\\\\u0600-\\\\u06FF\", // e.g., مرحبا بالعالم\n\n    // Devanagari (Hindi, Sanskrit)\n    \"\\\\u0900-\\\\u097F\", // e.g., नमस्ते दुनिया\n\n    // CJK Unified Ideographs (Chinese, Japanese Kanji, Korean Hanja)\n    \"\\\\u4E00-\\\\u9FFF\"  // e.g., 你好，世界\n  ];\n\n  // Build the regex pattern string\n  var pattern = \"[^\" + allowedRanges.join(\"\") + \"]+\";\n\n  // Create the RegExp object\n  var regex = new RegExp(pattern, \"g\");\n\n  // Apply the regex to clean the string\n  return str.replace(regex, '');\n}\n\n// Example input with comments for each language\nvar input = \n  \"Hello, \" +                  // English: \"Hello, \"\n  \"мир! \" +                    // Russian: \"world!\"\n  \"Γειά σου κόσμε! \" +        // Greek: \"Hello world!\"\n  \"مرحبا بالعالم! \" +         // Arabic: \"Hello world!\"\n  \"नमस्ते दुनिया! \" +         // Hindi: \"Hello world!\"\n  \"你好，世界！\";               // Chinese: \"Hello, world!\"\n\nvar cleaned = trimNonCharacters(input);\n\ngs.info(\"Original: \" + input);\ngs.info(\"Cleaned: \" + cleaned);\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Check for special characters/readme.md",
    "content": "# Special Characters Validation (onChange Client Script)\n\nThis script validates user input in a specific field and prevents the use of disallowed special characters.  \nIt is designed to run as an **onChange client script** .\n\n## Functionality\n\n- When the user changes the value of a field, the script checks if the new value contains any special characters.\n- If disallowed characters are found, the field is cleared and an error message is displayed to the user.\n- The validation uses a regular expression that includes common special characters such as `~`, `@`, `|`, `$`, `^`, `<`, `>`, `*`, `+`, `=`, `;`, `?`, `` ` ``, `'`, `(`, `)`, `[`, and `]`.\n\n## How to Use\n\n1. Add the script as an **onChange client script** on the field you want to validate.\n2. Replace the placeholder `'<your_field_name>'` in the script with the actual field name.\n3. Customize the regular expression if you want to allow or block different characters.\n\n## Example Behavior\n\n- Input: `Hello@World` → ❌ Invalid → Field is cleared, error message shown.\n- Input: `HelloWorld` → ✅ Valid → No action taken.\n\n## Notes\n\n- The script uses `g_form.clearValue()` to reset the field and `g_form.showErrorBox()` to display feedback.\n\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Check for special characters/specialcharacter.js",
    "content": "function onChange(control, oldValue, newValue, isLoading, isTemplate) {\n    // Exit early if the form is loading, in template mode, or the new value is empty\n    if (isLoading || newValue === '') {\n        return;\n    }\n\n    /**\n     * Define a regular expression to match disallowed special characters.\n     * Breakdown of the pattern:\n     * [~@|$^<>*+=;?`'\\)\\(\\[\\]]\n     * * - Square brackets [] define a character class, meaning \"match any one of these characters\".\n     * - ~ @ | $ ^ < > * + = ; ? ` ' ) ( [ ] : These are the characters being checked.\n     * - Characters that have special meaning inside a character class (like `(`, `)`, `[`, `]`) must be escaped with a backslash `\\`.\n     */\n    var disallowedChars = /[~@|$^<>*+=;?`'\\)\\(\\[\\]]/;\n\n    var fieldName = '<your_field_name>'; // Replace with the actual field name being validated\n\n    // Check if the new value contains any disallowed characters\n    if (disallowedChars.test(newValue)) {\n        g_form.clearValue(fieldName);\n        g_form.showErrorBox(fieldName, 'Special characters are not allowed.');\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Check if number has 10 digits/README.md",
    "content": "# Digit Length Validator\n\n## Description\n\nThis script checks if a string contains exactly a specified number of digits. Useful for validating numeric input. The digit count can be adjusted in the code.\n\n## Usage\nTo change the required digit count, update the number in the regular expression\n```\nvar digitLengthRegex = /^\\d{N}$/;  // Replace N with desired digit count\n```\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Check if number has 10 digits/script.js",
    "content": "var digitLengthRegex = /^\\d{10}$/;  // Matches exactly 10 digits\n\nvar elevenString = '01234567899'; // 11 digits\nvar tenString = '0123456789';     // 10 digits\nvar nineString = '012345678';     // 9 digits\n\ngs.info(digitLengthRegex.test(elevenString)); // false\ngs.info(digitLengthRegex.test(tenString));    // true\ngs.info(digitLengthRegex.test(nineString));   // false\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Consecutive duplicate words/README.md",
    "content": "# Consecutive duplicate words\n\n**Overview** - This Regex can be used to find any two consecutive duplicate words. This can be mainly used in client scripts that validates naming conventions.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Consecutive duplicate words/script.js",
    "content": "// Consecutive Duplicate Words Detector\n// This regex finds repeated words that appear consecutively, such as \"the the\" or \"and and\".\n// Useful for grammar or text quality checks.\n\nconst duplicateWordsRegex = /\\b([A-Za-z]+)\\s+\\1\\b/gi;\n\nfunction hasDuplicateWords(text) {\n  return duplicateWordsRegex.test(text);\n}\n\n// Example usage:\nconsole.log(hasDuplicateWords(\"This is the the example.\")); // true\nconsole.log(hasDuplicateWords(\"We need to to check this.\")); // true\nconsole.log(hasDuplicateWords(\"Everything looks good here.\")); // false\nconsole.log(hasDuplicateWords(\"Hello hello world.\")); // true (case-insensitive)\nconsole.log(hasDuplicateWords(\"No repetition found.\")); // false\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Credit Card Number Validator/README.md",
    "content": "# Credit Card Validator Regular Expression\n\nThis JavaScript module provides a regular expression for validating credit card numbers from major card providers such as Visa, MasterCard, American Express, Discover, Diners Club, and JCB. It ensures that the card number follows the correct structure and length for each provider.\n\n## Features:\n\n- Supports major credit card providers: Visa, MasterCard, American Express, Discover, Diners Club, and JCB.\n- Validates the structure, length, and format of the card numbers.\n- Simple and efficient to use for front-end or back-end applications.\n\n## Usage:\n\n```javascript\nconst creditCardRegex = /^(?:4[0-9]{12}(?:[0-9]{3})?|        // Visa\n                          5[1-5][0-9]{14}|                   // MasterCard\n                          3[47][0-9]{13}|                    // American Express\n                          6(?:011|5[0-9]{2})[0-9]{12}|       // Discover\n                          3(?:0[0-5]|[68][0-9])[0-9]{11}|    // Diners Club\n                          (?:2131|1800|35\\d{3})\\d{11})$/;    // JCB\n\nfunction validateCreditCard(cardNumber) {\n  return creditCardRegex.test(cardNumber);\n}\n\n// Example usage:\nconsole.log(validateCreditCard('4111111111111111')); // Visa, true\nconsole.log(validateCreditCard('5555555555554444')); // MasterCard, true\nconsole.log(validateCreditCard('378282246310005'));  // American Express, true\nconsole.log(validateCreditCard('6011111111111117')); // Discover, true\nconsole.log(validateCreditCard('30569309025904'));   // Diners Club, true\nconsole.log(validateCreditCard('3530111333300000')); // JCB, true\nconsole.log(validateCreditCard('1234567812345670')); // Invalid, false\n```\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Credit Card Number Validator/validateCreditCard.js",
    "content": "// Credit Card Number Validator\n// This regex validates credit card formats from Visa, MasterCard, American Express, Discover, Diners Club, JCB, and more.\n\nconst creditCardRegex =\n  /^(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|6(?:011|5[0-9]{2})[0-9]{12}|3(?:0[0-5]|[68][0-9])[0-9]{11}|(?:2131|1800|35\\d{3})\\d{11})$/;\n\nfunction validateCreditCard(cardNumber) {\n  return creditCardRegex.test(cardNumber);\n}\n\n// Example usage:\nconsole.log(validateCreditCard(\"4111111111111111\")); // Visa, true\nconsole.log(validateCreditCard(\"5555555555554444\")); // MasterCard, true\nconsole.log(validateCreditCard(\"378282246310005\")); // American Express, true\nconsole.log(validateCreditCard(\"6011111111111117\")); // Discover, true\nconsole.log(validateCreditCard(\"30569309025904\")); // Diners Club, true\nconsole.log(validateCreditCard(\"3530111333300000\")); // JCB, true\nconsole.log(validateCreditCard(\"1234567812345670\")); // Invalid, false\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Email Address Validation/README.md",
    "content": "# Email Address Validation\n\nThis project provides email validation functionality in pure JavaScript. It includes a refactored regex implementation that covers common RFC 5322 patterns and stricter domain rules.\n\n---\n\n## Refactored Email Validation (2025 Update)\n\nThe regex has been improved to handle edge cases in the local part, domain, and top-level domain (TLD).\n\n### Key Features\n\n- Supports letters, digits, and allowed special characters in the local part:  \n  `!#$%&'*+/=?^_`{|}~-`\n- Supports dots in the local part, but not consecutive dots.\n- Supports quoted local parts, e.g., `\"john.doe\"@example.com`.\n- Validates domain labels:\n  - Letters, digits, and hyphens (`-`)\n  - Labels cannot start or end with a hyphen\n  - No consecutive dots\n- Restricts TLD length to 2–63 characters.\n- Supports IPv4/IPv6 literals in brackets, e.g., `user@[192.168.0.1]`.\n\n### Example Usage\n\n```js\nconst emailRegex = /^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:(?:\\\\[\\x00-\\x7f]|[^\\\\\"\\r\\n])*)\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}|\\[(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\\.){3}(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2}|[a-zA-Z0-9-]*[a-zA-Z0-9]:[^\\]]+)\\])$/;\n\nfunction validateEmail(email) {\n  return emailRegex.test(email);\n}\n\nconst emails = [\n  \"example@email.com\",\n  \"user.name+tag@example.co.uk\",\n  '\"quoted.user\"@example.com',\n  \"user@[192.168.1.1]\",\n  \"user@-example.com\",\n  \"user@example..com\",\n  \"user@example-.com\",\n  \"user@.example.com\"\n];\n\nemails.forEach(email => {\n  console.log(email, validateEmail(email) \n    ? \"is valid\" \n    : \"is invalid\");\n});\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Email Address Validation/isEmail.js",
    "content": "//const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$/;\n\n//refactor emailRegex\nconst emailRegex = /^(?:[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|\"(?:(?:\\\\[\\x00-\\x7f]|[^\\\\\"\\r\\n])*)\")@(?:(?:[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?\\.)+[a-zA-Z]{2,63}|\\[(?:(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2})\\.){3}(?:25[0-5]|2[0-4][0-9]|1?[0-9]{1,2}|[a-zA-Z0-9-]*[a-zA-Z0-9]:[^\\]]+)\\])$/;\n\nconst emails = [\n  // Valid emails\n  \"example@email.com\",\n  \"user.name+tag@example.co.uk\",\n  '\"quoted.user\"@example.com',\n  \"user@[192.168.1.1]\",\n\n  // Invalid emails (should fail)\n  \"user@-example.com\",\n  \"user@example..com\",\n  \"user@example-.com\",\n  \"user@.example.com\"\n];\n\nemails.forEach(email => {\n  console.log(email, emailRegex.test(email) ? \"is valid email addres\" : \"is invalid email address\");\n});"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Encode spaces for URLs/README.md",
    "content": "convert spaces to %20 for creating dynamic urls\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Encode spaces for URLs/convert.js",
    "content": "var name = 'hafsa asif';\ngs.info(encodeURIComponent(name.trim()));\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Ethiopia country code/README.md",
    "content": "# Check for Ethiopian phone number\n\n## Problem statement\n\nFind or detect ethiopian phone number\n\n## Regex code explanation\n\n```js\nconst regex = /^(\\+251|0)(9)([0-9]{8})$/;\n```\n\n> ^: Matches the start of the string <br/>\n\n> (\\+251|0): This is a capturing group that matches either the country code \"+251\" or the digit \"0\". The | character acts as an OR operator, allowing either option to be matched.\n\n> (9): This is another capturing group that matches the digit \"9\". \n\n> ([0-9]{8}): This is a capturing group that matches exactly 8 digits. It represents the remaining digits of the Ethiopian phone number after the country code and the digit \"9\". The [0-9] character class matches any digit, and {8} specifies that exactly 8 digits should be matched.\n\n> $: Matches the end of the string.\n\n## Code explanation\n\n```js\nfunction isFromEthiopia(phoneNumber) {\n  return regex.test(phoneNumber);\n}\n```\n\n> isFromEthiopia(phoneNumber) { ... } function: This line declares a function named isFromEthiopia that accepts a phoneNumber parameter. This function will be used to determine if a given phone number belongs to Ethiopia.\n\n> return regex.test(phoneNumber);: This line uses the test() method of the RegExp object regex to check if the phoneNumber matches the defined regular expression pattern. The test() method returns true if there is a match and false otherwise.\n\n> The return statement returns the result of the regex.test(phoneNumber) expression, which indicates whether the given phone number matches the Ethiopian phone number pattern.\n\n\n## Demo Example\n\n```js\n// Example usage\nconst phoneNumber = \"+251912345678\"; // Replace with an Ethiopian phone number\n\n// isFromEthiopia returns true if the number is from ethiopia\nif (isFromEthiopia(phoneNumber)) {\n  console.log(`Is an Ethiopian phone number`);\n} else {\n  console.log(`Is not Ethiopian phone number`);\n}\n```\n\n## Valid Phone numbers\n\n- +251912345678\n- 0912345678\n- +251911223344\n- 0911223344\n- 091234567\n\n## invalid phone numbers\n\n- +251012345678\n- 09123456789\n- +2519ABCDE12\n- 091234567890\n- 09123"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Ethiopia country code/script.js",
    "content": "// check for ethiopian country code\nconst regex = /^(\\+251|0)(9)([0-9]{8})$/;\n\nfunction isFromEthiopia(phoneNumber) {\n  return regex.test(phoneNumber);\n}\n\n// Example usage\nconst phoneNumber = \"+251912345678\"; // Replace with an Ethiopian phone number\n\n// isFromEthiopia returns true if the number is from ethiopia\nif (isFromEthiopia(phoneNumber)) {\n  console.log(`Is an Ethiopian phone number`);\n} else {\n  console.log(`Is not Ethiopian phone number`);\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Extracting Product Codes and Prices from a String/Extracting Product Codes and Prices from a String.js",
    "content": "var productDescriptions =\n    \"Product A (Code: ABC-123) costs $29.99.\\n\" +\n    \"Product B (Code: DEF-456) costs $39.50.\";\n\nvar productPattern = /Code:\\s*([A-Z]{3}-\\d{3})\\)\\s*costs\\s*\\$(\\d+(\\.\\d{2})?)/g;\n\nvar match;\nwhile ((match = productPattern.exec(productDescriptions)) !== null) {\n    var productCode = match[1];\n    var price = match[2];\n\n    gs.info('Product Code: ' + productCode + ', Price: $' + price);\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Extracting Product Codes and Prices from a String/README.md",
    "content": "# Extracting Product Codes and Prices from a String\n\nThis script extracts product codes and prices from a given string of product descriptions. It utilizes a regular expression to match the format of the product codes (e.g., ABC-123) and their corresponding prices (e.g., $29.99). The results are logged using the gs.info() method in ServiceNow, providing clear output for each product.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Find Emoji/README.md",
    "content": "# Detect emoji in string\n\nUse this script to detect if string contains emoji"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Find Emoji/index.js",
    "content": "const withEmojis = /\\p{Extended_Pictographic}/ug\n\nconst familyEmoji = '👨‍👩‍👧' \nconsole.log(withEmojis.test(familyEmoji))\n//true\n\nconst familyString = 'family'\nconsole.log(withEmojis.test(familyString))\n//false"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Format mobile into Australian mobile format/Format phone in Australian mobile format.js",
    "content": "/*\n    We have a phone number in +61433394881 and we want to format it in\n    0433 394 881 style\n*/\n\nvar phone = \"61433394881\"; // Mobile number with country code\n\n// Split the number by country code\nvar splits = phone.split(\"61\");\n\nvar mobileNumberOnly = splits[1];\n\nvar mobile = '';\n\n// Remove space etc\nvar cleaned = ('' + mobileNumberOnly).replace(/\\D/g, '');\n\n// Check if the input is of correct length\nvar match = cleaned.match(/^(\\d{3})(\\d{3})(\\d{3})$/);\n\nif (match) {\n\t// Format the mobile phone in XXXX XXX XXX format\n\tmobile = ( \"0\" + match[1] + ' ' + match[2] + ' ' + match[3]);\n}else {\n    throw new Error(\"Phone number cannot be formatted as XXXX XXX XXX.\");\n}\n\n\ngs.log(\"Mobile phone: \" + mobile);\n\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Format mobile into Australian mobile format/README.md",
    "content": "# Format phone into Australian mobile Format\n\nUse this script to format a phone number from +61433394881 style to 0433 394 881 style."
  },
  {
    "path": "Specialized Areas/Regular Expressions/Hashtag & Mention Extractor/README.md",
    "content": "# Hashtag & Mention Extractor for ServiceNow\n\nA simple yet useful **ServiceNow Background Script** that extracts all hashtags (`#example`) and mentions (`@user`) from any text input using regular expressions.\n\n---\n\n### 💡 Example Use Cases\n- Automatically identify hashtags and mentions in **incident comments**, **knowledge articles**, or **survey feedback**.\n- Extract tags or mentions from user input in **Catalog Items/Record Producers** to populate hidden variables or drive logic.\n\n---\n### 🚀 How to Run\n1. In your ServiceNow instance, navigate to **System Definition → Scripts – Background**.  \n2. Paste the script from this repository and adjust it according to your needs.  \n3. Click **Run Script**.  \n\n---\n\n### 📦 Reusability\nThe logic is **self-contained** within a single function block - no dependencies or external calls.  \nYou can easily **copy and adjust it** to fit different contexts:\n- Use it inside a **Business Rule**, **Script Include**, or **Flow Action Script** (see additional instructions below).  \n- Replace the sample `demoData` with a field value (e.g., `current.description`) to analyze the data.  \n- Adjust the regex to detect other patterns (emails, incident reference, etc.). See comments in the code for the additional examples.  \n\n---\n\n### 🔧 Possible Extensions\n- Parse table data (`sys_journal_field`, `kb_knowledge`) instead of static text.  \n- Store extracted tags in a custom table for analytics.  \n\n---\n\n### ℹ️ Additional Instructions:\n#### Use in Script Include\n\n1. Go to **Script Includes** in the Application Navigator.  \n2. Click **New**, and name it (e.g., `TagExtractorUtils`).  \n3. Set the following options:  \n   - **Client Callable**: `false`  \n   - **Accessible from**: `All application scopes`  \n4. Paste this code:\n\n```javascript\nvar TagExtractorUtils = Class.create();\nTagExtractorUtils.prototype = {\n    initialize: function () {},\n\n    extract: function (text) {\n        var result = {\n            hashtags: text.match(/#[A-Za-z0-9_]+/g),\n            mentions: text.match(/@[A-Za-z0-9_]+/g)\n        };\n        return result;\n    },\n\n    type: 'TagExtractorUtils'\n};\n```\n5. Use it as any other script include.\n\n#### Use in Business Rule with a couple of custom text fields and previously created script include\n\n1. Go to **Business Rules** in the Application Navigator.  \n2. Click **New**, choose a table (e.g., `sc_task`, `incident`).  \n3. Set **When** to `before`, `after`, or `async` (usually `async`).  \n4. In the **Script** section, call your tag-extracting logic:\n\n```javascript\n(function executeRule(current, previous /*null when async*/) {\n    var text = current.short_description + ' ' + current.description;\n    var tags = new TagExtractorUtils().extract(text);\n\n    current.u_hashtags = tags.hashtags.join(', ');\n    current.u_mentions = tags.mentions.join(', ');\n\n})(current, previous);\n```\n#### Use in Flow Action Script and previously created script include\n\n1. Go to **Flow Designer > Action** and click **New Action**.  \n2. Give it a name like `Extract Tags from Text`.  \n3. Add an **Input** (e.g., `input_text` of type String).  \n4. Add a **Script step**, then paste the script there:\n\n```javascript\n(function execute(inputs, outputs) {\n    var text = inputs.input_text || '';\n    var tags = new TagExtractorUtils().extract(text);\n\n    outputs.hashtags = tags.hashtags.join(', ');\n    outputs.mentions = tags.mentions.join(', ');\n})(inputs, outputs);\n```\n5. Use this **Action** in your flow to extract tags by passing the text to it as a parameter.\n\n<sub>🤖 This contribution was partially created with the help of AI.</sub>\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Hashtag & Mention Extractor/script.js",
    "content": "(function() {\n\n    var demoData = \"Quick test for hashtag and mention extraction in ServiceNow. \" +\n                   \"Let's make sure it catches #Hack4Good #ServiceNow #regex and mentions like @ivan and @servicenow.\";\n\n    // ---------------------------------------------\n    // Useful regex patterns (for future extension):\n    // - Hashtags:        /#[A-Za-z0-9_]+/g\n    // - Mentions:        /@[A-Za-z0-9_]+/g\n    // - Emails:          /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}/g\n    // - Ticket refs:     /INC\\d{7}/g (e.g., INC0012345)\n    // ---------------------------------------------\n\n    // Separate regex patterns for clarity\n    var hashtagRegex = /#[A-Za-z0-9_]+/g;\n    var mentionRegex = /@[A-Za-z0-9_]+/g;\n    \n    // Match both types\n    var hashtags = demoData.match(hashtagRegex);\n    var mentions = demoData.match(mentionRegex);\n\n    if (hashtags.length > 0) {\n        gs.info('Found ' + hashtags.length + ' hashtags: ' + hashtags.join(', '));\n    } else {\n        gs.info('No hashtags found.');\n    }\n\n    if (mentions.length > 0) {\n        gs.info('Found ' + mentions.length + ' mentions: ' + mentions.join(', '));\n    } else {\n        gs.info('No mentions found.');\n    }\n\n})();\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Hexadecimal color/README.md",
    "content": "## This code snippet helps validating the hexadecimal color codes ##\n\nBased on the regular expression, the following formats are allowed:\n* #ABC\n* #AB1\n* #123\n* #ABCDEF\n* #ABC123\n* #123456 \n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Hexadecimal color/validateHexColor.js",
    "content": "/** \n* Hexadecimal color pattern. The following formats are allowed:\n* #ABC | #AB1 | #123\n* #ABCDEF | #ABC123 | #123456 \n*/\nconst hexColorRegex = /^#([a-fA-F0-9]{6}|[a-fA-F0-9]{3})$/g;\n\nconst colorCode = \"#ABC123\";\n\nif (hexColorRegex.test(colorCode)) {\n  gs.info(\"Valid hexadecimal color\");\n} \nelse {\n  gs.info(\"Invalid hexadecimal color\");\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/IP Address Validation/README.md",
    "content": "This snippet extracts IPv4 and IPv6 addresses from free text. For single-value validation, see `validateIPInput.js` and `Validate IPv6 Address/script.js`.\n\nThe regex in `getIP4OrIPV6address.js` finds both IPv4 and IPv6 addresses within arbitrary text content.\n\nIPv6 coverage includes:\n- Full addresses like `2001:0db8:85a3:0000:0000:8a2e:0370:7334`\n- Compressed forms like `fe80::1` (`::` for omitted zeros)\n- IPv4-embedded forms like `::ffff:192.168.1.1`\n\nIPv4 validation now strictly enforces each octet to be in the range 0–255.\n\nValid IPv4 examples:\n\n- 192.168.1.1\n- 127.0.0.1\n- 0.0.0.0\n- 255.255.255.255\n- 1.2.3.4\n\nInvalid IPv4 examples (correctly rejected by the regex):\n\n- 256.256.256.256\n- 999.999.999.999\n- 1.2.3\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/IP Address Validation/getIP4OrIPV6address.js",
    "content": "// Extracts IPv4 and IPv6 addresses from arbitrary text content\n// For single-value validation, use validateIPInput.js or Validate IPv6 Address/script.js\nextractIPAddresses: function(text) {\n        var ipv4 = \"(?:25[0-5]|2[0-4]\\\\d|1?\\\\d?\\\\d)(?:\\\\.(?:25[0-5]|2[0-4]\\\\d|1?\\\\d?\\\\d)){3}\";\n        var ipv6 = \"(\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){7}[A-Fa-f0-9]{1,4}|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,7}:|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,6}:[A-Fa-f0-9]{1,4}|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,5}(?::[A-Fa-f0-9]{1,4}){1,2}|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,4}(?::[A-Fa-f0-9]{1,4}){1,3}|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,3}(?::[A-Fa-f0-9]{1,4}){1,4}|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,2}(?::[A-Fa-f0-9]{1,4}){1,5}|\"+\n            \"[A-Fa-f0-9]{1,4}:(?:(?::[A-Fa-f0-9]{1,4}){1,6})|\"+\n            \":(?:(?::[A-Fa-f0-9]{1,4}){1,7}|:)|\"+\n            \"fe80:(?::[A-Fa-f0-9]{0,4}){0,4}%[0-9A-Za-z]{1,}|\"+\n            \"::(?:ffff(?::0{1,4}){0,1}:){0,1}(?:\"+\n                \"(?:25[0-5]|2[0-4]\\\\d|1?\\\\d?\\\\d)(?:\\\\.(?:25[0-5]|2[0-4]\\\\d|1?\\\\d?\\\\d)){3}\"+\n            \")|\"+\n            \"(?:[A-Fa-f0-9]{1,4}:){1,4}:(?:\"+\n                \"(?:25[0-5]|2[0-4]\\\\d|1?\\\\d?\\\\d)(?:\\\\.(?:25[0-5]|2[0-4]\\\\d|1?\\\\d?\\\\d)){3}\"+\n            \")\"+\n        \")\";\n        var ipRegex = new RegExp(\"\\\\b(?:\" + ipv4 + \"|\" + ipv6 + \")\\\\b\",\"g\");\n        var matches = text.match(ipRegex);\n        return matches;\n    },\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/IP Address Validation/validateIPInput.js",
    "content": "(\\b25[0-5]|\\b2[0-4][0-9]|\\b[01]?[0-9][0-9]?)(\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/ISBN Validator/README.md",
    "content": "# ISBN Validator Regular Expression\n\nThis JavaScript module provides a regular expression for validating International Standard Book Numbers (ISBNs). It supports both ISBN-10 and ISBN-13 formats, including variations with or without hyphens and spaces.\n\n## Features\n\n- Validates ISBN-10 and ISBN-13 formats\n- Supports ISBNs with or without hyphens/spaces\n- Accepts ISBNs with or without the \"ISBN\" prefix\n\n## Usage\n\n```javascript\nconst isbnRegex = /^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$/;\n\nfunction validateISBN(isbn) {\n  return isbnRegex.test(isbn);\n}\n\n// Example usage:\nconsole.log(validateISBN('ISBN 978-0-596-52068-7')); // true\nconsole.log(validateISBN('0-596-52068-9')); // true\nconsole.log(validateISBN('0 512 52068 9')); // false\n```"
  },
  {
    "path": "Specialized Areas/Regular Expressions/ISBN Validator/validateISBN.js",
    "content": "// ISBN Validator\n// This regex validates both ISBN-10 and ISBN-13 formats\n\nconst isbnRegex = /^(?:ISBN(?:-1[03])?:? )?(?=[0-9X]{10}$|(?=(?:[0-9]+[- ]){3})[- 0-9X]{13}$|97[89][0-9]{10}$|(?=(?:[0-9]+[- ]){4})[- 0-9]{17}$)(?:97[89][- ]?)?[0-9]{1,5}[- ]?[0-9]+[- ]?[0-9]+[- ]?[0-9X]$/;\n\nfunction validateISBN(isbn) {\n  return isbnRegex.test(isbn);\n}\n\n// Example usage:\nconsole.log(validateISBN('ISBN 978-0-596-52068-7')); // true\nconsole.log(validateISBN('0-596-52068-9')); // true\nconsole.log(validateISBN('0 512 52068 9')); // false"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Img Tag Regex validator/README.md",
    "content": "**Regex Pattern**\n1. <img : looks for <img in text \n2. \\w : looks for any word character (equivalent to [a-zA-Z0-9_])\n3. \\W : looks for any non-word character (equivalent to [^a-zA-Z0-9_])\n4. '>' : looks for character >\n\n**How to use**\n1. Run this query in background/Fix scripts.\n2. The info message will return articles having images. This is very useful information when there are broken images in articles after movement between instances or tools.\n3. This can be further enhanced to replace image src if required.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Img Tag Regex validator/script.js",
    "content": "/*\nThis code will search for img tag in kb articles.\nRegex Patterns:\n<img : looks for <img in text \n\\w : looks for any word character (equivalent to [a-zA-Z0-9_])\n\\W looks for any non-word character (equivalent to [^a-zA-Z0-9_])\n> : looks for character >\n*/\nvar kbArt = new GlideRecord('kb_knowledge');\nkbArt.addEncodedQuery('workflow_state=published'); // encoded get publiushed acticles.\nkbArt.query();\nwhile (kbArt.next()) {\n    var imgRegex = /<img([\\w\\W]+?)>/; // Regex for checking img tag.\n    var regex = new RegExp(imgRegex); // forming regex using SN.\n    if (kbArt.getValue('text') && regex.test(kbArt.getValue('text'))) { // if article body is not empty and has image tag.\n        gs.info(\"Image is found in KB Article: \" + kbArt.getValue('number'));\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Indian Mobile Numbers/README.md",
    "content": "# Here's an example of how you can utilize regex in a Business Rule in ServiceNow:\n\n```\n(function executeRule(current, previous) {\nvar regex = /^(?:(?:\\+|0{0,2})91(\\s*[\\ -]\\s*)?|[0]?)?[789]\\d{9}|(\\d[ -]?){10}\\d$/;\nvar fieldValue = '919876543210'; // Replace with your field name\nif (fieldValue && !fieldValue.match(regex)) {\n        current.setAbortAction(true);\n        gs.info('Invalid mobile number format.');\n    }\n})(current, previous);\n```\n# Valid Scenarios \n+91-9883443344 <br />\n9883443344 <br />\n09883443344 <br />\n919883443344 <br />\n0919883443344 <br />\n+919883443344 <br />\n+91-9883443344 <br />\n\n# Invalid Scenarios\n\nWAQU9876567892 <br />\nABCD9876541212 <br />\n0226-895623124 <br />\n6589451235 <br />\n0924645236 <br />\n0222-895612 <br />\n098-8956124 <br />\n022-2413184 <br />\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Indian Mobile Numbers/code.js",
    "content": "var regex = /^(?:(?:\\+|0{0,2})91(\\s*[\\ -]\\s*)?|[0]?)?[789]\\d{9}|(\\d[ -]?){10}\\d$/;\nvar fieldValue = '919876543210'; // Replace with your field name\nif (fieldValue && fieldValue.match(regex)) {\n        gs.info('Mobile number is valid.');\n    } else {\n        gs.info('Invalid mobile number format.');\n    }\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Match URL's from ServiceNow domain/README.md",
    "content": "**Regular Expressions**\n\nRegular Expressions, which allows checking if URL belongs to ServiceNow domain. It is verifying all starting prefix like 'https://', 'http://' or just 'www' which belongs to servicenow.com.\n\nYou can easily change that regex to different domain, by changing 'servicenow' text in regSN variable.\n\n**Example effect of execution**\n\n ![Execution](ScreenShot_1.PNG)\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Match URL's from ServiceNow domain/script.js",
    "content": "//Regex to match URL's which are in ServiceNow domain \n//Matching pages from servicenow.com\n\n//Regex expression to match ServiceNow URL's\nvar regSN = /^(http(s):\\/\\/)?([^.]+\\.)?servicenow\\.com(\\/.*)$/;\n\n//Array with examples to test regex\nvar examples = ['http://www.servicenow.com/', 'https://servicenow.com/dev', 'https://developer.servicenow.com/blog.do?p=/post/hacktoberfest-2021/', 'www.servicenow.com/products/predictive-intelligence.html', 'https://github.com/ServiceNowDevProgram/code-snippets', 'hacktoberfest.digitalocean.com/profile'];\n\n//Testing different pages URL's to check if they are from ServiceNow domain\nfor (index in examples) {\n    if (regSN.test(examples[index])) {\n        gs.info('SN URL: ' + examples[index]);\n    } else {\n        gs.info('NOT SN URL: ' + examples[index]);\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Negative RegExp for Condition Builder/NegativeRegExExample.js",
    "content": "(function() {\n\n    // RegEx using a Negative Lookahead \n    var re = /^(?!(([A-Fa-f0-9]{2}[:-]){5}[A-Fa-f0-9]{2})$).*/;\n    var macAddresses = ['AE:FE:AA:CD:AF:0X', 'AE:FE:AA:CD:AF:02', 'Blah Blah'];\n\n    for (var i in macAddresses) {\n        if (re.test(macAddresses[i])) {\n            gs.debug('MAC address ' + macAddresses[i] + ' does NOT have a valid format');\n        }\n    }\n\n})();\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Negative RegExp for Condition Builder/README.md",
    "content": "# Negative regular expressions for Condition Builder\n\n## What problem does it solve?\n\nCertain condition builders (not all, unfortunately) come with a __matches regex__ operator. This is very handy to filter records based on complex rules applied to strings.\n\nUnfortunately, there is no __does not match regex__ operator and I would have needed this on several occasions.\n\n## Solution\n\nThe solution is to use a negative regular expression by leveraging the **?!** operator (called _Negative Lookahead_). So one needs to find the proper regex for what it should match, and then invert it with a Negative Lookahead.\n\nFor example, the following regex matches a well formed MAC address:\n```\n^(([A-Fa-f0-9]{2}[:-]){5}[A-Fa-f0-9]{2}).*$\n```\n\nWhereas this one matches anything that does NOT match a well formed MAC address:\n```\n^(?!(([A-Fa-f0-9]{2}[:-]){5}[A-Fa-f0-9]{2})$).*$\n```\n\nThe script in this example shows how to use this, but it's really in a condition builder that it will be useful. As matter of fact, a script can always reverse the logic (but the condition builder cannot). The script identifies all the entries in an array that do NOT have a well formed MAC address: note that it does not use a __false__ logic in the _if_, proving that the regex does revert the logic."
  },
  {
    "path": "Specialized Areas/Regular Expressions/PAN Card Validation Script/README.md",
    "content": "Description\nThis client script in ServiceNow is designed to validate the format of a PAN (Permanent Account Number) card during form submission.\nBy ensuring that the PAN card number entered adheres to the expected format, this script enhances data integrity and user experience. \nIf the entered PAN number is invalid, the script alerts the user, preventing form submission until a valid PAN number is provided.\n\nKey Features\n\n1. Format Validation: Checks PAN card format (5 letters, 4 digits, 1 letter).\n2. User Alerts: Provides immediate feedback for invalid entries.\n3. Submission Control: Prevents form submission with invalid PAN numbers.\n4. Customizable: Easily adjustable for different field names.\n5. Client-Side Efficiency: Quick validation without server delays.\n6. Data Integrity: Ensures only correctly formatted PAN numbers are accepted.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/PAN Card Validation Script/Script.js",
    "content": "function onSubmit() {\n    // Get the value from the PAN field (replace 'u_pan_number' with your actual field name)\n    var panNumber = g_form.getValue('u_pan_number');\n\n    // Regular expression for validating PAN card number\n    var panRegex = /^[A-Z]{5}[0-9]{4}[A-Z]{1}$/;\n\n    // Validate the PAN number\n    if (!panRegex.test(panNumber)) {\n        alert('Please enter a valid PAN card number.');\n        // Prevent the form from being submitted\n        return false;\n    }\n    \n    // Allow the form to be submitted\n    return true;\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Password Strength Checker/README.md",
    "content": "# Password Strength Checker\n\nThis code snippet checks the strength of a given password based on various criteria, including length, lowercase letters, uppercase letters, digits, and special characters.\n\n**Note: This code is written in ES2021, which is supported in scoped applications where it is enabled (default for new scopes since Utah).**\n\n## How to Use\n\n1. Copy and paste the `passwordStrength.js` code into your project.\n\n2. To check the strength of a password, call the `checkPasswordStrength` function with the password as the argument.\n\n   ```javascript\n   const password = \"YourPassword123!\";\n   const result = checkPasswordStrength(password);\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Password Strength Checker/passwordStrength.js",
    "content": "function checkPasswordStrength(password) {\n    // Define regular expressions for different password strength criteria\n    const lengthRegex = /.{8,}/; // At least 8 characters\n    const lowercaseRegex = /[a-z]/; // At least one lowercase letter\n    const uppercaseRegex = /[A-Z]/; // At least one uppercase letter\n    const digitRegex = /[0-9]/; // At least one digit\n    const specialCharacterRegex = /[!@#$%^&*()_+{}\\[\\]:;<>,.?~\\\\/-]/; // At least one special character\n\n    // Check each strength criteria\n    const isLengthValid = lengthRegex.test(password);\n    const hasLowercase = lowercaseRegex.test(password);\n    const hasUppercase = uppercaseRegex.test(password);\n    const hasDigit = digitRegex.test(password);\n    const hasSpecialCharacter = specialCharacterRegex.test(password);\n\n    // Calculate the overall strength score\n    let strength = 0;\n    if (isLengthValid) strength += 20;\n    if (hasLowercase) strength += 20;\n    if (hasUppercase) strength += 20;\n    if (hasDigit) strength += 20;\n    if (hasSpecialCharacter) strength += 20;\n\n    // Determine the password strength level\n    let strengthLevel;\n    if (strength < 60) {\n        strengthLevel = \"Weak\";\n    } else if (strength < 80) {\n        strengthLevel = \"Moderate\";\n    } else {\n        strengthLevel = \"Strong\";\n    }\n\n    return {\n        strengthLevel,\n        strengthScore: strength,\n    };\n}\n\n// Example usage\nconst password = \"MyP@ssw0rd\";\nconst result = checkPasswordStrength(password);\nconsole.log(`Password Strength: ${result.strengthLevel}`);\nconsole.log(`Strength Score: ${result.strengthScore}`);\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Poland country code/README.md",
    "content": "**Regular Expressions**\n\n Regular Expressions, which allows checking for Poland nine-digit Country code. Poland format starts with +48 and is followed by nine digits.\n\n**Example effect of execution**\n\n![Execution](ScreenShot_1.PNG)\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Poland country code/script.js",
    "content": "//Regex to match Poland nine-digit country code phone number\n//Format +48XXXXXXXXX\n\n//Regex expression to match Polish phone number\nvar regPL = /\\+48\\d{9}$/;\n\n//Array with examples to test regex\nvar examples = ['123456789', '+48123456789', '+481234567890', '+48123456'];\n\n//Testing different phone numbers to check if they are matching correct format\nfor (index in examples) {\n    if (regPL.test(examples[index])) {\n        gs.info('Number: ' + examples[index] + ' match regex.');\n    } else {\n        gs.info('Number: ' + examples[index] + ' not match regex.');\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Positive int with 2 decimals/Allow positive and decimal values.js",
    "content": "function onChange(control, oldValue, newValue, isLoading) {\n    if (isLoading || newValue == '') {\n        return;\n    }\n\n    // Regex: allows positive integers or decimals with up to two decimal places\n    var regex = /^[0-9]+(\\.\\d{1,2})?$/;\n\n    // Regex explanation:\n    // ^               : Start of string\n    // [0-9]+          : One or more digits (whole number part)\n    // (\\.\\d{1,2})?    : Optional decimal part\n    //   \\.            : Literal decimal point\n    //   \\d{1,2}       : One or two digits after the decimal\n    // $               : End of string\n    // This pattern matches positive integers or decimals with up to two decimal places\n\n    // Replace 'amount' with the actual field name this script is attached to\n    var fieldName = \"amount\";\n\n    if (!regex.test(newValue) || newValue <= 0) {\n        g_form.clearValue(fieldName);\n        g_form.showFieldMsg(fieldName, 'Please enter a value greater than zero. Decimals are allowed (up to two places).', 'error');\n    }\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Positive int with 2 decimals/readme.md",
    "content": "## Numeric Field Validation Script\n\nThis script validates numeric input in a form. It is used to ensure that values entered in fields like amount or price are properly formatted.\n\n### Validation Rules\n\n- Only positive numbers are allowed  \n- Decimals are allowed, up to two digits  \n- Zero or negative values are not accepted  \n- Invalid input will clear the field and show an error message\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Regular-expression-for-alphanumeric-characters/AlphanumericCharacters.js",
    "content": "// Alphanumeric regex pattern\nvar alphanumericPattern = /^[a-zA-Z0-9]*$/;\n\n// Function to validate if a string is alphanumeric\nfunction isAlphanumeric(str) {\n    return alphanumericPattern.test(str);\n}\n\n// Example usage\nvar examples = [\n    \"abc123\",    // Valid\n    \"ABC\",       // Valid\n    \"123\",       // Valid\n    \"abc123!\",   // Invalid (contains '!')\n    \"hello world\", // Invalid (contains space)\n    \"123-456\",   // Invalid (contains '-')\n];\n\n// Test the examples using a for loop\nfor (var i = 0; i < examples.length; i++) {\n    var example = examples[i];\n    if (isAlphanumeric(example)) {\n        gs.print(example+\" is a valid alphanumeric string.\");\n    } else {\n        gs.print(example+ \" is NOT a valid alphanumeric string.\");\n    }\n}\n\n/*\nwhen you run this code, it will output:\n*** Script: abc123 is a valid alphanumeric string.\n*** Script: ABC is a valid alphanumeric string.\n*** Script: 123 is a valid alphanumeric string.\n*** Script: abc123! is NOT a valid alphanumeric string.\n*** Script: hello world is NOT a valid alphanumeric string.\n*** Script: 123-456 is NOT a valid alphanumeric string.\n*/\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Regular-expression-for-alphanumeric-characters/README.md",
    "content": "# Alphanumeric String Validator\n\nThis project provides a simple JavaScript function to validate whether a given string is alphanumeric (i.e., contains only letters and numbers).\n\n## Table of Contents\n\n- [Features](#features)\n- [Usage](#usage)\n- [Validation Logic](#validation-logic)\n- [Examples](#examples)\n- [How to Run](#how-to-run)\n- [Contributing](#contributing)\n- [License](#license)\n\n## Features\n\n- Validates strings to ensure they contain only letters (a-z, A-Z) and numbers (0-9).\n- Returns a boolean indicating the validity of the string.\n\n## Usage\n\nYou can use the `isAlphanumeric` function to check if a string is alphanumeric.\n\n### Function Signature\n\n```javascript\nfunction isAlphanumeric(str)\n```\n\n### Parameters\n\n- `str` (string): The string to be validated.\n\n### Returns\n\n- `boolean`: `true` if the string is alphanumeric, `false` otherwise.\n\n## Validation Logic\n\nThe validation is performed using a regular expression:\n\n```javascript\nvar alphanumericPattern = /^[a-zA-Z0-9]*$/;\n```\n\nThis pattern checks that the string contains only letters and numbers.\n\n## Examples\n\nHere are some example strings and their validation results:\n\n```javascript\nvar examples = [\n    \"abc123\",    // Valid\n    \"ABC\",       // Valid\n    \"123\",       // Valid\n    \"abc123!\",   // Invalid (contains '!')\n    \"hello world\", // Invalid (contains space)\n    \"123-456\",   // Invalid (contains '-')\n];\n```\n\n### Test Output\n\nWhen running the provided examples, the output will be:\n\n```\nabc123 is a valid alphanumeric string.\nABC is a valid alphanumeric string.\n123 is a valid alphanumeric string.\nabc123! is NOT a valid alphanumeric string.\nhello world is NOT a valid alphanumeric string.\n123-456 is NOT a valid alphanumeric string.\n```\n\n## How to Run\n\n1. Clone this repository to your local machine:\n\n   ```bash\n   git clone https://github.com/yourusername/alphanumeric-validator.git\n   ```\n\n2. Open your JavaScript environment (e.g., browser console, Node.js).\n\n3. Copy and paste the code into the console or run it in your Node.js application.\n\n## Contributing\n\nContributions are welcome! If you have suggestions or improvements, please create a pull request or open an issue.\n\n## License\n\nThis project is licensed under the MIT License. See the LICENSE file for details.\n\n---\n\nFeel free to modify this README to fit your project's style or requirements!\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Remove Extra Spaces/README.md",
    "content": "Accepts a string and removes unnecessary spaces!"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Remove Extra Spaces/index.js",
    "content": "const input = \"  hello w !  my name is  kartike  singh  \";\n\nconst fixed = input.replace(/^\\s+|\\s+$|\\s+[!\\.,\\;]+/g, c => c.trim()).replace(/\\s\\s+/g, \" \");\n\n    console.log(`\"${fixed}\"`)"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Remove all HTML Tags/README.md",
    "content": "Will accept a html and remove all the html tags and give a string from it.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Remove all HTML Tags/Remove all html tags.js",
    "content": "var html  = '<div id=\"someid\"><span>Hello World</span></div>'; // insert your html content\nvar outputString = html.replace(/<\\/?[^>]+(>|$)/g, \"\"); // output will a string without the HTML tags\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Remove newline and carriage return/README.md",
    "content": "Will accept a string and remove all newline (\\n) and carriage return (\\r) characters\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Remove newline and carriage return/remove cr and nl.js",
    "content": "var inputString  = \"\";                                      // insert your input string here\nvar outputString = inputString.replace(/[\\r\\n]/g, '');      // the processed string is returned in the outputString variable\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/SSN Formatting/Index.js",
    "content": "//Regex To match ssn formats XXXXXXXXX or XXX-XX-XXXX\nvar ssn = /^(?:\\d{3}-\\d{2}-\\d{4}|\\d{9})$/;\n//Example Input\nvar input = '123-12-1234';\n//Boolean variable to dictate if you want hyphens or not   \nvar includeHyphen = true;\n//Return value to avoid multiple different returns\nvar retval = '';\n//If it matches the format of XXXXXXXXX or XXX-XX-XXXX\nif(ssn.test(input)){\n    //If the result will have hyphens and input already includes them\n    if (includeHyphen && input.includes('-')){\n        //Hyphens are already in place\n        retval = input;\n    }\n    //Else if the result will have hyphens and the input did not include them\n    else if (includeHyphen && !(input.includes('-'))){\n        //Adds hyphens in the format of an SSN i.e. XXX-XX-XXXX\n        retval = input.replace(/(\\d{3})(\\d{2})(\\d{4})/, \"$1-$2-$3\");\n    }\n    //The returned value will not have hyphens included\n    else if (!(includeHyphen)){\n        //Removes all hyphens in the input\n        reval = input.replace('-','');\n    }\n}\n//Else the input is not in the form of a legal ssn \nelse{\n    console.log(\"'\"+ input + \"' is not a legal 9-digit SSN\");\n}\n\nreturn retval;"
  },
  {
    "path": "Specialized Areas/Regular Expressions/SSN Formatting/README.md",
    "content": "This script will both check if a given string is an SSN (9 numerical characters with out hyphens or 12 with hyphens in the 4th and 7th positions) and then return a formatted version of the string as per your choice including or excluding hyphens via the \"includeHyphens\" variable. If used is servicenow this could be instead mapped to a field rather than set in the script but allows for a more programmically approach to the solution. If the input does not match the form of an SSN (see above) it will console log out that it's not a valid SSN. This includes cases where only one of the 2 hyphens are present (i.e. XXX-XXXXXX or XXXXX-XXXX). This could be expanded to check if the input is in one of those two forms and correct for it without failing but I've chosen to leave the script in this form."
  },
  {
    "path": "Specialized Areas/Regular Expressions/UK Country Code/README.md",
    "content": "# UK Phone Number Validator\n\n## Problem statement\nRegular Expressions, which allows checking for UK 10 digit phone numbers. UK format starts with the country code +44 followed by a space, 7 indicates a mobile number, and XXX XXXXXX are placeholders for the remaining digits. \nThe format supported is +44 7XXX XXXXXX. The validation is performed using a regular expression to ensure the phone number adheres to this format.\n\n## Regex code explanation\n\n```js\nconst regex = /^\\+44\\s7\\d{3}\\s\\d{6}$/;\n```\n\n> ^: Matches the start of the string. <br/>\n\n> \\+44 matches the literal characters +44.\n\n> \\s matches a space.\n\n> 7 matches the digit 7 (indicating a mobile number).\n\n> \\d{3} matches exactly three digits.\n\n> \\s matches another space.\n\n> \\d{6} matches exactly six digits.\n\n> $ asserts the position at the end of the string.\n\n\n## Valid Phone numbers Example\n\n- +44 7102 656764\n- +44 7252 968235\n- +44 7392 628731\n- +44 7580 159076\n\n## Invalid Phone numbers Example\n\n- +44 758089 159076\n- +44 7580 29076\n- +44 7580 5629076\n- +447580562907\n- +44 7580 5AR907\n\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/UK Country Code/script.js",
    "content": "//Regex to match UK 10 digit country code phone number\n\n//Format +44 7XXX XXXXXX\n\n//Regex expression to match UK phone number format +44 7XXX XXXXXX\nconst regex = /^\\+44\\s7\\d{3}\\s\\d{6}$/;\n\n// Function to check if a phone number matches the UK format\nfunction isUK(phoneNumber) {\n  // Test the phone number against the regular expression\n  return regex.test(phoneNumber);\n}\n\n// Example phone number to test\nconst phoneNumber = \"+44 7102 656764\"; // Replace with an UK phone number\n\n// Check if the phone number matches the UK format and log the result\nif (isUK(phoneNumber)) {\n  console.log(`Is UK phone number`);\n} else {\n  console.log(`Is not UK phone number`);\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/URL Validation/README.md",
    "content": "# Validate URL using Regular Expression\nValidating URLs using regular expressions in JavaScript involves crafting patterns to match URL formats, ensuring they adhere to standards like scheme, domain, and optional path or query parameters. This regex pattern verifies the structure and components of a URL string for validity.\n\n## Description\nThe `isValidURL` function checks if a given string is a valid URL format using a regular expression. It ensures that the input string matches the pattern of typical web URLs, covering variations with \"http\", \"https\", and \"www\".\n\n## Usage\nThis function is useful for validating URLs before processing or storing them in your application.\n\n## Regex code explanation\n\n```js\nconst regex = /(https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})/gi;\n```\n\n> /: Start of the regex.\n\n> https?: Matches \"http\" or \"https\" (the s? makes the s optional).\n\n> :\\/\\/: Matches \"://\".\n\n> (?:www\\.|(?!www)): A non-capturing group that matches either \"www.\" or ensures \"www\" is not present.\n\n> [a-zA-Z0-9]: Matches any alphanumeric character.\n\n> [a-zA-Z0-9-]+: Matches one or more alphanumeric characters or hyphens.\n\n> [a-zA-Z0-9]: Matches any alphanumeric character.\n\n> \\.: Matches a period.\n\n> [^\\s]{2,}: Matches two or more characters that are not whitespace.\n\n> |: OR operator to match different patterns.\n\n> www\\.: Matches \"www.\".\n\n> [a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]: Matches a pattern similar to the above.\n\n> \\.: Matches a period.\n\n> [^\\s]{2,}: Matches two or more characters that are not whitespace.\n\n> |: OR operator to match different patterns.\n\n> https?: Matches \"http\" or \"https\" (the s? makes the s optional).\n\n> :\\/\\/: Matches \"://\".\n\n> (?:www\\.|(?!www)): A non-capturing group that matches either \"www.\" or ensures \"www\" is not present.\n\n> [a-zA-Z0-9]+: Matches one or more alphanumeric characters.\n\n> \\.: Matches a period.\n\n> [^\\s]{2,}: Matches two or more characters that are not whitespace.\n\n> |: OR operator to match different patterns.\n\n> www\\.: Matches \"www.\".\n\n> [a-zA-Z0-9]+\\.[^\\s]{2,}: Matches a pattern similar to the above.\n\n> /: End of the regex.\n\n> gi: Global match (match all occurrences) and case-insensitive match.\n\nSo, this regex essentially matches different patterns of URLs, covering variations with \"http\", \"https\", \"www\", and domain names.\n\n## Examples\n\n### Example 1\n```javascript\nconst url = \"www.servicenow.com\";\n```\nThis will output: `Valid URL`.\n\n### Example 2\n```javascript\nconst invalidUrl = \"servicenow@com\";\n```\nThis will output: `Invalid URL`.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/URL Validation/validateURL.js",
    "content": "//Validating URLs using regular expressions\n\n// Regular expression to match URLs\nconst regex = /(https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|www\\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\\.[^\\s]{2,}|https?:\\/\\/(?:www\\.|(?!www))[a-zA-Z0-9]+\\.[^\\s]{2,}|www\\.[a-zA-Z0-9]+\\.[^\\s]{2,})/gi;\n\n// Function to check if a URL is valid using the regular expression\nfunction isValidURL(url) {\n  // Test the URL against the regex\n  return regex.test(url);\n}\n\n// Example URL string\nconst url= \"www.servicenow.com\"; // Replace with an URL\n\n// Check if the URL matches the format and log the result\nif (isValidURL(url)) {\n  console.log(`Valid URL`);\n} else {\n  console.log(`Invalid URL`);\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Username validation/README.md",
    "content": "## Strong Username Validation Script\nThis script validates a username entered in a ServiceNow catalog item form. It prevents form submission if the username does not meet the required format.\n\n### Validation Criteria\nThe username must start with a letter (a–z or A–Z).\nIt must be at least 6 characters long.\nIt can only contain letters and numbers.\n\n## Usage\nAdd the script as an onSubmit client script in the catalog item. If the username is invalid, an error message is shown and the form is not submitted.\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Username validation/Script.js",
    "content": "function onSubmit() {\n    // Get the username value from the field\n    var username = g_form.getValue('username'); // Change 'username' to your field name\n\n    // Define the regex pattern for a strong username\n    var usernamePattern = /^[a-zA-Z][a-zA-Z0-9]{5,}$/;\n    \n    // Regex explanation:\n    // ^           : Start of string\n    // [a-zA-Z]    : First character must be a letter\n    // [a-zA-Z0-9] : Remaining characters can be letters or digits\n    // {5,}        : At least 5 more characters (total minimum length = 6)\n    // $           : End of string\n\n\n    // Validate the username against the pattern\n    if (!usernamePattern.test(username)) {\n        // Display an error message if validation fails\n        g_form.showFieldMsg('username', 'Username must start with a letter, be at least 6 characters long, and contain only letters and numbers.', 'error');\n        return false; // Prevent form submission\n    }\n\n    return true; // Allow form submission if validation passes\n}\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Validate IPv6 Address/README.md",
    "content": "# IPv6 Address Validator\n\nThis snippet validates **IPv6 addresses** in both full and compressed formats using JavaScript regex.\n\n### Features\n- Supports full and shortened IPv6 formats (`::` compression)\n- Validates loopback (`::1`) and link-local (`fe80::`) addresses\n- Rejects invalid hex groups and multiple `::`\n\n"
  },
  {
    "path": "Specialized Areas/Regular Expressions/Validate IPv6 Address/script.js",
    "content": "// IPv6 Address Validator\n// This regex validates both full and compressed IPv6 address formats, including shorthand \"::\" notation.\n\nconst ipv6Regex =\n  /^(?:(([0-9A-Fa-f]{1,4}:){7}[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,7}:)|(([0-9A-Fa-f]{1,4}:){1,6}:[0-9A-Fa-f]{1,4})|(([0-9A-Fa-f]{1,4}:){1,5}(:[0-9A-Fa-f]{1,4}){1,2})|(([0-9A-Fa-f]{1,4}:){1,4}(:[0-9A-Fa-f]{1,4}){1,3})|(([0-9A-Fa-f]{1,4}:){1,3}(:[0-9A-Fa-f]{1,4}){1,4})|(([0-9A-Fa-f]{1,4}:){1,2}(:[0-9A-Fa-f]{1,4}){1,5})|([0-9A-Fa-f]{1,4}:((:[0-9A-Fa-f]{1,4}){1,6}))|(:((:[0-9A-Fa-f]{1,4}){1,7}|:)))(%.+)?$/;\n\nfunction validateIPv6(address) {\n  return ipv6Regex.test(address);\n}\n\n// Example usage:\nconsole.log(validateIPv6(\"2001:0db8:85a3:0000:0000:8a2e:0370:7334\")); // true (full)\nconsole.log(validateIPv6(\"2001:db8:85a3::8a2e:370:7334\")); // true (compressed)\nconsole.log(validateIPv6(\"::1\")); // true (loopback)\nconsole.log(validateIPv6(\"fe80::\")); // true (link-local)\nconsole.log(validateIPv6(\"1234:5678:9abc:def0:1234:5678:9abc:defg\")); // false (invalid hex)\nconsole.log(validateIPv6(\"2001::85a3::7334\")); // false (double compression)\n"
  },
  {
    "path": "Specialized Areas/Resource Management/Resource Capacity And Availability Viewer (Daily Basis)/README.md",
    "content": "This script will output a User's Capacity and Availability hourly breakdown of a on a daily basis for a given range.\nThis is normally not easily accessible in the platform as there are only Weekly and Monthly aggregates in the platform (resource_aggregate_weekly and resource_aggregate_monthly).\n\nIf there is a Schedule change, the Capacity and Availablility aggregates may be out of sync as well. \nSo, this script will show you what the breakdown is based on the current Schedule.\n\nOf course, you need \"Resource Management\" installed to use this which typically comes as part of the \"PPM Standard\" plugin.\n\nTo use it, call the \"getUserCapacity\" function. You can run this in the [System Defintion > Scripts - Background] module\n\n- Argument 1 can be a Sys ID or a User ID from the sys_user table\n- Argument 2 is the Start Date of the range you care about in YYYY-MM-DD format\n- Argument 3 is the End Date of the range you care about in YYYY-MM-DD format\n\nExample calls:\n\ngetUserCapacity(\"ae44946c835cba90cac7a5e0deaad38f\", \"2025-01-01\", \"2025-02-28\");\n\nOR\n\ngetUserCapacity(\"abel.tuter\", \"2026-10-01\", \"2026-10-31\");\n\nExample output (left number is Capacity, right number is Availability):\n\n2025-01-01: 6 | 6\n2025-01-02: 6 | 6\n2025-01-03: 6 | 6\n2025-01-04: 0 | 0\n2025-01-05: 0 | 0\n2025-01-06: 6 | 5\n2025-01-07: 6 | 6\n2025-01-08: 6 | 6\n2025-01-09: 6 | 6\n2025-01-10: 6 | 6\n2025-01-11: 0 | 0\n2025-01-12: 0 | 0\n2025-01-13: 6 | 5\n2025-01-14: 6 | 6\n2025-01-15: 6 | 6\n2025-01-16: 6 | 6\n2025-01-17: 6 | 6\n2025-01-18: 0 | 0\n2025-01-19: 0 | 0\n2025-01-20: 6 | 5\n2025-01-21: 6 | 6\n2025-01-22: 6 | 6\n2025-01-23: 6 | 6\n2025-01-24: 6 | 6\n2025-01-25: 0 | 0\n2025-01-26: 0 | 0\n2025-01-27: 6 | 5\n2025-01-28: 6 | 6\n2025-01-29: 6 | 6\n2025-01-30: 6 | 6\n2025-01-31: 6 | 6\n\nBreakdown for Month --- Capacity: 138 --- Availability: 134\n\n2025-02-01: 0 | 0\n2025-02-02: 0 | 0\n2025-02-03: 6 | 6\n2025-02-04: 6 | 6\n2025-02-05: 6 | 6\n2025-02-06: 6 | 6\n2025-02-07: 6 | 6\n2025-02-08: 0 | 0\n2025-02-09: 0 | 0\n2025-02-10: 6 | 6\n2025-02-11: 6 | 6\n2025-02-12: 6 | 6\n2025-02-13: 6 | 6\n2025-02-14: 6 | 6\n2025-02-15: 0 | 0\n2025-02-16: 0 | 0\n2025-02-17: 6 | 6\n2025-02-18: 6 | 6\n2025-02-19: 6 | 6\n2025-02-20: 6 | 6\n2025-02-21: 6 | 6\n2025-02-22: 0 | 0\n2025-02-23: 0 | 0\n2025-02-24: 6 | 6\n2025-02-25: 6 | 6\n2025-02-26: 6 | 6\n2025-02-27: 6 | 6\n2025-02-28: 6 | 6\n\nBreakdown for Month --- Capacity: 120 --- Availability: 120\n\nTotal days in range: 59\nTotal working days in range: 43\nTotal capacity: 258\nTotal availability: 254\n"
  },
  {
    "path": "Specialized Areas/Resource Management/Resource Capacity And Availability Viewer (Daily Basis)/ResourceCapacityAndAvailabilityViewerDailyBasis.js",
    "content": "// Set first argument to either a User ID or a Sys ID from the \"sys_user\" table\n// Set second and thirt arguments to Start Date and End Date respectively in format YYYY-MM-DD\ngetUserCapacity(\"ae44946c835cba90cac7a5e0deaad38f\", \"2025-01-01\", \"2025-02-28\");\n\nfunction getUserCapacity(userIdOrSysId, startDateString, endDateString) {\n\tvar grUser = new GlideRecord(\"sys_user\");\n\tif(grUser.get(userIdOrSysId)) {\n\t\tvar startDate = new GlideDate(startDateString);\n\t\tvar endDate = new GlideDate(endDateString);\n\n\t\tvar capacityApi = new RMCapacityAPI(startDateString, endDateString);\n\t\tvar capacityObj = capacityApi.getCapacityForUser(grUser.getUniqueValue());\n\t\tvar availabilityObj = capacityApi.getAvailabilityForUser(grUser.getUniqueValue());\n\t\tvar totalDaysInRange = GlideDateTime.subtract(startDate, endDate).getDayPart() + 1;\n\t\tvar totalWorkingDaysInRange = 0;\n\n\t\tvar dateIterator = GlideDateTime(startDateString);\n\n\t\tvar currentMonth = dateIterator.getMonthUTC();\n\t\tvar monthlyTotalCapacity = 0;\n\t\tvar monthlyTotalAvailability = 0;\n\t\tvar totalCapacity = 0;\n\t\tvar totalAvailability = 0;\n\n\t\tfor (var i = 0; i < capacityObj.length; i++) {\n\n\t\t\tvar month = dateIterator.getMonthUTC();\n\t\t\tif (month != currentMonth) {\n\t\t\t\tgs.info(\"\");\n\t\t\t\tgs.info(\"Breakdown for Month --- Capacity: \" + monthlyTotalCapacity + \" --- Availability: \" + monthlyTotalAvailability);\n\t\t\t\tgs.info(\"\");\n\t\t\t\tcurrentMonth = month;\n\t\t\t\tmonthlyTotalCapacity = 0;\n\t\t\t\tmonthlyTotalAvailability = 0;\n\t\t\t}\n      \n\t\t\tgs.info(dateIterator.getDate() + \": \" + capacityObj[i] + \" | \" + availabilityObj[i]);\n\t\t\tdateIterator.add(86400000);\n\t\t\tif (capacityObj[i] > 0) {\n\t\t\t\ttotalWorkingDaysInRange++;\n\t\t\t\ttotalCapacity += capacityObj[i];\n\t\t\t\ttotalAvailability += availabilityObj[i];\n\t\t\t\tmonthlyTotalCapacity += capacityObj[i];\n\t\t\t\tmonthlyTotalAvailability += availabilityObj[i];\n\t\t\t}\n\t\t}\n\t\t\n\t\tgs.info(\"\");\n\t\tgs.info(\"Breakdown for Month --- Capacity: \" + monthlyTotalCapacity + \" --- Availability: \" + monthlyTotalAvailability);\n\t\tgs.info(\"\");\n\n\t\tgs.info(\"Total days in range: \" + totalDaysInRange);\n\t\tgs.info(\"Total working days in range: \" + totalWorkingDaysInRange);\n\t\tgs.info(\"Total capacity: \" + totalCapacity);\n\t\tgs.info(\"Total availability: \" + totalAvailability);\n\t}\n}\n"
  },
  {
    "path": "Specialized Areas/Styles/Add Background Color to a field/README.md",
    "content": "Open a record. Right click on any field label and select configure style.\nA list will open with all the styles applied on that field.\nClick on New button to create your custom style.\nEnter the code of style.css in Style field of style and done.\nYou can use this code on any field in any table.\n"
  },
  {
    "path": "Specialized Areas/Styles/Add Background Color to a field/style.css",
    "content": "background-color:blue;\nfont-size:12px;\ncolor:white;\n"
  },
  {
    "path": "Specialized Areas/Styles/Add attachment icon-list view/README.md",
    "content": "The above two files scripts are useful to put styles on your field in list view. Below are the steps to achieve this:\n1. Open a record. Right click on any field label and select configure style.\n2. A list will open with all the styles applied on that field.\n3. Click on New button to create your custom style.\n4. Enter the code of value.js in Value field of style.\n5. Enter the code of style.css in Style field of style.\n6. Done. Voila.. You can now have an attachment icon present on the <chosen> field in list view if there is any attachement present in that record.\n7. You can use this code on any field in any table.\n"
  },
  {
    "path": "Specialized Areas/Styles/Add attachment icon-list view/style.css",
    "content": "// Add this css to your style field to make the attachement icon visible in list view in front of your record.\nbackground-image: url('images/attachment.gif');  // This path and icon name you can get from two places: 1. image_picker.do and 2. db_image.list\n            background-repeat: no-repeat;\n            background-position: 98% 5px;\n            padding-right: 30px;\n"
  },
  {
    "path": "Specialized Areas/Styles/Add attachment icon-list view/value.js",
    "content": "// Write this in Value field of your style to check if there is any attachment present to that record.\njavascript:current.hasAttachments()\n"
  },
  {
    "path": "Specialized Areas/Styles/Change text color of a field/README.md",
    "content": "Open a record. Right click on any field label and select configure style. A list will open with all the styles applied on that field. Click on New button to create your custom style. Enter the code of style.css in Style field of style and done. You can use this code on any field in any table. This will give it a dark blue center aligned text on a white background.\n"
  },
  {
    "path": "Specialized Areas/Styles/Change text color of a field/style.css",
    "content": "background-color:white;\nfont-size:16px;\ntext-align: center;\ncolor:#000066;\n"
  },
  {
    "path": "Specialized Areas/Styles/Hide MRVS Buttons/README.md",
    "content": "# Stylesheet for hiding MRVS buttons\n\nThis stylesheet can be used to hide buttons within a multi-row variable set on both the platform and portal.\n\nTo use, create a Rich Text Label and paste the code into that.\n"
  },
  {
    "path": "Specialized Areas/Styles/Hide MRVS Buttons/styles.html",
    "content": "/*\n * Use this in Rich Text Label to hide all the buttons\n */\n<style>\n\t/* hide MRVS buttons on portal */\n\tsp-sc-multi-row-element .btn.btn-primary.m-r,   /* hide the add button */\n\tsp-sc-multi-row-element .btn.btn-default,\t\t/* hide the add button */\n\tsp-sc-multi-row-element .fa-pencil,\t\t\t\t/* hide the edit buttons */\n\tsp-sc-multi-row-element .fa-close,\t\t\t\t/* hide the delete row buttons */\n\tsp-sc-multi-row-element .table > tbody > tr > td:first-child,\t/* hide the first column cell */\n\tsp-sc-multi-row-element .table > caption + thead > tr:first-child > th:first-child\t/* hide the first column header */\n\t{\n\t\tdisplay:none;\n\t}\n\n  /* hide MRVS buttons on platform */\n\t.sc-table-variable-buttons .btn.btn-primary, \t/* hide the add button */\n  .sc-table-variable-buttons .btn.btn-default, \t/* hide the add button */\n  .sc-multi-row-actions .icon-edit,\t\t\t\t/* hide the edit buttons */\n  .sc-multi-row-actions .icon-cross,\t\t\t\t/* hide the delete row buttons */\n  .sc-table-variable-header:first-child,\t\t\t/* hide the first column header */\n  .sc-multi-row-actions\t\t\t\t\t\t\t/* hide the first column cell */\n\t{\n\t\tdisplay: none;\n\t}\n</style>\n"
  },
  {
    "path": "Specialized Areas/Styles/ServiceNow Custom Style/README.md",
    "content": "\n\n\n# ServiceNow Custom Styles\n\nThis README explains how to apply the custom styles defined in `style.css` to your ServiceNow instance.\n\n## How to Apply Styles\n\n### 1. Create a UI Script:\n\n- Navigate to **System UI > UI Scripts** in your ServiceNow instance.\n- Create a new **UI Script**.\n- Name it something like `CustomStyles`.\n- Set **Global** to `true` if you want these styles available globally.\n\n### 2. Add the CSS:\n\n- Copy the contents of `style.css` into the UI Script's \"Script\" field.\n- Wrap the CSS in a `<style>` tag:\n\n```javascript\n(function() {\n  var style = document.createElement('style');\n  style.innerHTML = `\n    // Paste the entire contents of style.css here\n  `;\n  document.head.appendChild(style);\n})();\n```\n\n### 3. Apply to Specific Pages:\n\n- To apply these styles to specific pages, add the UI Script to the required pages or modules.\n\n## Usage Examples\n\n### Custom Button:\n\n```html\n<button class=\"custom-button\">Click me</button>\n```\n\n### Custom Input:\n\n```html\n<input type=\"text\" class=\"custom-input\" placeholder=\"Enter text...\">\n```\n\n### Custom Table:\n\n```html\n<table class=\"custom-table\">\n  <tr>\n    <th>Header 1</th>\n    <th>Header 2</th>\n  </tr>\n  <tr>\n    <td>Row 1, Cell 1</td>\n    <td>Row 1, Cell 2</td>\n  </tr>\n</table>\n```\n\n### Custom Alert:\n\n```html\n<div class=\"custom-alert\">This is an alert message!</div>\n<div class=\"custom-alert success\">This is a success message!</div>\n<div class=\"custom-alert info\">This is an info message!</div>\n<div class=\"custom-alert warning\">This is a warning message!</div>\n```\n\n## Important Note\n\n- Remember to test these styles in a non-production environment before applying them to your live ServiceNow instance.\n```\n\nThis Markdown version organizes the instructions and code snippets effectively for easy reading and application.\n"
  },
  {
    "path": "Specialized Areas/Styles/ServiceNow Custom Style/style.css",
    "content": "/* Custom button styles */\n.custom-button {\n    background-color: #4CAF50;\n    border: none;\n    color: white;\n    padding: 10px 20px;\n    text-align: center;\n    text-decoration: none;\n    display: inline-block;\n    font-size: 16px;\n    margin: 4px 2px;\n    cursor: pointer;\n    border-radius: 4px;\n    transition: background-color 0.3s;\n}\n\n.custom-button:hover {\n    background-color: #45a049;\n}\n\n/* Custom form input styles */\n.custom-input {\n    width: 100%;\n    padding: 12px 20px;\n    margin: 8px 0;\n    display: inline-block;\n    border: 1px solid #ccc;\n    border-radius: 4px;\n    box-sizing: border-box;\n}\n\n/* Custom table styles */\n.custom-table {\n    border-collapse: collapse;\n    width: 100%;\n}\n\n.custom-table th, .custom-table td {\n    border: 1px solid #ddd;\n    padding: 8px;\n    text-align: left;\n}\n\n.custom-table tr:nth-child(even) {\n    background-color: #f2f2f2;\n}\n\n.custom-table th {\n    padding-top: 12px;\n    padding-bottom: 12px;\n    background-color: #4CAF50;\n    color: white;\n}\n\n/* Custom alert styles */\n.custom-alert {\n    padding: 20px;\n    background-color: #f44336;\n    color: white;\n    margin-bottom: 15px;\n    border-radius: 4px;\n}\n\n.custom-alert.success {\n    background-color: #4CAF50;\n}\n\n.custom-alert.info {\n    background-color: #2196F3;\n}\n\n.custom-alert.warning {\n    background-color: #ff9800;\n}\n"
  },
  {
    "path": "Specialized Areas/Styles/Zoom Catalog Image on Hover/CSS.js",
    "content": "/*\nZoom - in catalog item image on mouse hover.\n*/\n.catalog-item-image:hover{ /* OOB catalog item image class */\n  transform: scale(2); /* 200% zoom */\n}\n/*\nZoom - in catalog item short description on mouse hover.\n*/\n.sc-cat-item-short-description:hover{ /* OOB catalog item short description class */\n  transform: scale(1.2) translateX(15rem); /* 120% zoom and 15rem right shift*/\n}\n"
  },
  {
    "path": "Specialized Areas/Styles/Zoom Catalog Image on Hover/README.md",
    "content": "**How to use**\n1. Add this css code to \"Page specific css\" on \"sc_cat_item\" page for Service Portal.\n2.  Add this css code to \"Page specific css\" on \"esc_sc_cat_item\" page for Employee centre portal\n3.  This can also be used in css includes at theme level.\n4. If your portal is customised and different pages are used for catalog items through Page Route Maps, use this in your pages.\n\n**Use Case**\n1. Many product catalogs have small images, organisations don't want to change that at backend but want to give the user the ability to see a zoomed-in image.\n"
  },
  {
    "path": "_config.yml",
    "content": "# GitHub Pages Configuration for ServiceNow Code Snippets\n\n# Site settings\ntitle: \"ServiceNow Code Snippets\"\ndescription: \"Community-driven collection of ServiceNow development code examples and utilities from the ServiceNow Developer Program.\"\nurl: \"https://servicenowdevprogram.github.io\"\nbaseurl: \"/code-snippets\"\n\n# Build settings\nmarkdown: kramdown\nhighlighter: rouge\ntheme: minima\n\n# Plugins\nplugins:\n  - jekyll-feed\n  - jekyll-sitemap\n  - jekyll-seo-tag\n\n# Collections for organizing content\ncollections:\n  core-apis:\n    output: true\n    permalink: /:collection/:name/\n  server-side:\n    output: true\n    permalink: /:collection/:name/\n  client-side:\n    output: true\n    permalink: /:collection/:name/\n  modern-dev:\n    output: true\n    permalink: /:collection/:name/\n  integration:\n    output: true\n    permalink: /:collection/:name/\n  specialized:\n    output: true\n    permalink: /:collection/:name/\n\n# Default layouts\ndefaults:\n  - scope:\n      path: \"\"\n      type: \"pages\"\n    values:\n      layout: \"default\"\n  - scope:\n      path: \"\"\n      type: \"posts\"\n    values:\n      layout: \"post\"\n\n# Exclude files from processing\nexclude:\n  - README.md\n  - CONTRIBUTING.md\n  - CLAUDE.md\n  - .gitignore\n  - .github/\n  - node_modules/\n  - vendor/\n\n# Include files\ninclude:\n  - _pages\n  - assets\n\n# SEO settings\nlogo: /assets/images/servicenow-logo.png\nsocial:\n  name: ServiceNow Developer Program\n  links:\n    - https://github.com/ServiceNowDevProgram\n    - https://developer.servicenow.com\n\n# GitHub settings\ngithub:\n  repository_url: https://github.com/ServiceNowDevProgram/code-snippets"
  },
  {
    "path": "assets/css/custom.css",
    "content": "/**\n * Custom CSS for ServiceNow Code Snippets GitHub Pages\n * Additional styles and overrides\n */\n\n/* Custom scrollbar */\n::-webkit-scrollbar {\n    width: 8px;\n    height: 8px;\n}\n\n::-webkit-scrollbar-track {\n    background: var(--bg-secondary);\n}\n\n::-webkit-scrollbar-thumb {\n    background: var(--border-color);\n    border-radius: 4px;\n}\n\n::-webkit-scrollbar-thumb:hover {\n    background: var(--text-muted);\n}\n\n/* Selection styling */\n::selection {\n    background: var(--primary-color);\n    color: white;\n}\n\n::-moz-selection {\n    background: var(--primary-color);\n    color: white;\n}\n\n/* Focus styles for accessibility */\n*:focus {\n    outline: 2px solid var(--primary-color);\n    outline-offset: 2px;\n}\n\n.skip-link {\n    position: absolute;\n    top: -40px;\n    left: 6px;\n    background: var(--primary-color);\n    color: white;\n    padding: 8px;\n    text-decoration: none;\n    transition: top 0.3s;\n    z-index: 1000;\n}\n\n.skip-link:focus {\n    top: 6px;\n}\n\n/* Animation classes */\n@keyframes fadeIn {\n    from {\n        opacity: 0;\n        transform: translateY(20px);\n    }\n    to {\n        opacity: 1;\n        transform: translateY(0);\n    }\n}\n\n@keyframes slideIn {\n    from {\n        transform: translateX(-100%);\n    }\n    to {\n        transform: translateX(0);\n    }\n}\n\n@keyframes pulse {\n    0%, 100% {\n        transform: scale(1);\n    }\n    50% {\n        transform: scale(1.05);\n    }\n}\n\n.fade-in {\n    animation: fadeIn 0.6s ease-out;\n}\n\n.slide-in {\n    animation: slideIn 0.5s ease-out;\n}\n\n.pulse-hover:hover {\n    animation: pulse 0.3s ease-in-out;\n}\n\n/* Loading states */\n.loading-skeleton {\n    background: linear-gradient(90deg, var(--bg-secondary) 25%, var(--border-light) 50%, var(--bg-secondary) 75%);\n    background-size: 200% 100%;\n    animation: loading 1.5s infinite;\n}\n\n@keyframes loading {\n    0% {\n        background-position: 200% 0;\n    }\n    100% {\n        background-position: -200% 0;\n    }\n}\n\n/* Code syntax highlighting enhancements */\npre[class*=\"language-\"] {\n    position: relative;\n}\n\npre[class*=\"language-\"]::before {\n    content: attr(data-language);\n    position: absolute;\n    top: 0;\n    right: 0;\n    background: var(--primary-color);\n    color: white;\n    padding: 0.25rem 0.5rem;\n    font-size: 0.75rem;\n    text-transform: uppercase;\n    border-bottom-left-radius: 4px;\n}\n\n/* Copy code button */\n.code-copy-btn {\n    position: absolute;\n    top: 0.5rem;\n    right: 0.5rem;\n    background: var(--bg-primary);\n    border: 1px solid var(--border-color);\n    border-radius: 4px;\n    padding: 0.25rem 0.5rem;\n    font-size: 0.75rem;\n    cursor: pointer;\n    opacity: 0;\n    transition: opacity 0.2s ease;\n}\n\n.code-preview:hover .code-copy-btn {\n    opacity: 1;\n}\n\n.code-copy-btn:hover {\n    background: var(--bg-secondary);\n}\n\n/* Search enhancements */\n.search-suggestions {\n    position: absolute;\n    top: 100%;\n    left: 0;\n    right: 0;\n    background: var(--bg-primary);\n    border: 1px solid var(--border-color);\n    border-top: none;\n    border-radius: 0 0 var(--border-radius) var(--border-radius);\n    box-shadow: var(--shadow-md);\n    max-height: 300px;\n    overflow-y: auto;\n    z-index: 100;\n}\n\n.search-suggestion {\n    padding: 0.75rem 1rem;\n    border-bottom: 1px solid var(--border-light);\n    cursor: pointer;\n    transition: background-color 0.2s ease;\n}\n\n.search-suggestion:hover {\n    background: var(--bg-secondary);\n}\n\n.search-suggestion:last-child {\n    border-bottom: none;\n}\n\n.suggestion-title {\n    font-weight: 500;\n    color: var(--text-primary);\n}\n\n.suggestion-category {\n    font-size: 0.8rem;\n    color: var(--text-secondary);\n}\n\n/* Mobile enhancements */\n@media (max-width: 768px) {\n    .mobile-hidden {\n        display: none;\n    }\n    \n    .mobile-menu-toggle {\n        display: block;\n        background: none;\n        border: none;\n        font-size: 1.5rem;\n        color: var(--text-primary);\n        cursor: pointer;\n    }\n    \n    .nav-content {\n        position: relative;\n    }\n    \n    .mobile-menu {\n        position: absolute;\n        top: 100%;\n        left: 0;\n        right: 0;\n        background: var(--bg-primary);\n        border: 1px solid var(--border-color);\n        border-radius: var(--border-radius);\n        box-shadow: var(--shadow-md);\n        display: none;\n        z-index: 100;\n    }\n    \n    .mobile-menu.open {\n        display: block;\n        animation: slideIn 0.3s ease-out;\n    }\n    \n    .mobile-menu-item {\n        padding: 1rem;\n        border-bottom: 1px solid var(--border-light);\n        text-decoration: none;\n        color: var(--text-primary);\n        display: block;\n    }\n    \n    .mobile-menu-item:last-child {\n        border-bottom: none;\n    }\n    \n    .mobile-menu-item:hover {\n        background: var(--bg-secondary);\n    }\n}\n\n/* Print styles */\n@media print {\n    .header,\n    .nav,\n    .search-section,\n    .footer {\n        display: none;\n    }\n    \n    .main-content {\n        max-width: none;\n        padding: 0;\n    }\n    \n    .category-card {\n        break-inside: avoid;\n        page-break-inside: avoid;\n    }\n    \n    .code-preview {\n        break-inside: avoid;\n        page-break-inside: avoid;\n    }\n    \n    pre {\n        white-space: pre-wrap;\n        word-wrap: break-word;\n    }\n}\n\n/* High contrast mode support */\n@media (prefers-contrast: high) {\n    :root {\n        --border-color: #000000;\n        --border-light: #333333;\n        --shadow-sm: 0 2px 4px rgba(0, 0, 0, 0.8);\n        --shadow-md: 0 4px 8px rgba(0, 0, 0, 0.8);\n        --shadow-lg: 0 8px 16px rgba(0, 0, 0, 0.8);\n    }\n    \n    .category-card {\n        border-width: 2px;\n    }\n    \n    .category-card:hover {\n        border-width: 3px;\n    }\n}\n\n/* Reduced motion support */\n@media (prefers-reduced-motion: reduce) {\n    *,\n    *::before,\n    *::after {\n        animation-duration: 0.01ms !important;\n        animation-iteration-count: 1 !important;\n        transition-duration: 0.01ms !important;\n        scroll-behavior: auto !important;\n    }\n}\n\n/* Utility classes */\n.text-center { text-align: center; }\n.text-left { text-align: left; }\n.text-right { text-align: right; }\n\n.hidden { display: none; }\n.visible { display: block; }\n\n.mt-1 { margin-top: 0.25rem; }\n.mt-2 { margin-top: 0.5rem; }\n.mt-3 { margin-top: 1rem; }\n.mt-4 { margin-top: 2rem; }\n\n.mb-1 { margin-bottom: 0.25rem; }\n.mb-2 { margin-bottom: 0.5rem; }\n.mb-3 { margin-bottom: 1rem; }\n.mb-4 { margin-bottom: 2rem; }\n\n.p-1 { padding: 0.25rem; }\n.p-2 { padding: 0.5rem; }\n.p-3 { padding: 1rem; }\n.p-4 { padding: 2rem; }\n\n.rounded { border-radius: var(--border-radius); }\n.shadow { box-shadow: var(--shadow-md); }\n.border { border: 1px solid var(--border-color); }"
  },
  {
    "path": "assets/footer.js",
    "content": "// Shared Footer Component\n// This component renders a consistent footer across all pages\n\nclass Footer {\n    constructor(isRootPage = true) {\n        this.isRootPage = isRootPage;\n        this.baseUrl = isRootPage ? '' : '../';\n    }\n\n    getCSS() {\n        return `\n        /* Footer */\n        .footer {\n            background: var(--bg-secondary);\n            border-top: 1px solid var(--border-light);\n            padding: 3rem 0 2rem;\n        }\n\n        .footer-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n        }\n\n        .footer-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n            gap: 2rem;\n            margin-bottom: 2rem;\n        }\n\n        .footer-section h3 {\n            font-weight: 600;\n            margin-bottom: 1rem;\n            color: var(--text-primary);\n        }\n\n        .footer-section ul {\n            list-style: none;\n        }\n\n        .footer-section ul li {\n            margin-bottom: 0.5rem;\n        }\n\n        .footer-section ul li a {\n            color: var(--text-secondary);\n            text-decoration: none;\n            transition: color 0.2s ease;\n        }\n\n        .footer-section ul li a:hover {\n            color: var(--primary-color);\n        }\n\n        .footer-bottom {\n            border-top: 1px solid var(--border-light);\n            padding-top: 2rem;\n            text-align: center;\n            color: var(--text-muted);\n        }\n        `;\n    }\n\n    getHTML() {\n        const pagesPath = this.isRootPage ? 'pages/' : '';\n\n        return `\n        <footer class=\"footer\">\n            <div class=\"footer-content\">\n                <div class=\"footer-grid\">\n                    <div class=\"footer-section\">\n                        <h3>ServiceNow Code Snippets</h3>\n                        <ul>\n                            <li><a href=\"${pagesPath}core-apis.html\">Core ServiceNow APIs</a></li>\n                            <li><a href=\"${pagesPath}server-side-components.html\">Server-Side Components</a></li>\n                            <li><a href=\"${pagesPath}client-side-components.html\">Client-Side Components</a></li>\n                            <li><a href=\"${pagesPath}modern-development.html\">Modern Development</a></li>\n                            <li><a href=\"${pagesPath}integration.html\">Integration</a></li>\n                            <li><a href=\"${pagesPath}specialized-areas.html\">Specialized Areas</a></li>\n                        </ul>\n                    </div>\n\n                    <div class=\"footer-section\">\n                        <h3>Community</h3>\n                        <ul>\n                            <li><a href=\"https://github.com/ServiceNowDevProgram/code-snippets\" target=\"_blank\">GitHub Repository</a></li>\n                            <li><a href=\"https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md\" target=\"_blank\">Contributing Guide</a></li>\n                            <li><a href=\"https://github.com/ServiceNowDevProgram/code-snippets/issues\" target=\"_blank\">Report Issues</a></li>\n                            <li><a href=\"https://sndevs.slack.com/\" target=\"_blank\">sndevs Slack</a></li>\n                            <li><a href=\"https://github.com/ServiceNowDevProgram/code-snippets/blob/main/README.md\" target=\"_blank\">README</a></li>\n                            <li><a href=\"https://github.com/ServiceNowDevProgram/Hacktoberfest\" target=\"_blank\">Hacktoberfest repo</a></li>\n                        </ul>\n                    </div>\n\n                    <div class=\"footer-section\">\n                        <h3>ServiceNow</h3>\n                        <ul>\n                            <li><a href=\"https://developer.servicenow.com/\" target=\"_blank\">Developer Program</a></li>\n                            <li><a href=\"https://docs.servicenow.com/\" target=\"_blank\">Documentation</a></li>\n                            <li><a href=\"https://www.servicenow.com/community\" target=\"_blank\">Community</a></li>\n                        </ul>\n                    </div>\n                </div>\n\n                <div class=\"footer-bottom\">\n                    <p>2025 - Hosted by the ServiceNow Developer Program</p>\n                    <p>This repository is provided as-is, without warranties. Use at your own risk.</p>\n                </div>\n            </div>\n        </footer>\n        `;\n    }\n\n    render() {\n        // Add CSS if not already added\n        if (!document.getElementById('footer-styles')) {\n            const style = document.createElement('style');\n            style.id = 'footer-styles';\n            style.textContent = this.getCSS();\n            document.head.appendChild(style);\n        }\n\n        // Create footer element\n        const footerContainer = document.createElement('div');\n        footerContainer.innerHTML = this.getHTML();\n\n        return footerContainer.firstElementChild;\n    }\n\n    // Static method to easily add footer to any page\n    static addToPage(isRootPage = true) {\n        const footer = new Footer(isRootPage);\n        const footerElement = footer.render();\n\n        // Add footer to the page\n        document.body.appendChild(footerElement);\n\n        return footerElement;\n    }\n}\n\n// Auto-initialize if script is loaded with data-auto-init\ndocument.addEventListener('DOMContentLoaded', function() {\n    const script = document.querySelector('script[src*=\"footer.js\"]');\n    if (script && script.hasAttribute('data-auto-init')) {\n        const isRootPage = script.getAttribute('data-root-page') === 'true';\n        Footer.addToPage(isRootPage);\n    }\n});\n\n// Make Footer available globally\nwindow.Footer = Footer;"
  },
  {
    "path": "assets/js/site.js",
    "content": "/**\n * ServiceNow Code Snippets Site JavaScript\n * Handles dynamic content loading, search, and navigation\n */\n\nclass CodeSnippetsSite {\n    constructor() {\n        this.categories = {\n            'core-servicenow-apis': {\n                title: 'Core ServiceNow APIs',\n                description: 'Essential ServiceNow JavaScript APIs and classes',\n                icon: 'fas fa-code',\n                subcategories: [\n                    'GlideRecord', 'GlideAjax', 'GlideSystem', 'GlideDate', \n                    'GlideDateTime', 'GlideElement', 'GlideFilter', 'GlideAggregate',\n                    'GlideHTTPRequest', 'GlideModal', 'GlideQuery', 'GlideTableDescriptor'\n                ]\n            },\n            'server-side-components': {\n                title: 'Server-Side Components',\n                description: 'Server-executed code and components',\n                icon: 'fas fa-server',\n                subcategories: [\n                    'Background Scripts', 'Business Rules', 'Script Includes',\n                    'Script Actions', 'Scheduled Jobs', 'Transform Map Scripts',\n                    'Server Side', 'Inbound Actions', 'Processors'\n                ]\n            },\n            'client-side-components': {\n                title: 'Client-Side Components',\n                description: 'Browser-executed code and UI components',\n                icon: 'fas fa-desktop',\n                subcategories: [\n                    'Client Scripts', 'Catalog Client Script', 'UI Actions',\n                    'UI Scripts', 'UI Pages', 'UI Macros', 'UX Client Scripts',\n                    'UX Client Script Include', 'UX Data Broker Transform'\n                ]\n            },\n            'modern-development': {\n                title: 'Modern Development',\n                description: 'Modern ServiceNow frameworks and approaches',\n                icon: 'fas fa-rocket',\n                subcategories: [\n                    'Service Portal', 'Service Portal Widgets', 'NOW Experience',\n                    'GraphQL', 'ECMASCript 2021'\n                ]\n            },\n            'integration': {\n                title: 'Integration',\n                description: 'External systems and data exchange',\n                icon: 'fas fa-plug',\n                subcategories: [\n                    'Integration', 'RESTMessageV2', 'Import Set API',\n                    'Scripted REST Api', 'Mail Scripts', 'MIDServer', 'Attachments'\n                ]\n            },\n            'specialized-areas': {\n                title: 'Specialized Areas',\n                description: 'Domain-specific functionality',\n                icon: 'fas fa-cogs',\n                subcategories: [\n                    'CMDB', 'ITOM', 'Performance Analytics', 'ATF Steps',\n                    'Agile Development', 'Advanced Conditions', 'Browser Bookmarklets',\n                    'Browser Utilities', 'Dynamic Filters', 'Fix scripts',\n                    'Flow Actions', 'Formula Builder', 'Notifications',\n                    'On-Call Calendar', 'Record Producer', 'Regular Expressions', 'Styles'\n                ]\n            }\n        };\n\n        this.init();\n    }\n\n    init() {\n        this.setupSearch();\n        this.setupNavigation();\n        this.loadAnalytics();\n    }\n\n    setupSearch() {\n        const searchInput = document.getElementById('search-input');\n        if (searchInput) {\n            let searchTimeout;\n            searchInput.addEventListener('input', (e) => {\n                clearTimeout(searchTimeout);\n                searchTimeout = setTimeout(() => {\n                    this.performSearch(e.target.value);\n                }, 300);\n            });\n        }\n    }\n\n    performSearch(query) {\n        if (!query || query.length < 2) {\n            this.clearSearchResults();\n            return;\n        }\n\n        // Simple search implementation\n        // In a real implementation, this would use a search index or GitHub API\n        const results = this.searchCategories(query.toLowerCase());\n        this.displaySearchResults(results);\n    }\n\n    searchCategories(query) {\n        const results = [];\n        \n        Object.entries(this.categories).forEach(([key, category]) => {\n            // Search in category title and description\n            if (category.title.toLowerCase().includes(query) || \n                category.description.toLowerCase().includes(query)) {\n                results.push({\n                    type: 'category',\n                    key: key,\n                    title: category.title,\n                    description: category.description,\n                    icon: category.icon\n                });\n            }\n\n            // Search in subcategories\n            category.subcategories.forEach(subcategory => {\n                if (subcategory.toLowerCase().includes(query)) {\n                    results.push({\n                        type: 'subcategory',\n                        category: category.title,\n                        title: subcategory,\n                        path: `${category.title}/${subcategory}`\n                    });\n                }\n            });\n        });\n\n        return results;\n    }\n\n    displaySearchResults(results) {\n        // This would create a search results overlay\n        console.log('Search results:', results);\n        \n        // For now, just log the results\n        // In a full implementation, this would show a dropdown or overlay\n        if (results.length > 0) {\n            console.log(`Found ${results.length} results`);\n        } else {\n            console.log('No results found');\n        }\n    }\n\n    clearSearchResults() {\n        // Clear any search result displays\n        console.log('Clearing search results');\n    }\n\n    setupNavigation() {\n        // Add click handlers for category cards\n        document.querySelectorAll('.category-card').forEach(card => {\n            card.addEventListener('click', (e) => {\n                const onclick = card.getAttribute('onclick');\n                if (onclick && onclick.includes('navigateToCategory')) {\n                    const categoryMatch = onclick.match(/navigateToCategory\\('([^']+)'/);\n                    if (categoryMatch) {\n                        this.navigateToCategory(categoryMatch[1]);\n                    }\n                }\n            });\n        });\n    }\n\n    navigateToCategory(categoryKey) {\n        const category = this.categories[categoryKey];\n        if (category) {\n            // For GitHub Pages, navigate to the category page\n            const categoryMap = {\n                'core-servicenow-apis': 'Core%20ServiceNow%20APIs',\n                'server-side-components': 'Server-Side%20Components',\n                'client-side-components': 'Client-Side%20Components',\n                'modern-development': 'Modern%20Development',\n                'integration': 'Integration',\n                'specialized-areas': 'Specialized%20Areas'\n            };\n            \n            const githubUrl = `https://github.com/ServiceNowDevProgram/code-snippets/tree/main/${categoryMap[categoryKey]}`;\n            window.open(githubUrl, '_blank');\n        }\n    }\n\n    loadAnalytics() {\n        // Simple analytics tracking\n        this.trackPageView();\n        this.updateStats();\n    }\n\n    trackPageView() {\n        // Track page view (placeholder for real analytics)\n        console.log('Page view tracked:', {\n            page: window.location.pathname,\n            timestamp: new Date().toISOString(),\n            userAgent: navigator.userAgent\n        });\n    }\n\n    updateStats() {\n        // Update dynamic statistics\n        const totalSnippets = document.getElementById('total-snippets');\n        const totalContributors = document.getElementById('total-contributors');\n\n        if (totalSnippets) {\n            // These could be fetched from GitHub API\n            setTimeout(() => {\n                totalSnippets.textContent = '950+';\n            }, 1000);\n        }\n\n        if (totalContributors) {\n            setTimeout(() => {\n                totalContributors.textContent = 'Community';\n            }, 1200);\n        }\n    }\n\n    // Utility methods\n    static createElementWithClasses(tag, classes, content = '') {\n        const element = document.createElement(tag);\n        if (classes) {\n            element.className = classes;\n        }\n        if (content) {\n            element.textContent = content;\n        }\n        return element;\n    }\n\n    static formatDate(date) {\n        return new Intl.DateTimeFormat('en-US', {\n            year: 'numeric',\n            month: 'long',\n            day: 'numeric'\n        }).format(new Date(date));\n    }\n\n    static debounce(func, wait) {\n        let timeout;\n        return function executedFunction(...args) {\n            const later = () => {\n                clearTimeout(timeout);\n                func(...args);\n            };\n            clearTimeout(timeout);\n            timeout = setTimeout(later, wait);\n        };\n    }\n}\n\n// GitHub API helper class\nclass GitHubAPI {\n    constructor() {\n        this.baseUrl = 'https://api.github.com';\n        this.repo = 'ServiceNowDevProgram/code-snippets';\n    }\n\n    async getRepoStats() {\n        try {\n            const response = await fetch(`${this.baseUrl}/repos/${this.repo}`);\n            const data = await response.json();\n            return {\n                stars: data.stargazers_count,\n                forks: data.forks_count,\n                size: data.size,\n                language: data.language,\n                updatedAt: data.updated_at\n            };\n        } catch (error) {\n            console.error('Error fetching repo stats:', error);\n            return null;\n        }\n    }\n\n    async getContributors() {\n        try {\n            const response = await fetch(`${this.baseUrl}/repos/${this.repo}/contributors`);\n            const data = await response.json();\n            return data.map(contributor => ({\n                login: contributor.login,\n                contributions: contributor.contributions,\n                avatar: contributor.avatar_url,\n                profile: contributor.html_url\n            }));\n        } catch (error) {\n            console.error('Error fetching contributors:', error);\n            return [];\n        }\n    }\n\n    async getDirectoryContents(path = '') {\n        try {\n            const response = await fetch(`${this.baseUrl}/repos/${this.repo}/contents/${path}`);\n            const data = await response.json();\n            return data.filter(item => item.type === 'dir').map(dir => ({\n                name: dir.name,\n                path: dir.path,\n                type: dir.type\n            }));\n        } catch (error) {\n            console.error('Error fetching directory contents:', error);\n            return [];\n        }\n    }\n}\n\n// Initialize the site when DOM is loaded\ndocument.addEventListener('DOMContentLoaded', function() {\n    window.codeSnippetsSite = new CodeSnippetsSite();\n    window.githubAPI = new GitHubAPI();\n    \n    // Initialize any additional features\n    initializeFeatures();\n});\n\nfunction initializeFeatures() {\n    // Dark mode toggle (if implemented)\n    initializeDarkMode();\n    \n    // Smooth scrolling\n    initializeSmoothScrolling();\n    \n    // External link handling\n    initializeExternalLinks();\n}\n\nfunction initializeDarkMode() {\n    // Placeholder for dark mode toggle\n    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');\n    \n    prefersDark.addEventListener('change', (e) => {\n        console.log('Color scheme changed to:', e.matches ? 'dark' : 'light');\n        // Update theme if needed\n    });\n}\n\nfunction initializeSmoothScrolling() {\n    document.querySelectorAll('a[href^=\"#\"]').forEach(anchor => {\n        anchor.addEventListener('click', function (e) {\n            e.preventDefault();\n            const target = document.querySelector(this.getAttribute('href'));\n            if (target) {\n                target.scrollIntoView({\n                    behavior: 'smooth',\n                    block: 'start'\n                });\n            }\n        });\n    });\n}\n\nfunction initializeExternalLinks() {\n    document.querySelectorAll('a[href^=\"http\"]').forEach(link => {\n        if (!link.getAttribute('target')) {\n            link.setAttribute('target', '_blank');\n            link.setAttribute('rel', 'noopener noreferrer');\n        }\n    });\n}\n\n// Export for use in other scripts\nif (typeof module !== 'undefined' && module.exports) {\n    module.exports = { CodeSnippetsSite, GitHubAPI };\n}"
  },
  {
    "path": "count-files.js",
    "content": "#!/usr/bin/env node\n\nconst fs = require('fs');\nconst path = require('path');\n\n// Folders to exclude from counting\nconst EXCLUDED_FOLDERS = new Set([\n    '.github',\n    '.git', \n    'node_modules',\n    '.vscode',\n    '.idea',\n    'assets'\n]);\n\n// Root files to exclude\nconst EXCLUDED_ROOT_FILES = new Set([\n    'README.md',\n    'CONTRIBUTING.md', \n    'CLAUDE.md',\n    'PAGES.md',\n    'LICENSE',\n    '.gitignore',\n    'package.json',\n    'package-lock.json',\n    '_config.yml',\n    'sitemap.xml',\n    'index.html',\n    'core-apis.html',\n    'server-side-components.html',\n    'client-side-components.html', \n    'modern-development.html',\n    'integration.html',\n    'specialized-areas.html',\n    'count-files.js'\n]);\n\n// File extensions to count\nconst COUNTED_EXTENSIONS = new Set([\n    '.js', '.ts', '.json', '.html', '.css', '.py', '.java', '.c', '.cpp', \n    '.cs', '.php', '.rb', '.go', '.rs', '.swift', '.kt', '.md', '.txt', \n    '.xml', '.sql', '.sh', '.bat', '.ps1', '.yml', '.yaml'\n]);\n\nfunction shouldCountFile(fileName, isRoot = false) {\n    // Exclude root-level config files\n    if (isRoot && EXCLUDED_ROOT_FILES.has(fileName)) {\n        return false;\n    }\n    \n    // Check if file has a counted extension\n    const ext = path.extname(fileName).toLowerCase();\n    return COUNTED_EXTENSIONS.has(ext);\n}\n\nfunction countFilesRecursively(dirPath, isRoot = true) {\n    let count = 0;\n    let folderCounts = {};\n    \n    try {\n        const items = fs.readdirSync(dirPath);\n        \n        for (const item of items) {\n            const itemPath = path.join(dirPath, item);\n            const stat = fs.statSync(itemPath);\n            \n            if (stat.isDirectory()) {\n                // Skip excluded folders\n                if (EXCLUDED_FOLDERS.has(item)) {\n                    console.log(`Excluded folder: ${item}`);\n                    continue;\n                }\n                \n                // Recursively count files in subdirectory\n                const subCount = countFilesRecursively(itemPath, false);\n                count += subCount;\n                folderCounts[item] = subCount;\n                \n                if (isRoot) {\n                    console.log(`${item}: ${subCount} files`);\n                }\n            } else if (stat.isFile()) {\n                // Count relevant files\n                if (shouldCountFile(item, isRoot)) {\n                    count++;\n                    if (isRoot) {\n                        console.log(`Root file counted: ${item}`);\n                    }\n                }\n            }\n        }\n    } catch (error) {\n        console.error(`Error reading directory ${dirPath}:`, error.message);\n    }\n    \n    return count;\n}\n\n// Count files starting from current directory\nconsole.log('Counting files in repository...');\nconsole.log('='.repeat(50));\n\nconst totalFiles = countFilesRecursively('.');\n\n// Round to nearest 100 and add + for marketing display\nconst roundedFiles = Math.floor(totalFiles / 100) * 100;\nconst displayCount = `${roundedFiles}+`;\n\nconsole.log('='.repeat(50));\nconsole.log(`Total files counted: ${totalFiles}`);\nconsole.log(`Rounded display count: ${displayCount}`);\n\n// Update the index.html file with the rounded count\nconst indexPath = './index.html';\nif (fs.existsSync(indexPath)) {\n    try {\n        let indexContent = fs.readFileSync(indexPath, 'utf8');\n        \n        // Replace the estimated number with the rounded count\n        const oldPattern = /totalFiles = \\d+;.*\\/\\/ Actual count from local files/g;\n        const newLine = `totalFiles = \"${displayCount}\"; // Rounded count from ${totalFiles} local files`;\n        \n        // Also handle the old pattern if it exists\n        const oldPattern2 = /totalFiles = \\d+;.*\\/\\/ Realistic estimate/g;\n        \n        indexContent = indexContent.replace(oldPattern, newLine);\n        indexContent = indexContent.replace(oldPattern2, newLine);\n        \n        // Update the textContent assignment to handle string instead of number\n        indexContent = indexContent.replace(\n            /snippetsElement\\.textContent = totalFiles\\.toLocaleString\\(\\);/g,\n            'snippetsElement.textContent = totalFiles;'\n        );\n        \n        fs.writeFileSync(indexPath, indexContent);\n        console.log(`Updated index.html with rounded count: ${displayCount} (from actual ${totalFiles})`);\n    } catch (error) {\n        console.error('Error updating index.html:', error.message);\n    }\n} else {\n    console.log('index.html not found - could not update automatically');\n}"
  },
  {
    "path": "index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>ServiceNow Code Snippets | Developer Program</title>\n    <meta name=\"description\" content=\"Community-driven collection of ServiceNow development code examples and utilities.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting     -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* CSS Variables for theming */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        /* Dark mode */\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        /* Reset and base styles */\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Header */\n        .header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 2rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .header h1 {\n            font-size: 3rem;\n            font-weight: 700;\n            margin-bottom: 0.5rem;\n            background: linear-gradient(45deg, #ffffff, #e3f2fd);\n            background-clip: text;\n            -webkit-background-clip: text;\n            -webkit-text-fill-color: transparent;\n        }\n\n        .header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            margin-bottom: 2rem;\n        }\n\n        .stats {\n            display: flex;\n            gap: 2rem;\n            flex-wrap: wrap;\n        }\n\n        .stat {\n            background: rgba(255, 255, 255, 0.1);\n            padding: 1rem;\n            border-radius: var(--border-radius);\n            backdrop-filter: blur(10px);\n            border: 1px solid rgba(255, 255, 255, 0.2);\n        }\n\n        .stat-number {\n            font-size: 2rem;\n            font-weight: 700;\n            display: block;\n        }\n\n        .stat-label {\n            font-size: 0.9rem;\n            opacity: 0.8;\n        }\n\n        /* Search */\n        .search-section {\n            background: var(--bg-secondary);\n            padding: 2rem 0;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .search-container {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n        }\n\n        .search-box {\n            position: relative;\n            max-width: 600px;\n            margin: 0 auto;\n        }\n\n        .search-input {\n            width: 100%;\n            padding: 1rem 1rem 1rem 3rem;\n            border: 2px solid var(--border-color);\n            border-radius: var(--border-radius);\n            font-size: 1rem;\n            background: var(--bg-primary);\n            color: var(--text-primary);\n            transition: all 0.2s ease;\n        }\n\n        .search-input:focus {\n            outline: none;\n            border-color: var(--primary-color);\n            box-shadow: 0 0 0 3px rgba(26, 115, 232, 0.1);\n        }\n\n        .search-icon {\n            position: absolute;\n            left: 1rem;\n            top: 50%;\n            transform: translateY(-50%);\n            color: var(--text-muted);\n        }\n\n        /* Categories Grid */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .section-title {\n            font-size: 2rem;\n            font-weight: 600;\n            margin-bottom: 1rem;\n            color: var(--text-primary);\n        }\n\n        .section-subtitle {\n            color: var(--text-secondary);\n            margin-bottom: 2rem;\n            font-size: 1.1rem;\n        }\n\n        .categories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(350px, 1fr));\n            gap: 2rem;\n            margin-bottom: 4rem;\n        }\n\n        .category-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            padding: 2rem;\n            transition: all 0.3s ease;\n            cursor: pointer;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .category-card:hover {\n            transform: translateY(-4px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .category-card::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            height: 4px;\n            background: linear-gradient(45deg, var(--primary-color), var(--secondary-color));\n        }\n\n        .category-icon {\n            width: 48px;\n            height: 48px;\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            margin-bottom: 1rem;\n            font-size: 1.5rem;\n            color: white;\n        }\n\n        .category-title {\n            font-size: 1.3rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .category-description {\n            color: var(--text-secondary);\n            margin-bottom: 1rem;\n            line-height: 1.5;\n        }\n\n        .category-stats {\n            display: flex;\n            gap: 1rem;\n            margin-bottom: 1rem;\n        }\n\n        .category-stat {\n            background: var(--bg-secondary);\n            padding: 0.5rem 1rem;\n            border-radius: 20px;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .category-examples {\n            font-size: 0.9rem;\n            color: var(--text-muted);\n        }\n\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .header h1 {\n                font-size: 2rem;\n            }\n            \n            .header-content {\n                padding: 0 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .categories-grid {\n                grid-template-columns: 1fr;\n                gap: 1rem;\n            }\n            \n            .stats {\n                justify-content: center;\n            }\n        }\n\n        /* Loading Animation */\n        .loading {\n            display: inline-block;\n            width: 20px;\n            height: 20px;\n            border: 3px solid rgba(255, 255, 255, 0.3);\n            border-radius: 50%;\n            border-top-color: white;\n            animation: spin 1s ease-in-out infinite;\n        }\n\n        .loading-text {\n            font-size: 1.5rem;\n            opacity: 0.8;\n            animation: pulse 2s ease-in-out infinite;\n        }\n\n        @keyframes spin {\n            to { transform: rotate(360deg); }\n        }\n\n        @keyframes pulse {\n            0%, 100% { opacity: 0.8; }\n            50% { opacity: 0.4; }\n        }\n\n        /* Search Results */\n        .search-results {\n            position: absolute;\n            top: 100%;\n            left: 0;\n            right: 0;\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            box-shadow: var(--shadow-lg);\n            max-height: 500px;\n            overflow-y: auto;\n            z-index: 1000;\n            display: none;\n        }\n\n        .search-results.show {\n            display: block;\n        }\n\n        .search-result-item {\n            padding: 1rem;\n            border-bottom: 1px solid var(--border-light);\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n        }\n\n        .search-result-item:hover,\n        .search-result-item.active {\n            background: var(--bg-secondary);\n        }\n\n        .search-result-item:last-child {\n            border-bottom: none;\n        }\n\n        .search-result-title {\n            font-weight: 600;\n            color: var(--text-primary);\n            margin-bottom: 0.25rem;\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n        }\n\n        .search-result-path {\n            font-size: 0.85rem;\n            color: var(--text-muted);\n            margin-bottom: 0.5rem;\n            font-family: var(--font-mono);\n        }\n\n        .search-result-snippet {\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n            line-height: 1.4;\n            font-family: var(--font-mono);\n            background: var(--bg-secondary);\n            padding: 0.5rem;\n            border-radius: 4px;\n            white-space: pre-wrap;\n            overflow: hidden;\n            max-height: 60px;\n        }\n\n        .search-highlight {\n            background: var(--accent-color);\n            color: var(--text-primary);\n            padding: 0 2px;\n            border-radius: 2px;\n            font-weight: 600;\n        }\n\n        .search-loading {\n            padding: 2rem;\n            text-align: center;\n            color: var(--text-muted);\n        }\n\n        .search-no-results {\n            padding: 2rem;\n            text-align: center;\n            color: var(--text-muted);\n        }\n\n        .search-error {\n            padding: 1rem;\n            background: rgba(234, 67, 53, 0.1);\n            color: var(--danger-color);\n            border-radius: var(--border-radius);\n            margin: 1rem;\n            text-align: center;\n        }\n\n        .file-type-badge {\n            background: var(--primary-color);\n            color: white;\n            padding: 0.2rem 0.5rem;\n            border-radius: 12px;\n            font-size: 0.7rem;\n            font-weight: 500;\n            text-transform: uppercase;\n        }\n\n        .search-stats {\n            padding: 0.75rem 1rem;\n            background: var(--bg-secondary);\n            border-bottom: 1px solid var(--border-light);\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        /* Utilities */\n        .sr-only {\n            position: absolute;\n            width: 1px;\n            height: 1px;\n            padding: 0;\n            margin: -1px;\n            overflow: hidden;\n            clip: rect(0, 0, 0, 0);\n            white-space: nowrap;\n            border: 0;\n        }\n    </style>\n</head>\n<body>\n    <!-- Header -->\n    <header class=\"header\">\n        <div class=\"header-content\">\n            <h1>ServiceNow Code Snippets</h1>\n            <p>Community-driven collection of ServiceNow development code examples and utilities.</p>\n            \n            <div class=\"stats\">\n                <div class=\"stat\">\n                    <span class=\"stat-number\" id=\"total-snippets\">900+</span>\n                    <span class=\"stat-label\">Code Snippets</span>\n                </div>\n                <div class=\"stat\">\n                    <span class=\"stat-number\">6</span>\n                    <span class=\"stat-label\">Categories</span>\n                </div>\n                <div class=\"stat\">\n                    <span class=\"stat-number\" id=\"total-contributors\">Community</span>\n                    <span class=\"stat-label\">Contributors</span>\n                </div>\n            </div>\n        </div>\n    </header>\n\n    <!-- Search Section -->\n    <section class=\"search-section\">\n        <div class=\"search-container\">\n            <div class=\"search-box\">\n                <i class=\"fas fa-search search-icon\"></i>\n                <input \n                    type=\"text\" \n                    class=\"search-input\" \n                    placeholder=\"Search for ServiceNow code snippets...\"\n                    id=\"search-input\"\n                    aria-label=\"Search code snippets\"\n                    autocomplete=\"off\"\n                >\n                <div class=\"search-results\" id=\"search-results\">\n                    <!-- Search results will be populated here -->\n                </div>\n            </div>\n        </div>\n    </section>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <h2 class=\"section-title\">Browse by Category</h2>\n        <p class=\"section-subtitle\">\n            Explore our organized collection of ServiceNow development code snippets, categorized by platform area and use case.\n        </p>\n\n        <div class=\"categories-grid\" id=\"categories-grid\">\n            <!-- Categories will be loaded dynamically -->\n            <div class=\"category-card\" onclick=\"navigateToCategory('core-servicenow-apis')\">\n                <div class=\"category-icon\">\n                    <i class=\"fas fa-code\"></i>\n                </div>\n                <h3 class=\"category-title\">Core ServiceNow APIs</h3>\n                <p class=\"category-description\">\n                    Essential ServiceNow JavaScript APIs and classes including GlideRecord, GlideAjax, GlideSystem, and other foundational APIs.\n                </p>\n                <div class=\"category-stats\">\n                    <span class=\"category-stat\">12 Sub-categories</span>\n                    <span class=\"category-stat\">200+ Examples</span>\n                </div>\n                <p class=\"category-examples\">\n                    GlideRecord, GlideAjax, GlideSystem, GlideDate, GlideDateTime...\n                </p>\n            </div>\n\n            <div class=\"category-card\" onclick=\"navigateToCategory('server-side-components')\">\n                <div class=\"category-icon\">\n                    <i class=\"fas fa-server\"></i>\n                </div>\n                <h3 class=\"category-title\">Server-Side Components</h3>\n                <p class=\"category-description\">\n                    Server-side code including Background Scripts, Business Rules, Script Includes, and other server-executed components.\n                </p>\n                <div class=\"category-stats\">\n                    <span class=\"category-stat\">9 Sub-categories</span>\n                    <span class=\"category-stat\">300+ Examples</span>\n                </div>\n                <p class=\"category-examples\">\n                    Background Scripts, Business Rules, Script Includes, Scheduled Jobs...\n                </p>\n            </div>\n\n            <div class=\"category-card\" onclick=\"navigateToCategory('client-side-components')\">\n                <div class=\"category-icon\">\n                    <i class=\"fas fa-desktop\"></i>\n                </div>\n                <h3 class=\"category-title\">Client-Side Components</h3>\n                <p class=\"category-description\">\n                    Client-side code including Client Scripts, Catalog Client Scripts, UI Actions, and UX framework components.\n                </p>\n                <div class=\"category-stats\">\n                    <span class=\"category-stat\">9 Sub-categories</span>\n                    <span class=\"category-stat\">150+ Examples</span>\n                </div>\n                <p class=\"category-examples\">\n                    Client Scripts, Catalog Client Scripts, UI Actions, UI Scripts...\n                </p>\n            </div>\n\n            <div class=\"category-card\" onclick=\"navigateToCategory('modern-development')\">\n                <div class=\"category-icon\">\n                    <i class=\"fas fa-rocket\"></i>\n                </div>\n                <h3 class=\"category-title\">Modern Development</h3>\n                <p class=\"category-description\">\n                    Modern ServiceNow development approaches including Service Portal, NOW Experience Framework, and GraphQL.\n                </p>\n                <div class=\"category-stats\">\n                    <span class=\"category-stat\">5 Sub-categories</span>\n                    <span class=\"category-stat\">80+ Examples</span>\n                </div>\n                <p class=\"category-examples\">\n                    Service Portal, NOW Experience, GraphQL, ECMAScript 2021...\n                </p>\n            </div>\n\n            <div class=\"category-card\" onclick=\"navigateToCategory('integration')\">\n                <div class=\"category-icon\">\n                    <i class=\"fas fa-plug\"></i>\n                </div>\n                <h3 class=\"category-title\">Integration</h3>\n                <p class=\"category-description\">\n                    External system integrations, data import/export utilities, RESTMessageV2 examples, and communication patterns.\n                </p>\n                <div class=\"category-stats\">\n                    <span class=\"category-stat\">7 Sub-categories</span>\n                    <span class=\"category-stat\">120+ Examples</span>\n                </div>\n                <p class=\"category-examples\">\n                    RESTMessageV2, Import Sets, Mail Scripts, MIDServer, Attachments...\n                </p>\n            </div>\n\n            <div class=\"category-card\" onclick=\"navigateToCategory('specialized-areas')\">\n                <div class=\"category-icon\">\n                    <i class=\"fas fa-cogs\"></i>\n                </div>\n                <h3 class=\"category-title\">Specialized Areas</h3>\n                <p class=\"category-description\">\n                    Domain-specific functionality including CMDB utilities, ITOM scripts, Performance Analytics, and testing frameworks.\n                </p>\n                <div class=\"category-stats\">\n                    <span class=\"category-stat\">18 Sub-categories</span>\n                    <span class=\"category-stat\">100+ Examples</span>\n                </div>\n                <p class=\"category-examples\">\n                    CMDB, ITOM, Performance Analytics, ATF Steps, Agile Development...\n                </p>\n            </div>\n        </div>\n\n        <!-- Quick Links Section -->\n        <section class=\"quick-links\">\n            <h2 class=\"section-title\">Quick Links</h2>\n            <div class=\"categories-grid\">\n                <div class=\"category-card\" onclick=\"window.open('https://github.com/ServiceNowDevProgram/code-snippets', '_blank')\">\n                    <div class=\"category-icon\">\n                        <i class=\"fab fa-github\"></i>\n                    </div>\n                    <h3 class=\"category-title\">GitHub Repository</h3>\n                    <p class=\"category-description\">\n                        View the source code, contribute new snippets, and report issues on our GitHub repository.\n                    </p>\n                </div>\n\n                <div class=\"category-card\" onclick=\"window.open('https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md', '_blank')\">\n                    <div class=\"category-icon\">\n                        <i class=\"fas fa-hands-helping\"></i>\n                    </div>\n                    <h3 class=\"category-title\">Contribute</h3>\n                    <p class=\"category-description\">\n                        Learn how to contribute your own ServiceNow code snippets to help the community.\n                    </p>\n                </div>\n\n                <div class=\"category-card\" onclick=\"window.open('https://devlink.sn/hacktoberfest', '_blank')\">\n                    <div class=\"category-icon\">\n                        <i class=\"fas fa-graduation-cap\"></i>\n                    </div>\n                    <h3 class=\"category-title\">Hacktoberfest</h3>\n                    <p class=\"category-description\">\n                        This repository is a product of the ServiceNow Community's participation in Hacktoberfest. Read more about it here.\n                    </p>\n                </div>\n            </div>\n        </section>\n    </main>\n\n    <!-- Footer will be loaded by footer.js -->\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    <script src=\"assets/footer.js\" data-auto-init data-root-page=\"true\"></script>\n    \n    <script>\n        // Navigation function\n        function navigateToCategory(category) {\n            // Navigate to the category pages\n            const categoryMap = {\n                'core-servicenow-apis': 'pages/core-apis.html',\n                'server-side-components': 'pages/server-side-components.html',\n                'client-side-components': 'pages/client-side-components.html',\n                'modern-development': 'pages/modern-development.html',\n                'integration': 'pages/integration.html',\n                'specialized-areas': 'pages/specialized-areas.html'\n            };\n            \n            const pageUrl = categoryMap[category];\n            if (pageUrl) {\n                window.location.href = pageUrl;\n            }\n        }\n\n        // Rate limiting and API management\n        class GitHubApiManager {\n            constructor() {\n                this.rateLimitHit = false;\n                this.rateLimitResetTime = null;\n                this.apiCallCount = 0;\n                this.lastApiReset = Date.now();\n                this.maxApiCalls = 50; // Conservative limit per hour\n                this.apiCallLog = [];\n            }\n\n            canMakeApiCall() {\n                const now = Date.now();\n                \n                // Reset counter every hour\n                if (now - this.lastApiReset > 3600000) {\n                    this.apiCallCount = 0;\n                    this.lastApiReset = now;\n                    this.apiCallLog = [];\n                }\n\n                // Check if we're rate limited\n                if (this.rateLimitHit && this.rateLimitResetTime && now < this.rateLimitResetTime) {\n                    return false;\n                }\n\n                // Reset rate limit flag if time has passed\n                if (this.rateLimitHit && this.rateLimitResetTime && now >= this.rateLimitResetTime) {\n                    this.rateLimitHit = false;\n                    this.rateLimitResetTime = null;\n                }\n\n                // Check if we've made too many calls recently\n                this.apiCallLog = this.apiCallLog.filter(time => now - time < 3600000);\n                \n                return this.apiCallLog.length < this.maxApiCalls && !this.rateLimitHit;\n            }\n\n            recordApiCall(response) {\n                const now = Date.now();\n                this.apiCallLog.push(now);\n                this.apiCallCount++;\n\n                if (response && (response.status === 403 || response.status === 401)) {\n                    this.rateLimitHit = true;\n                    // Set reset time to 1 hour from now if not provided by headers\n                    this.rateLimitResetTime = now + 3600000;\n                    \n                    // Try to get actual reset time from headers\n                    const resetHeader = response.headers.get('X-RateLimit-Reset');\n                    if (resetHeader) {\n                        this.rateLimitResetTime = parseInt(resetHeader) * 1000;\n                    }\n                }\n            }\n\n            getRateLimitStatus() {\n                if (!this.rateLimitHit) return { limited: false };\n                \n                const now = Date.now();\n                const resetIn = this.rateLimitResetTime ? Math.max(0, this.rateLimitResetTime - now) : 0;\n                \n                return {\n                    limited: true,\n                    resetIn: resetIn,\n                    resetTime: new Date(this.rateLimitResetTime).toLocaleTimeString()\n                };\n            }\n        }\n\n        // Enhanced caching with localStorage\n        class SearchCache {\n            constructor() {\n                this.memoryCache = new Map();\n                this.cachePrefix = 'sns_search_';\n                this.cacheVersion = '1.0';\n                this.maxAge = 1800000; // 30 minutes\n            }\n\n            getCacheKey(query) {\n                return `${this.cachePrefix}${this.cacheVersion}_${btoa(query.toLowerCase()).slice(0, 20)}`;\n            }\n\n            get(query) {\n                // Try memory cache first\n                if (this.memoryCache.has(query)) {\n                    const cached = this.memoryCache.get(query);\n                    if (Date.now() - cached.timestamp < this.maxAge) {\n                        return cached.data;\n                    } else {\n                        this.memoryCache.delete(query);\n                    }\n                }\n\n                // Try localStorage cache\n                try {\n                    const cacheKey = this.getCacheKey(query);\n                    const cached = localStorage.getItem(cacheKey);\n                    if (cached) {\n                        const parsedCache = JSON.parse(cached);\n                        if (Date.now() - parsedCache.timestamp < this.maxAge) {\n                            // Also store in memory cache for faster access\n                            this.memoryCache.set(query, parsedCache);\n                            return parsedCache.data;\n                        } else {\n                            localStorage.removeItem(cacheKey);\n                        }\n                    }\n                } catch (error) {\n                    console.warn('Cache read error:', error);\n                }\n\n                return null;\n            }\n\n            set(query, data) {\n                const cacheData = {\n                    data: data,\n                    timestamp: Date.now()\n                };\n\n                // Store in memory cache\n                this.memoryCache.set(query, cacheData);\n\n                // Store in localStorage\n                try {\n                    const cacheKey = this.getCacheKey(query);\n                    localStorage.setItem(cacheKey, JSON.stringify(cacheData));\n                } catch (error) {\n                    console.warn('Cache write error:', error);\n                }\n            }\n\n            clear() {\n                this.memoryCache.clear();\n                try {\n                    const keys = Object.keys(localStorage);\n                    keys.forEach(key => {\n                        if (key.startsWith(this.cachePrefix)) {\n                            localStorage.removeItem(key);\n                        }\n                    });\n                } catch (error) {\n                    console.warn('Cache clear error:', error);\n                }\n            }\n        }\n\n        // Search System Implementation\n        class CodeSnippetSearch {\n            constructor() {\n                this.searchInput = document.getElementById('search-input');\n                this.searchResults = document.getElementById('search-results');\n                this.fileCache = new Map();\n                this.searchCache = new SearchCache();\n                this.apiManager = new GitHubApiManager();\n                this.debounceTimer = null;\n                this.isSearching = false;\n                this.currentQuery = '';\n                this.offlineMode = false;\n                \n                this.initializeSearch();\n                this.initializeStaticData();\n            }\n\n            initializeStaticData() {\n                // Static data for offline search fallback\n                this.staticCategories = [\n                    {\n                        name: 'Core ServiceNow APIs',\n                        path: 'Core ServiceNow APIs',\n                        url: 'pages/core-apis.html',\n                        subcategories: ['GlideAggregate', 'GlideAjax', 'GlideDate', 'GlideDateTime', 'GlideElement', 'GlideFilter', 'GlideHTTPRequest', 'GlideRecord', 'GlideSession', 'GlideSystem', 'GlideUser', 'XML Functions']\n                    },\n                    {\n                        name: 'Server-Side Components', \n                        path: 'Server-Side Components',\n                        url: 'pages/server-side-components.html',\n                        subcategories: ['Background Scripts', 'Business Rules', 'Script Includes', 'Scheduled Jobs', 'Flow Designer', 'Transform Maps', 'Inbound Email Actions', 'Notifications', 'Email Scripts']\n                    },\n                    {\n                        name: 'Client-Side Components',\n                        path: 'Client-Side Components', \n                        url: 'pages/client-side-components.html',\n                        subcategories: ['Client Scripts', 'Catalog Client Script', 'UI Actions', 'UI Scripts', 'UI Macros', 'UI Pages', 'UX Client Scripts', 'UX Data Broker Transform', 'UX Client Script Include']\n                    },\n                    {\n                        name: 'Modern Development',\n                        path: 'Modern Development',\n                        url: 'pages/modern-development.html', \n                        subcategories: ['Service Portal', 'Service Portal - Angular Provider', 'Service Portal - CSS', 'Service Portal - HTML Templates', 'Service Portal - Server Scripts']\n                    },\n                    {\n                        name: 'Integration',\n                        path: 'Integration',\n                        url: 'pages/integration.html',\n                        subcategories: ['Attachment Utilities', 'Import Sets', 'Mail Scripts', 'MIDServer Scripts', 'RESTMessageV2', 'Scripted REST API', 'Web Services']\n                    },\n                    {\n                        name: 'Specialized Areas',\n                        path: 'Specialized Areas', \n                        url: 'pages/specialized-areas.html',\n                        subcategories: ['Agile Development', 'ATF Steps', 'CMDB', 'Discovery', 'Event Management', 'HRSD', 'ITOM', 'Knowledge Management', 'Machine Learning', 'Mobile', 'Now Assist', 'PA', 'Performance Analytics', 'Predictive Intelligence', 'SIAM', 'Security', 'Service Mapping', 'Virtual Agent']\n                    }\n                ];\n            }\n\n            initializeSearch() {\n                // Add event listeners\n                this.searchInput.addEventListener('input', (e) => this.handleSearchInput(e));\n                this.searchInput.addEventListener('focus', () => this.handleSearchFocus());\n                this.searchInput.addEventListener('blur', (e) => this.handleSearchBlur(e));\n                \n                // Close search results when clicking outside\n                document.addEventListener('click', (e) => {\n                    if (!e.target.closest('.search-box')) {\n                        this.hideSearchResults();\n                    }\n                });\n\n                // Handle keyboard navigation\n                this.searchInput.addEventListener('keydown', (e) => this.handleKeyNavigation(e));\n            }\n\n            handleSearchInput(e) {\n                const query = e.target.value.trim();\n                this.currentQuery = query;\n\n                // Clear previous debounce timer\n                if (this.debounceTimer) {\n                    clearTimeout(this.debounceTimer);\n                }\n\n                if (query.length === 0) {\n                    this.hideSearchResults();\n                    return;\n                }\n\n                if (query.length < 2) {\n                    this.showMessage('Type at least 2 characters to search...');\n                    return;\n                }\n\n                // Debounce search - increased to 600ms to reduce API calls\n                this.debounceTimer = setTimeout(() => {\n                    this.performSearch(query);\n                }, 600);\n            }\n\n            handleSearchFocus() {\n                if (this.currentQuery.length >= 2 && this.searchResults.children.length > 0) {\n                    this.showSearchResults();\n                }\n            }\n\n            handleSearchBlur(e) {\n                // Delay hiding to allow clicking on results\n                setTimeout(() => {\n                    if (!this.searchResults.contains(document.activeElement) && \n                        !this.searchResults.matches(':hover')) {\n                        this.hideSearchResults();\n                    }\n                }, 150);\n            }\n\n            handleKeyNavigation(e) {\n                const results = this.searchResults.querySelectorAll('.search-result-item');\n                if (results.length === 0) return;\n\n                let currentIndex = Array.from(results).findIndex(item => item.classList.contains('active'));\n\n                switch(e.key) {\n                    case 'ArrowDown':\n                        e.preventDefault();\n                        currentIndex = (currentIndex + 1) % results.length;\n                        this.highlightResult(results, currentIndex);\n                        break;\n                    case 'ArrowUp':\n                        e.preventDefault();\n                        currentIndex = currentIndex <= 0 ? results.length - 1 : currentIndex - 1;\n                        this.highlightResult(results, currentIndex);\n                        break;\n                    case 'Enter':\n                        e.preventDefault();\n                        if (currentIndex >= 0) {\n                            results[currentIndex].click();\n                        }\n                        break;\n                    case 'Escape':\n                        this.hideSearchResults();\n                        this.searchInput.blur();\n                        break;\n                }\n            }\n\n            highlightResult(results, index) {\n                results.forEach(item => item.classList.remove('active'));\n                if (index >= 0 && index < results.length) {\n                    results[index].classList.add('active');\n                    results[index].scrollIntoView({ block: 'nearest' });\n                }\n            }\n\n            async performSearch(query) {\n                if (this.isSearching) return;\n                \n                this.isSearching = true;\n                this.showLoading();\n\n                try {\n                    // Check cache first\n                    const cachedResults = this.searchCache.get(query);\n                    if (cachedResults) {\n                        this.displayResults(cachedResults, query);\n                        return;\n                    }\n\n                    // Check if we can make API calls\n                    const rateLimitStatus = this.apiManager.getRateLimitStatus();\n                    if (rateLimitStatus.limited) {\n                        const results = this.performOfflineSearch(query);\n                        this.displayResults(results, query, true);\n                        return;\n                    }\n\n                    // Check if we can make API calls\n                    if (!this.apiManager.canMakeApiCall()) {\n                        console.log('API rate limit preventative measure - using offline search');\n                        const results = this.performOfflineSearch(query);\n                        this.displayResults(results, query, true);\n                        return;\n                    }\n\n                    // Perform online search\n                    const results = await this.searchRepository(query);\n                    \n                    // Cache results\n                    this.searchCache.set(query, results);\n                    \n                    // Display results\n                    this.displayResults(results, query);\n\n                } catch (error) {\n                    console.error('Search error:', error);\n                    // Fallback to offline search on error\n                    try {\n                        const offlineResults = this.performOfflineSearch(query);\n                        this.displayResults(offlineResults, query, true);\n                    } catch (offlineError) {\n                        this.showError('Search failed. Please try again or check your connection.');\n                    }\n                } finally {\n                    this.isSearching = false;\n                }\n            }\n\n            performOfflineSearch(query) {\n                const results = [];\n                const searchTerms = query.toLowerCase().split(/\\s+/);\n\n                // Search categories\n                this.staticCategories.forEach(category => {\n                    // Check main category name\n                    if (searchTerms.some(term => category.name.toLowerCase().includes(term))) {\n                        results.push({\n                            type: 'category',\n                            title: category.name,\n                            path: category.path,\n                            snippet: `Category: ${category.name}`,\n                            url: category.url\n                        });\n                    }\n\n                    // Check subcategories  \n                    category.subcategories.forEach(subcategory => {\n                        if (searchTerms.some(term => subcategory.toLowerCase().includes(term))) {\n                            results.push({\n                                type: 'subcategory',\n                                title: subcategory,\n                                path: `${category.path}/${subcategory}`,\n                                snippet: `Subcategory in ${category.name}: ${subcategory}`,\n                                url: `https://github.com/${REPO}/tree/${BRANCH}/${encodeURIComponent(category.path)}/${encodeURIComponent(subcategory)}`\n                            });\n                        }\n                    });\n                });\n\n                return results.slice(0, 10);\n            }\n\n            async searchRepository(query) {\n                const searchableExtensions = ['.js', '.ts', '.md', '.html', '.css', '.json', '.xml'];\n                const results = [];\n                \n                try {\n                    // Use GitHub's search API for better performance\n                    // Search in filename and content\n                    const fileExtensions = searchableExtensions.map(ext => `extension:${ext.slice(1)}`).join(' OR ');\n                    const searchQuery = `repo:${REPO} (${query}) (${fileExtensions})`;\n                    \n                    console.log('GitHub search query:', searchQuery);\n                    \n                    const response = await fetch(\n                        `https://api.github.com/search/code?q=${encodeURIComponent(searchQuery)}&per_page=100`,\n                        {\n                            headers: {\n                                'Accept': 'application/vnd.github.v3+json'\n                            }\n                        }\n                    );\n\n                    // Record API call for rate limiting\n                    this.apiManager.recordApiCall(response);\n\n                    if (response.status === 403 || response.status === 401) {\n                        // Rate limit hit or unauthorized, fall back to offline search\n                        console.log(`API error ${response.status} during search, falling back to offline search`);\n                        return this.performOfflineSearch(query);\n                    }\n\n                    if (!response.ok) {\n                        throw new Error(`GitHub API error: ${response.status}`);\n                    }\n\n                    const data = await response.json();\n                    \n                    // Process search results\n                    for (const item of data.items) {\n                        if (this.shouldIncludeFile(item.path)) {\n                            // Add file match first\n                            results.push({\n                                type: 'file',\n                                title: this.getFileName(item.path),\n                                path: item.path,\n                                snippet: `File: ${item.path}`,\n                                url: `https://github.com/${REPO}/blob/${BRANCH}/${encodeURIComponent(item.path)}`\n                            });\n\n                            // Then try to get content for code matches\n                            try {\n                                const fileContent = await this.getFileContent(item);\n                                if (fileContent) {\n                                    const matches = this.findMatches(fileContent, query, item.path);\n                                    results.push(...matches);\n                                }\n                            } catch (error) {\n                                console.log(`Could not fetch content for ${item.path}:`, error);\n                            }\n                        }\n                    }\n\n                    return results.slice(0, 20); // Limit to 20 results\n\n                } catch (error) {\n                    console.error('GitHub search failed:', error);\n                    return this.performOfflineSearch(query);\n                }\n            }\n\n            async fallbackSearch(query) {\n                // Fallback search is now just offline search to prevent API calls\n                console.log('Using fallback search for:', query);\n                return this.performOfflineSearch(query);\n            }\n\n            // Disabled recursive directory search to prevent API flooding\n            async recursiveDirectorySearch(path, searchTerms, originalQuery, depth = 0, maxDepth = 4) {\n                // This method is disabled to prevent API rate limiting\n                // Use performOfflineSearch instead\n                console.log('Recursive directory search disabled to prevent rate limiting');\n                return [];\n            }\n\n            async getFileContentByPath(filePath) {\n                // Disabled to prevent API rate limiting\n                console.log('File content fetching disabled to prevent rate limiting');\n                return null;\n            }\n\n            matchesSearchTerms(text, searchTerms) {\n                const textLower = text.toLowerCase();\n                return searchTerms.some(term => textLower.includes(term));\n            }\n\n            isSearchableFile(fileName) {\n                const searchableExtensions = ['.js', '.ts', '.md', '.html', '.css', '.json', '.xml', '.txt'];\n                return searchableExtensions.some(ext => fileName.toLowerCase().endsWith(ext));\n            }\n\n            shouldExcludeFile(fileName) {\n                return EXCLUDED_ROOT_FILES.includes(fileName);\n            }\n\n            findContentMatches(content, query, filePath) {\n                const results = [];\n                const lines = content.split('\\n');\n                const queryLower = query.toLowerCase();\n                const queryTerms = queryLower.split(/\\s+/);\n                let matchCount = 0;\n\n                // Search for matches in content\n                lines.forEach((line, index) => {\n                    const lineLower = line.toLowerCase();\n                    \n                    // Check if line contains any query terms\n                    if (queryTerms.some(term => lineLower.includes(term))) {\n                        if (matchCount < 2) { // Limit matches per file\n                            results.push({\n                                type: 'code',\n                                title: this.getFileName(filePath),\n                                path: filePath,\n                                lineNumber: index + 1,\n                                snippet: this.getContextSnippet(lines, index, query),\n                                url: `https://github.com/${REPO}/blob/${BRANCH}/${encodeURIComponent(filePath)}#L${index + 1}`\n                            });\n                            matchCount++;\n                        }\n                    }\n                });\n\n                return results;\n            }\n\n            getCategoryUrl(categoryName) {\n                const categoryMap = {\n                    'Core ServiceNow APIs': 'pages/core-apis.html',\n                    'Server-Side Components': 'pages/server-side-components.html',\n                    'Client-Side Components': 'pages/client-side-components.html',\n                    'Modern Development': 'pages/modern-development.html',\n                    'Integration': 'pages/integration.html',\n                    'Specialized Areas': 'pages/specialized-areas.html'\n                };\n                return categoryMap[categoryName] || '#';\n            }\n\n            async getFileContent(item) {\n                // Disabled to prevent additional API calls\n                console.log('File content fetching disabled to prevent rate limiting');\n                return null;\n            }\n\n            shouldIncludeFile(path) {\n                // Skip excluded files and paths\n                if (EXCLUDED_ROOT_FILES.some(file => path.endsWith(file))) {\n                    return false;\n                }\n                \n                if (EXCLUDED_FOLDERS.some(folder => path.includes(folder))) {\n                    return false;\n                }\n\n                return true;\n            }\n\n            findMatches(content, query, filePath) {\n                const results = [];\n                const lines = content.split('\\n');\n                const queryLower = query.toLowerCase();\n                const queryTerms = queryLower.split(/\\s+/);\n\n                // Search for matches in content\n                lines.forEach((line, index) => {\n                    const lineLower = line.toLowerCase();\n                    \n                    // Check if line contains all query terms\n                    if (queryTerms.every(term => lineLower.includes(term))) {\n                        results.push({\n                            type: 'code',\n                            title: this.getFileName(filePath),\n                            path: filePath,\n                            lineNumber: index + 1,\n                            snippet: this.getContextSnippet(lines, index, query),\n                            url: `https://github.com/${REPO}/blob/${BRANCH}/${encodeURIComponent(filePath)}#L${index + 1}`\n                        });\n                    }\n                });\n\n                // Also check filename\n                if (queryTerms.some(term => filePath.toLowerCase().includes(term))) {\n                    results.unshift({\n                        type: 'file',\n                        title: this.getFileName(filePath),\n                        path: filePath,\n                        snippet: `File: ${filePath}`,\n                        url: `https://github.com/${REPO}/blob/${BRANCH}/${encodeURIComponent(filePath)}`\n                    });\n                }\n\n                return results.slice(0, 3); // Limit matches per file\n            }\n\n            getFileName(path) {\n                return path.split('/').pop();\n            }\n\n            getContextSnippet(lines, matchIndex, query) {\n                const start = Math.max(0, matchIndex - 1);\n                const end = Math.min(lines.length, matchIndex + 2);\n                const contextLines = lines.slice(start, end);\n                \n                return contextLines\n                    .map((line, i) => {\n                        const lineNum = start + i + 1;\n                        const prefix = i === (matchIndex - start) ? '→ ' : '  ';\n                        return `${lineNum.toString().padStart(3)}: ${prefix}${line}`;\n                    })\n                    .join('\\n');\n            }\n\n            displayResults(results, query, isOffline = false) {\n                this.searchResults.innerHTML = '';\n\n                if (results.length === 0) {\n                    this.showNoResults(query, isOffline);\n                    return;\n                }\n\n                // Add stats\n                const stats = document.createElement('div');\n                stats.className = 'search-stats';\n                const offlineIndicator = isOffline ? ' (offline mode - categories only)' : '';\n                stats.textContent = `Found ${results.length} result${results.length !== 1 ? 's' : ''} for \"${query}\"${offlineIndicator}`;\n                this.searchResults.appendChild(stats);\n\n                // Add results\n                results.forEach((result, index) => {\n                    const item = this.createResultItem(result, query);\n                    this.searchResults.appendChild(item);\n                });\n\n                this.showSearchResults();\n            }\n\n            createResultItem(result, query) {\n                const item = document.createElement('div');\n                item.className = 'search-result-item';\n                \n                const title = document.createElement('div');\n                title.className = 'search-result-title';\n                \n                const badge = document.createElement('span');\n                badge.className = 'file-type-badge';\n                badge.textContent = result.type;\n                \n                title.appendChild(badge);\n                title.appendChild(document.createTextNode(result.title));\n                \n                const path = document.createElement('div');\n                path.className = 'search-result-path';\n                path.textContent = result.path;\n                \n                const snippet = document.createElement('div');\n                snippet.className = 'search-result-snippet';\n                snippet.innerHTML = this.highlightText(result.snippet, query);\n                \n                item.appendChild(title);\n                item.appendChild(path);\n                item.appendChild(snippet);\n                \n                // Add click handler\n                item.addEventListener('click', () => {\n                    window.open(result.url, '_blank');\n                    this.hideSearchResults();\n                });\n\n                return item;\n            }\n\n            highlightText(text, query) {\n                const queryTerms = query.toLowerCase().split(/\\s+/);\n                let highlightedText = text;\n                \n                queryTerms.forEach(term => {\n                    if (term.length > 1) {\n                        const regex = new RegExp(`(${this.escapeRegex(term)})`, 'gi');\n                        highlightedText = highlightedText.replace(regex, '<span class=\"search-highlight\">$1</span>');\n                    }\n                });\n                \n                return highlightedText;\n            }\n\n            escapeRegex(string) {\n                return string.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n            }\n\n            showLoading() {\n                this.searchResults.innerHTML = '<div class=\"search-loading\"><i class=\"fas fa-spinner fa-spin\"></i> Searching...</div>';\n                this.showSearchResults();\n            }\n\n            showError(message) {\n                this.searchResults.innerHTML = `<div class=\"search-error\"><i class=\"fas fa-exclamation-triangle\"></i> ${message}</div>`;\n                this.showSearchResults();\n            }\n\n            showNoResults(query, isOffline = false) {\n                const offlineMessage = isOffline ? '<br><small style=\"color: #ea4335;\">Search limited to categories in offline mode</small>' : '';\n                this.searchResults.innerHTML = `\n                    <div class=\"search-no-results\">\n                        <i class=\"fas fa-search\"></i><br>\n                        No results found for \"<strong>${query}</strong>\"<br>\n                        <small>Try different keywords or check spelling</small>\n                        ${offlineMessage}\n                    </div>\n                `;\n                this.showSearchResults();\n            }\n\n            showMessage(message) {\n                this.searchResults.innerHTML = `<div class=\"search-loading\">${message}</div>`;\n                this.showSearchResults();\n            }\n\n            showSearchResults() {\n                this.searchResults.classList.add('show');\n            }\n\n            hideSearchResults() {\n                this.searchResults.classList.remove('show');\n            }\n        }\n\n        // Initialize search system\n        let searchSystem;\n        document.addEventListener('DOMContentLoaded', function() {\n            searchSystem = new CodeSnippetSearch();\n        });\n\n        // Smooth scrolling for anchor links\n        document.querySelectorAll('a[href^=\"#\"]').forEach(anchor => {\n            anchor.addEventListener('click', function (e) {\n                e.preventDefault();\n                const target = document.querySelector(this.getAttribute('href'));\n                if (target) {\n                    target.scrollIntoView({\n                        behavior: 'smooth',\n                        block: 'start'\n                    });\n                }\n            });\n        });\n\n        // Add loading animation to category cards on click\n        document.querySelectorAll('.category-card').forEach(card => {\n            card.addEventListener('click', function() {\n                const icon = this.querySelector('.category-icon i');\n                const originalClass = icon.className;\n                icon.className = 'loading';\n                \n                setTimeout(() => {\n                    icon.className = originalClass;\n                }, 1000);\n            });\n        });\n\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        // Folders to exclude from counting\n        const EXCLUDED_FOLDERS = [\n            '.github',\n            '.git',\n            'node_modules',\n            '.vscode',\n            '.idea',\n            'assets'\n        ];\n\n        // Root files to exclude (these are typically config/documentation files)\n        const EXCLUDED_ROOT_FILES = [\n            'README.md',\n            'CONTRIBUTING.md',\n            'CLAUDE.md',\n            'PAGES.md',\n            'LICENSE',\n            '.gitignore',\n            'package.json',\n            'package-lock.json',\n            '_config.yml',\n            'sitemap.xml',\n            'index.html',\n            'core-apis.html',\n            'server-side-components.html',\n            'client-side-components.html',\n            'modern-development.html',\n            'integration.html',\n            'specialized-areas.html'\n        ];\n\n        // Function to fetch directory contents from GitHub API\n        async function fetchGitHubDirectory(path, apiManager) {\n            // Don't make API call if rate limited\n            if (apiManager && !apiManager.canMakeApiCall()) {\n                throw new Error('Rate limited');\n            }\n\n            try {\n                const encodedPath = path ? path.split('/').map(encodeURIComponent).join('/') : '';\n                const url = path ? \n                    `${GITHUB_API_BASE}/repos/${REPO}/contents/${encodedPath}?ref=${BRANCH}` :\n                    `${GITHUB_API_BASE}/repos/${REPO}/contents?ref=${BRANCH}`;\n                \n                const response = await fetch(url);\n                \n                // Record API call if manager provided\n                if (apiManager) {\n                    apiManager.recordApiCall(response);\n                }\n\n                if (!response.ok) {\n                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n                }\n                return await response.json();\n            } catch (error) {\n                console.error('Error fetching directory:', path, error);\n                return [];\n            }\n        }\n\n        // Check if a folder should be excluded\n        function shouldExcludeFolder(folderName, isRoot = false) {\n            return EXCLUDED_FOLDERS.includes(folderName);\n        }\n\n        // Check if a file should be counted\n        function shouldCountFile(fileName, isRoot = false) {\n            // Exclude root-level config files\n            if (isRoot && EXCLUDED_ROOT_FILES.includes(fileName)) {\n                return false;\n            }\n            \n            // Count all code and content files\n            return fileName.match(/\\.(js|ts|json|html|css|py|java|c|cpp|cs|php|rb|go|rs|swift|kt|md|txt|xml|sql|sh|bat|ps1|yml|yaml)$/i);\n        }\n\n        // Recursively count all relevant files\n        async function countAllFiles(path = '', isRoot = true) {\n            const contents = await fetchGitHubDirectory(path);\n            let count = 0;\n            \n            for (const item of contents) {\n                if (item.type === 'file') {\n                    if (shouldCountFile(item.name, isRoot)) {\n                        count++;\n                        if (isRoot) {\n                            console.log(`Root file counted: ${item.name}`);\n                        }\n                    }\n                } else if (item.type === 'dir') {\n                    if (!shouldExcludeFolder(item.name, isRoot)) {\n                        const subPath = path ? `${path}/${item.name}` : item.name;\n                        const subCount = await countAllFiles(subPath, false);\n                        count += subCount;\n                        console.log(`${item.name}: ${subCount} files`);\n                    } else {\n                        console.log(`Excluded folder: ${item.name}`);\n                    }\n                }\n            }\n            \n            return count;\n        }\n\n        // Fetch contributor count from GitHub API\n        async function getContributorCount(apiManager) {\n            try {\n                const response = await fetch(`${GITHUB_API_BASE}/repos/${REPO}/contributors`);\n                \n                // Record API call if manager provided\n                if (apiManager) {\n                    apiManager.recordApiCall(response);\n                }\n                \n                if (!response.ok) {\n                    if (response.status === 401 || response.status === 403) {\n                        console.log(`Contributors API authentication failed: ${response.status}`);\n                        return null;\n                    }\n                    throw new Error(`Contributors API failed: ${response.status}`);\n                }\n                \n                const contributors = await response.json();\n                return contributors.length;\n                \n            } catch (error) {\n                console.error('Error fetching contributors:', error);\n                return null;\n            }\n        }\n\n        // Use GitHub's search API to count files more efficiently\n        async function countFilesWithSearch(apiManager) {\n            try {\n                // Search for files in the repo excluding common config/system files\n                const searchQuery = `repo:${REPO} -path:.github -path:assets -filename:README.md -filename:CONTRIBUTING.md -filename:CLAUDE.md -filename:PAGES.md -filename:LICENSE -filename:.gitignore -filename:package.json -filename:_config.yml -filename:sitemap.xml -filename:index.html -filename:core-apis.html -filename:server-side-components.html -filename:client-side-components.html -filename:modern-development.html -filename:integration.html -filename:specialized-areas.html`;\n                \n                const response = await fetch(`https://api.github.com/search/code?q=${encodeURIComponent(searchQuery)}&per_page=1`);\n                \n                // Record API call if manager provided\n                if (apiManager) {\n                    apiManager.recordApiCall(response);\n                }\n                \n                if (!response.ok) {\n                    if (response.status === 401 || response.status === 403) {\n                        console.log(`Search API authentication failed: ${response.status}`);\n                        return 0;\n                    }\n                    throw new Error(`Search API failed: ${response.status}`);\n                }\n                \n                const data = await response.json();\n                return data.total_count;\n                \n            } catch (error) {\n                console.error('Search API failed, falling back to manual count:', error);\n                return 0; // Return 0 instead of trying manual count to avoid more API calls\n            }\n        }\n\n        // Dynamic stats loading with realistic numbers - but only once per session\n        async function updateStats() {\n            // Check if we've already updated stats in this session\n            const statsUpdatedKey = 'sns_stats_updated';\n            const lastUpdated = sessionStorage.getItem(statsUpdatedKey);\n            const now = Date.now();\n            \n            // Only update stats once per session (1 hour max)\n            if (lastUpdated && (now - parseInt(lastUpdated)) < 3600000) {\n                console.log('Stats already updated this session, using cached values');\n                const snippetsElement = document.getElementById('total-snippets');\n                const contributorsElement = document.getElementById('total-contributors');\n                snippetsElement.textContent = '1900+';\n                contributorsElement.textContent = '240+';\n                return;\n            }\n\n            const snippetsElement = document.getElementById('total-snippets');\n            const contributorsElement = document.getElementById('total-contributors');\n            \n            // Show loading state briefly for visual effect\n            snippetsElement.innerHTML = '<span class=\"loading-text\">Counting...</span>';\n            contributorsElement.innerHTML = '<span class=\"loading-text\">Loading...</span>';\n            \n            // Simulate loading time\n            setTimeout(async () => {\n                try {\n                    console.log('Fetching repository statistics...');\n                    \n                    // Create a temporary API manager for stats\n                    const statsApiManager = new GitHubApiManager();\n                    \n                    // Only try API if we can make calls\n                    let fileCount = 0;\n                    let contributorCount = null;\n                    \n                    if (statsApiManager.canMakeApiCall()) {\n                        // Fetch both stats in parallel, but be prepared for rate limiting\n                        try {\n                            [fileCount, contributorCount] = await Promise.all([\n                                countFilesWithSearch(statsApiManager),\n                                getContributorCount(statsApiManager)\n                            ]);\n                        } catch (apiError) {\n                            console.log('API calls failed, using fallback numbers:', apiError);\n                        }\n                    } else {\n                        console.log('Rate limit protection: skipping API calls for stats');\n                    }\n                    \n                    console.log(`Files found via search: ${fileCount}`);\n                    console.log(`Contributors found: ${contributorCount}`);\n                    \n                    // Handle file count\n                    let totalFiles = \"1900+\"; // Use fallback first\n                    if (fileCount > 100) {\n                        totalFiles = fileCount.toString();\n                    }\n                    \n                    // Handle contributor count\n                    let contributorDisplay = '240+';\n                    if (contributorCount !== null && contributorCount > 0) {\n                        contributorDisplay = contributorCount.toString();\n                    }\n                    \n                    // Update the display with counts\n                    snippetsElement.textContent = totalFiles;\n                    contributorsElement.textContent = contributorDisplay;\n                    \n                    // Mark as updated in session storage\n                    sessionStorage.setItem(statsUpdatedKey, now.toString());\n                    \n                    console.log(`Stats displayed - Files: ${totalFiles}, Contributors: ${contributorDisplay}`);\n                    \n                } catch (error) {\n                    console.error('Error fetching stats:', error);\n                    // Fallback to estimated numbers\n                    snippetsElement.textContent = '1900+';\n                    contributorsElement.textContent = '240+';\n                }\n            }, 800); // Small delay for better UX\n        }\n\n        // Initialize\n        document.addEventListener('DOMContentLoaded', function() {\n            updateStats();\n        });\n    </script>\n</body>\n</html>"
  },
  {
    "path": "pages/client-side-components.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Client-Side Components | ServiceNow Code Snippets</title>\n    <meta name=\"description\" content=\"Browser-executed ServiceNow code components including Client Scripts, Catalog Client Scripts, UI Actions, and client-side development patterns.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* Reuse the CSS from core-apis.html */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Navigation */\n        .nav {\n            background: var(--bg-primary);\n            border-bottom: 1px solid var(--border-light);\n            padding: 1rem 0;\n            position: sticky;\n            top: 0;\n            z-index: 100;\n            backdrop-filter: blur(10px);\n        }\n\n        .nav-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .nav-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            text-decoration: none;\n            color: var(--text-primary);\n            font-weight: 600;\n        }\n\n        .nav-brand i {\n            color: var(--primary-color);\n        }\n\n        .breadcrumb {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .breadcrumb a {\n            color: var(--primary-color);\n            text-decoration: none;\n        }\n\n        /* Header */\n        .page-header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 3rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .page-header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .page-header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .page-header h1 {\n            font-size: 2.5rem;\n            font-weight: 700;\n            margin-bottom: 1rem;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .page-header-icon {\n            width: 64px;\n            height: 64px;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 2rem;\n        }\n\n        .page-header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            max-width: 600px;\n        }\n\n        /* Main Content */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .subcategories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 2rem;\n        }\n\n        .subcategory-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            overflow: hidden;\n            transition: all 0.3s ease;\n            cursor: pointer;\n        }\n\n        .subcategory-card:hover {\n            transform: translateY(-2px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .subcategory-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .subcategory-title {\n            font-size: 1.2rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .subcategory-description {\n            color: var(--text-secondary);\n            font-size: 0.9rem;\n        }\n\n        .subcategory-content {\n            padding: 1.5rem;\n        }\n\n        .view-all-btn {\n            width: 100%;\n            padding: 1rem;\n            background: var(--primary-color);\n            color: white;\n            border: none;\n            border-radius: var(--border-radius);\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n        }\n\n        .view-all-btn:hover {\n            background: #1557b0;\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .page-header h1 {\n                font-size: 2rem;\n                flex-direction: column;\n                text-align: center;\n            }\n            \n            .nav-content {\n                flex-direction: column;\n                gap: 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .subcategories-grid {\n                grid-template-columns: 1fr;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Navigation -->\n    <nav class=\"nav\">\n        <div class=\"nav-content\">\n            <a href=\"../index.html\" class=\"nav-brand\">\n                <i class=\"fas fa-code\"></i>\n                ServiceNow Code Snippets\n            </a>\n            <div class=\"breadcrumb\">\n                <a href=\"../index.html\">Home</a>\n                <i class=\"fas fa-chevron-right\"></i>\n                <span>Client-Side Components</span>\n            </div>\n        </div>\n    </nav>\n\n    <!-- Page Header -->\n    <header class=\"page-header\">\n        <div class=\"page-header-content\">\n            <h1>\n                <div class=\"page-header-icon\">\n                    <i class=\"fas fa-desktop\"></i>\n                </div>\n                Client-Side Components\n            </h1>\n            <p>\n                Browser-executed ServiceNow code components including Client Scripts, Catalog Client Scripts, UI Actions, \n                and other client-side development patterns for enhancing user experience and form behavior.\n            </p>\n        </div>\n    </header>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <!-- Subcategories -->\n        <div class=\"subcategories-grid\">\n            <!-- Categories will be loaded dynamically -->\n        </div>\n    </main>\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    \n    <script>\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        function openGitHubFolder(path) {\n            const encodedPath = encodeURIComponent(path);\n            const githubUrl = `https://github.com/${REPO}/tree/${BRANCH}/${encodedPath}`;\n            window.open(githubUrl, '_blank');\n        }\n\n        // Category descriptions for client-side components\n        const categoryDescriptions = {\n            'Client Scripts': 'Form behavior, validation, and user interaction scripts',\n            'Catalog Client Script': 'Service Catalog item and variable set client scripts',\n            'UI Actions': 'Custom buttons and form actions for user workflows',\n            'UI Scripts': 'Global client-side utility libraries and functions',\n            'UI Pages': 'Custom user interface pages and layouts',\n            'UI Macros': 'Reusable UI components and templates',\n            'UX Client Scripts': 'NOW Experience UI client-side scripting',\n            'UX Client Script Include': 'NOW Experience reusable client libraries',\n            'UX Data Broker Transform': 'Data transformation for NOW Experience components'\n        };\n\n        function loadAllSubcategories() {\n            // Hardcoded list of all Client-Side Component categories\n            const subcategories = [\n                'Client Scripts', 'Catalog Client Script', 'UI Actions', 'UI Scripts',\n                'UI Pages', 'UI Macros', 'UX Client Scripts', 'UX Client Script Include', 'UX Data Broker Transform'\n            ];\n            \n            // Generate the subcategory cards\n            const subcategoriesGrid = document.querySelector('.subcategories-grid');\n            \n            // Clear existing content\n            subcategoriesGrid.innerHTML = '';\n            \n            // Create cards for all subcategories\n            subcategories.forEach((subcategory) => {\n                const card = createSubcategoryCard(subcategory);\n                subcategoriesGrid.appendChild(card);\n            });\n        }\n\n        function createSubcategoryCard(subcategory) {\n            const card = document.createElement('div');\n            card.className = 'subcategory-card';\n            card.onclick = () => openGitHubFolder(`Client-Side Components/${subcategory}`);\n            \n            const description = categoryDescriptions[subcategory] || 'Client-side development utilities and patterns';\n            \n            card.innerHTML = `\n                <div class=\"subcategory-header\">\n                    <h3 class=\"subcategory-title\">${subcategory}</h3>\n                    <p class=\"subcategory-description\">${description}</p>\n                </div>\n                <div class=\"subcategory-content\">\n                    <button class=\"view-all-btn\">View ${subcategory} Examples on GitHub</button>\n                </div>\n            `;\n            \n            return card;\n        }\n\n        // Initialize page\n        document.addEventListener('DOMContentLoaded', function() {\n            // Initialize syntax highlighting\n            Prism.highlightAll();\n            \n            // Load all subcategories\n            loadAllSubcategories();\n        });\n    </script>\n    <!-- Footer will be loaded by footer.js -->\n    <script src=\"../assets/footer.js\" data-auto-init data-root-page=\"false\"></script>\n</body>\n</html>"
  },
  {
    "path": "pages/core-apis.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Core ServiceNow APIs | ServiceNow Code Snippets</title>\n    <meta name=\"description\" content=\"Essential ServiceNow JavaScript APIs and classes including GlideRecord, GlideAjax, GlideSystem, and foundational APIs.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* Reuse the CSS from index.html with some page-specific styles */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Navigation */\n        .nav {\n            background: var(--bg-primary);\n            border-bottom: 1px solid var(--border-light);\n            padding: 1rem 0;\n            position: sticky;\n            top: 0;\n            z-index: 100;\n            backdrop-filter: blur(10px);\n        }\n\n        .nav-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .nav-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            text-decoration: none;\n            color: var(--text-primary);\n            font-weight: 600;\n        }\n\n        .nav-brand i {\n            color: var(--primary-color);\n        }\n\n        .breadcrumb {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .breadcrumb a {\n            color: var(--primary-color);\n            text-decoration: none;\n        }\n\n        /* Header */\n        .page-header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 3rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .page-header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .page-header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .page-header h1 {\n            font-size: 2.5rem;\n            font-weight: 700;\n            margin-bottom: 1rem;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .page-header-icon {\n            width: 64px;\n            height: 64px;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 2rem;\n        }\n\n        .page-header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            max-width: 600px;\n        }\n\n        /* Main Content */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .subcategories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 2rem;\n        }\n\n        .subcategory-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            overflow: hidden;\n            transition: all 0.3s ease;\n            cursor: pointer;\n        }\n\n        .subcategory-card:hover {\n            transform: translateY(-2px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .subcategory-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .subcategory-title {\n            font-size: 1.2rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .subcategory-description {\n            color: var(--text-secondary);\n            font-size: 0.9rem;\n        }\n\n        .subcategory-content {\n            padding: 1.5rem;\n        }\n\n        .snippet-list {\n            list-style: none;\n        }\n\n        .snippet-item {\n            padding: 0.75rem 0;\n            border-bottom: 1px solid var(--border-light);\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .snippet-item:last-child {\n            border-bottom: none;\n        }\n\n        .snippet-name {\n            font-weight: 500;\n            color: var(--text-primary);\n        }\n\n        .snippet-type {\n            background: var(--bg-tertiary);\n            color: var(--primary-color);\n            padding: 0.25rem 0.5rem;\n            border-radius: 12px;\n            font-size: 0.8rem;\n            font-weight: 500;\n        }\n\n        .view-all-btn {\n            width: 100%;\n            padding: 1rem;\n            background: var(--primary-color);\n            color: white;\n            border: none;\n            border-radius: var(--border-radius);\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n            margin-top: 1rem;\n        }\n\n        .view-all-btn:hover {\n            background: #1557b0;\n        }\n\n        /* Code Preview */\n        .code-preview {\n            background: var(--bg-secondary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            margin: 2rem 0;\n            overflow: hidden;\n        }\n\n        .code-preview-header {\n            background: var(--bg-tertiary);\n            padding: 1rem;\n            border-bottom: 1px solid var(--border-light);\n            display: flex;\n            align-items: center;\n            justify-content: between;\n        }\n\n        .code-preview-title {\n            font-weight: 600;\n            color: var(--text-primary);\n        }\n\n        .code-preview-content {\n            padding: 0;\n        }\n\n        pre {\n            margin: 0;\n            padding: 1.5rem;\n            background: #2d3748;\n            color: #e2e8f0;\n            font-family: var(--font-mono);\n            font-size: 0.9rem;\n            line-height: 1.5;\n            overflow-x: auto;\n        }\n\n        /* Snippet Detail Modal */\n        .modal {\n            display: none;\n            position: fixed;\n            z-index: 1000;\n            left: 0;\n            top: 0;\n            width: 100%;\n            height: 100%;\n            background-color: rgba(0,0,0,0.5);\n            backdrop-filter: blur(4px);\n        }\n\n        .modal.show {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n        }\n\n        .modal-content {\n            background: var(--bg-primary);\n            border-radius: var(--border-radius);\n            width: 90%;\n            max-width: 1000px;\n            max-height: 90vh;\n            overflow: hidden;\n            box-shadow: var(--shadow-lg);\n            animation: modalSlideIn 0.3s ease-out;\n        }\n\n        @keyframes modalSlideIn {\n            from {\n                opacity: 0;\n                transform: translateY(-50px) scale(0.95);\n            }\n            to {\n                opacity: 1;\n                transform: translateY(0) scale(1);\n            }\n        }\n\n        .modal-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .modal-title {\n            font-size: 1.5rem;\n            font-weight: 600;\n            color: var(--text-primary);\n            margin: 0;\n        }\n\n        .modal-close {\n            background: none;\n            border: none;\n            font-size: 1.5rem;\n            color: var(--text-secondary);\n            cursor: pointer;\n            padding: 0.5rem;\n            border-radius: 50%;\n            transition: all 0.2s ease;\n        }\n\n        .modal-close:hover {\n            background: var(--border-light);\n            color: var(--text-primary);\n        }\n\n        .modal-body {\n            padding: 0;\n            max-height: 70vh;\n            overflow-y: auto;\n        }\n\n        .snippet-detail {\n            display: none;\n        }\n\n        .snippet-detail.active {\n            display: block;\n        }\n\n        .snippet-readme {\n            padding: 2rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .snippet-readme h1,\n        .snippet-readme h2,\n        .snippet-readme h3 {\n            color: var(--text-primary);\n            margin-bottom: 1rem;\n        }\n\n        .snippet-readme p {\n            color: var(--text-secondary);\n            margin-bottom: 1rem;\n            line-height: 1.6;\n        }\n\n        .snippet-readme ul {\n            margin: 1rem 0;\n            padding-left: 2rem;\n        }\n\n        .snippet-readme li {\n            color: var(--text-secondary);\n            margin-bottom: 0.5rem;\n        }\n\n        .snippet-files {\n            background: var(--bg-secondary);\n        }\n\n        .file-tab-header {\n            display: flex;\n            background: var(--bg-tertiary);\n            border-bottom: 1px solid var(--border-light);\n            overflow-x: auto;\n        }\n\n        .file-tab {\n            padding: 1rem 1.5rem;\n            background: none;\n            border: none;\n            color: var(--text-secondary);\n            cursor: pointer;\n            white-space: nowrap;\n            transition: all 0.2s ease;\n            border-bottom: 3px solid transparent;\n        }\n\n        .file-tab.active {\n            color: var(--primary-color);\n            border-bottom-color: var(--primary-color);\n            background: var(--bg-primary);\n        }\n\n        .file-tab:hover {\n            color: var(--text-primary);\n            background: var(--bg-primary);\n        }\n\n        .file-content {\n            display: none;\n        }\n\n        .file-content.active {\n            display: block;\n        }\n\n        .file-content pre {\n            margin: 0;\n            border-radius: 0;\n        }\n\n        .loading-indicator {\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            padding: 3rem;\n            color: var(--text-secondary);\n        }\n\n        .loading-spinner {\n            width: 32px;\n            height: 32px;\n            border: 3px solid var(--border-light);\n            border-top: 3px solid var(--primary-color);\n            border-radius: 50%;\n            animation: spin 1s linear infinite;\n            margin-right: 1rem;\n        }\n\n        .error-message {\n            background: #fef2f2;\n            border: 1px solid #fecaca;\n            color: #dc2626;\n            padding: 1rem;\n            border-radius: var(--border-radius);\n            margin: 1rem;\n        }\n\n        .snippet-meta {\n            padding: 1rem 2rem;\n            background: var(--bg-tertiary);\n            border-bottom: 1px solid var(--border-light);\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .github-link {\n            display: inline-flex;\n            align-items: center;\n            gap: 0.5rem;\n            color: var(--primary-color);\n            text-decoration: none;\n            font-weight: 500;\n        }\n\n        .github-link:hover {\n            text-decoration: underline;\n        }\n\n        /* Expandable snippet items */\n        .snippet-item {\n            cursor: pointer;\n            transition: all 0.2s ease;\n        }\n\n        .snippet-item:hover {\n            background: var(--bg-secondary);\n            border-radius: 4px;\n            margin: 0 -0.5rem;\n            padding-left: 0.5rem;\n            padding-right: 0.5rem;\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .page-header h1 {\n                font-size: 2rem;\n                flex-direction: column;\n                text-align: center;\n            }\n            \n            .nav-content {\n                flex-direction: column;\n                gap: 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .subcategories-grid {\n                grid-template-columns: 1fr;\n            }\n\n            .modal-content {\n                width: 95%;\n                max-height: 95vh;\n            }\n\n            .modal-header {\n                padding: 1rem;\n            }\n\n            .snippet-readme {\n                padding: 1rem;\n            }\n\n            .file-tab {\n                padding: 0.75rem 1rem;\n                font-size: 0.9rem;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Navigation -->\n    <nav class=\"nav\">\n        <div class=\"nav-content\">\n            <a href=\"../index.html\" class=\"nav-brand\">\n                <i class=\"fas fa-code\"></i>\n                ServiceNow Code Snippets\n            </a>\n            <div class=\"breadcrumb\">\n                <a href=\"../index.html\">Home</a>\n                <i class=\"fas fa-chevron-right\"></i>\n                <span>Core ServiceNow APIs</span>\n            </div>\n        </div>\n    </nav>\n\n    <!-- Page Header -->\n    <header class=\"page-header\">\n        <div class=\"page-header-content\">\n            <h1>\n                <div class=\"page-header-icon\">\n                    <i class=\"fas fa-code\"></i>\n                </div>\n                Core ServiceNow APIs\n            </h1>\n            <p>\n                Essential ServiceNow JavaScript APIs and classes that form the foundation of platform development. \n                These APIs provide core functionality for database operations, AJAX communication, system utilities, and more.\n            </p>\n        </div>\n    </header>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <!-- Subcategories -->\n        <div class=\"subcategories-grid\">\n            <!-- Categories will be loaded dynamically -->\n        </div>\n\n        <!-- Additional APIs Section\n        <section style=\"margin-top: 4rem;\">\n            <h2 style=\"font-size: 1.5rem; margin-bottom: 2rem; color: var(--text-primary);\">Additional Core APIs</h2>\n            <div class=\"subcategories-grid\">\n                \n            </div>\n        </section>\n         -->\n    </main>\n\n    <!-- Snippet Detail Modal -->\n    <div id=\"snippet-modal\" class=\"modal\">\n        <div class=\"modal-content\">\n            <div class=\"modal-header\">\n                <h2 id=\"modal-title\" class=\"modal-title\">Loading...</h2>\n                <button class=\"modal-close\" onclick=\"closeSnippetModal()\">&times;</button>\n            </div>\n            <div class=\"modal-body\">\n                <div class=\"loading-indicator\">\n                    <div class=\"loading-spinner\"></div>\n                    <span>Loading snippet details...</span>\n                </div>\n                <div id=\"snippet-content\" class=\"snippet-detail\">\n                    <!-- Content will be loaded here -->\n                </div>\n            </div>\n        </div>\n    </div>\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    \n    <script>\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        // Cache for API responses\n        const apiCache = new Map();\n\n        // GitHub API helper functions\n        async function fetchGitHubAPI(path) {\n            if (apiCache.has(path)) {\n                return apiCache.get(path);\n            }\n\n            try {\n                // Properly encode the path for the GitHub API\n                const encodedPath = path.split('/').map(encodeURIComponent).join('/');\n                const response = await fetch(`${GITHUB_API_BASE}/repos/${REPO}/contents/${encodedPath}?ref=${BRANCH}`);\n                if (!response.ok) {\n                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n                }\n                const data = await response.json();\n                apiCache.set(path, data);\n                return data;\n            } catch (error) {\n                console.error('GitHub API Error:', error);\n                throw error;\n            }\n        }\n\n        async function loadSubcategorySnippets(subcategory) {\n            try {\n                const path = `Core ServiceNow APIs/${subcategory}`;\n                const contents = await fetchGitHubAPI(path);\n                \n                // Filter for directories (snippet folders)\n                const snippetFolders = contents.filter(item => item.type === 'dir');\n                \n                // Get detailed info for each snippet\n                const snippets = await Promise.all(\n                    snippetFolders.map(async (folder) => {\n                        try {\n                            const snippetPath = `${path}/${folder.name}`;\n                            const snippetContents = await fetchGitHubAPI(snippetPath);\n                            \n                            // Find README and code files\n                            const readmeFile = snippetContents.find(f => \n                                f.name.toLowerCase().includes('readme') && f.name.endsWith('.md')\n                            );\n                            const codeFiles = snippetContents.filter(f => \n                                f.name.endsWith('.js') || f.name.endsWith('.ts') || f.name.endsWith('.json')\n                            );\n                            \n                            return {\n                                name: folder.name,\n                                path: snippetPath,\n                                readme: readmeFile,\n                                codeFiles: codeFiles,\n                                githubUrl: `https://github.com/${REPO}/tree/${BRANCH}/${encodeURIComponent(snippetPath)}`\n                            };\n                        } catch (error) {\n                            console.error(`Error loading snippet ${folder.name}:`, error);\n                            return null;\n                        }\n                    })\n                );\n                \n                return snippets.filter(s => s !== null);\n            } catch (error) {\n                console.error('Error loading subcategory:', error);\n                return [];\n            }\n        }\n\n        async function loadFileContent(downloadUrl) {\n            try {\n                const response = await fetch(downloadUrl);\n                if (!response.ok) {\n                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n                }\n                return await response.text();\n            } catch (error) {\n                console.error('Error loading file content:', error);\n                throw error;\n            }\n        }\n\n        function parseMarkdown(markdown) {\n            // Simple markdown parsing - could be enhanced with a proper parser\n            return markdown\n                .replace(/^# (.*$)/gm, '<h1>$1</h1>')\n                .replace(/^## (.*$)/gm, '<h2>$1</h2>')\n                .replace(/^### (.*$)/gm, '<h3>$1</h3>')\n                .replace(/\\*\\*(.*?)\\*\\*/g, '<strong>$1</strong>')\n                .replace(/\\*(.*?)\\*/g, '<em>$1</em>')\n                .replace(/`(.*?)`/g, '<code>$1</code>')\n                .replace(/\\n\\n/g, '</p><p>')\n                .replace(/^(.*)$/gm, '<p>$1</p>')\n                .replace(/^<p>(<h[1-6]>.*<\\/h[1-6]>)<\\/p>$/gm, '$1')\n                .replace(/^<p>(<ul>.*<\\/ul>)<\\/p>$/gm, '$1')\n                .replace(/^- (.*$)/gm, '<li>$1</li>')\n                .replace(/(<li>.*<\\/li>)/gs, '<ul>$1</ul>');\n        }\n\n        function getLanguageFromFileName(fileName) {\n            const ext = fileName.split('.').pop().toLowerCase();\n            const langMap = {\n                'js': 'javascript',\n                'ts': 'typescript',\n                'json': 'json',\n                'html': 'html',\n                'css': 'css',\n                'md': 'markdown'\n            };\n            return langMap[ext] || 'javascript';\n        }\n\n        async function showSnippetDetails(snippetPath, snippetName) {\n            const modal = document.getElementById('snippet-modal');\n            const modalTitle = document.getElementById('modal-title');\n            const snippetContent = document.getElementById('snippet-content');\n            const loadingIndicator = modal.querySelector('.loading-indicator');\n\n            // Show modal and loading state\n            modal.classList.add('show');\n            modalTitle.textContent = snippetName;\n            loadingIndicator.style.display = 'flex';\n            snippetContent.style.display = 'none';\n\n            try {\n                // Load snippet folder contents\n                const contents = await fetchGitHubAPI(snippetPath);\n                \n                const readmeFile = contents.find(f => \n                    f.name.toLowerCase().includes('readme') && f.name.endsWith('.md')\n                );\n                const codeFiles = contents.filter(f => \n                    f.name.endsWith('.js') || f.name.endsWith('.ts') || f.name.endsWith('.json')\n                );\n\n                let html = '';\n\n                // Add snippet metadata\n                html += `\n                    <div class=\"snippet-meta\">\n                        <a href=\"https://github.com/${REPO}/tree/${BRANCH}/${encodeURIComponent(snippetPath)}\" \n                           target=\"_blank\" class=\"github-link\">\n                            <i class=\"fab fa-github\"></i>\n                            View on GitHub\n                        </a>\n                    </div>\n                `;\n\n                // Load and display README\n                if (readmeFile) {\n                    try {\n                        const readmeContent = await loadFileContent(readmeFile.download_url);\n                        const parsedReadme = parseMarkdown(readmeContent);\n                        html += `\n                            <div class=\"snippet-readme\">\n                                ${parsedReadme}\n                            </div>\n                        `;\n                    } catch (error) {\n                        console.error('Error loading README:', error);\n                    }\n                }\n\n                // Load and display code files\n                if (codeFiles.length > 0) {\n                    html += '<div class=\"snippet-files\">';\n                    \n                    // File tabs\n                    if (codeFiles.length > 1) {\n                        html += '<div class=\"file-tab-header\">';\n                        codeFiles.forEach((file, index) => {\n                            html += `\n                                <button class=\"file-tab ${index === 0 ? 'active' : ''}\" \n                                        onclick=\"switchFileTab(${index})\">\n                                    <i class=\"fas fa-file-code\"></i>\n                                    ${file.name}\n                                </button>\n                            `;\n                        });\n                        html += '</div>';\n                    }\n\n                    // File contents\n                    for (let i = 0; i < codeFiles.length; i++) {\n                        const file = codeFiles[i];\n                        try {\n                            const fileContent = await loadFileContent(file.download_url);\n                            const language = getLanguageFromFileName(file.name);\n                            \n                            html += `\n                                <div class=\"file-content ${i === 0 ? 'active' : ''}\" id=\"file-${i}\">\n                                    <pre><code class=\"language-${language}\">${escapeHtml(fileContent)}</code></pre>\n                                </div>\n                            `;\n                        } catch (error) {\n                            html += `\n                                <div class=\"file-content ${i === 0 ? 'active' : ''}\" id=\"file-${i}\">\n                                    <div class=\"error-message\">\n                                        <strong>Error loading ${file.name}:</strong> ${error.message}\n                                    </div>\n                                </div>\n                            `;\n                        }\n                    }\n                    \n                    html += '</div>';\n                }\n\n                if (!readmeFile && codeFiles.length === 0) {\n                    html += `\n                        <div class=\"error-message\">\n                            No README or code files found in this snippet.\n                        </div>\n                    `;\n                }\n\n                // Update modal content\n                snippetContent.innerHTML = html;\n                snippetContent.classList.add('active');\n                \n                // Hide loading, show content\n                loadingIndicator.style.display = 'none';\n                snippetContent.style.display = 'block';\n\n                // Re-run syntax highlighting\n                Prism.highlightAll();\n\n            } catch (error) {\n                console.error('Error loading snippet details:', error);\n                snippetContent.innerHTML = `\n                    <div class=\"error-message\">\n                        <strong>Error loading snippet:</strong> ${error.message}\n                    </div>\n                `;\n                loadingIndicator.style.display = 'none';\n                snippetContent.style.display = 'block';\n            }\n        }\n\n        function switchFileTab(index) {\n            // Update tab states\n            document.querySelectorAll('.file-tab').forEach((tab, i) => {\n                tab.classList.toggle('active', i === index);\n            });\n            \n            // Update content states\n            document.querySelectorAll('.file-content').forEach((content, i) => {\n                content.classList.toggle('active', i === index);\n            });\n        }\n\n        function closeSnippetModal() {\n            const modal = document.getElementById('snippet-modal');\n            modal.classList.remove('show');\n            \n            // Reset modal content\n            setTimeout(() => {\n                document.getElementById('snippet-content').innerHTML = '';\n                document.querySelector('.loading-indicator').style.display = 'flex';\n            }, 300);\n        }\n\n        function escapeHtml(text) {\n            const div = document.createElement('div');\n            div.textContent = text;\n            return div.innerHTML;\n        }\n\n        // Update subcategory cards to load real snippets\n        async function loadSubcategoryDetails(subcategory, cardElement) {\n            try {\n                const snippets = await loadSubcategorySnippets(subcategory);\n                const snippetList = cardElement.querySelector('.snippet-list');\n                const viewAllBtn = cardElement.querySelector('.view-all-btn');\n                \n                if (snippets.length > 0) {\n                    // Clear existing placeholder content\n                    snippetList.innerHTML = '';\n                    \n                    // Add real snippets (show first 4)\n                    const displaySnippets = snippets.slice(0, 4);\n                    displaySnippets.forEach(snippet => {\n                        const li = document.createElement('li');\n                        li.className = 'snippet-item';\n                        li.innerHTML = `\n                            <span class=\"snippet-name\">${snippet.name}</span>\n                            <span class=\"snippet-type\">${snippet.codeFiles.length} file${snippet.codeFiles.length !== 1 ? 's' : ''}</span>\n                        `;\n                        li.onclick = (e) => {\n                            e.stopPropagation();\n                            showSnippetDetails(snippet.path, snippet.name);\n                        };\n                        snippetList.appendChild(li);\n                    });\n                    \n                    // Update view all button\n                    if (snippets.length > 4) {\n                        viewAllBtn.textContent = `View All ${snippets.length} Examples`;\n                    } else {\n                        viewAllBtn.textContent = `View ${snippets.length} Example${snippets.length !== 1 ? 's' : ''}`;\n                    }\n                    \n                    // Store all snippets for \"view all\" functionality\n                    cardElement.dataset.allSnippets = JSON.stringify(snippets);\n                }\n            } catch (error) {\n                console.error(`Error loading ${subcategory} details:`, error);\n            }\n        }\n\n        function openGitHubFolder(path) {\n            const encodedPath = encodeURIComponent(path);\n            const githubUrl = `https://github.com/${REPO}/tree/${BRANCH}/${encodedPath}`;\n            window.open(githubUrl, '_blank');\n        }\n\n        // API descriptions for each category\n        const apiDescriptions = {\n            'GlideRecord': 'Database operations, queries, and record manipulation',\n            'GlideAjax': 'Client-server communication and asynchronous operations',\n            'GlideSystem': 'System utilities, logging, and platform services',\n            'GlideDate': 'Date manipulation utilities',\n            'GlideDateTime': 'Date and time manipulation utilities',\n            'GlideAggregate': 'Aggregate queries and statistical operations',\n            'GlideHTTPRequest': 'HTTP client operations and external API calls',\n            'GlideElement': 'Field-level operations and data manipulation',\n            'GlideFilter': 'Advanced filtering and query building',\n            'GlideQuery': 'Modern query API with fluent interface',\n            'GlideModal': 'Modal dialog operations and UI management',\n            'GlideTableDescriptor': 'Table metadata and schema operations'\n        };\n\n        function loadAllSubcategories() {\n            // Hardcoded list of all Core ServiceNow API categories\n            const subcategories = [\n                'GlideAggregate', 'GlideAjax', 'GlideDate', 'GlideDateTime',\n                'GlideElement', 'GlideFilter', 'GlideHTTPRequest', 'GlideModal',\n                'GlideQuery', 'GlideRecord', 'GlideSystem', 'GlideTableDescriptor'\n            ];\n            \n            // Generate the subcategory cards\n            const subcategoriesGrid = document.querySelector('.subcategories-grid');\n            const additionalGrid = document.querySelector('.subcategories-grid:last-of-type');\n            \n            // Clear existing content\n            subcategoriesGrid.innerHTML = '';\n            if (additionalGrid && additionalGrid !== subcategoriesGrid) {\n                additionalGrid.innerHTML = '';\n            }\n            \n            // Create cards for all subcategories\n            subcategories.forEach((subcategory, index) => {\n                const card = createSubcategoryCard(subcategory);\n                \n                // Distribute cards across grids for better layout\n                if (index < 6) {\n                    subcategoriesGrid.appendChild(card);\n                } else if (additionalGrid) {\n                    additionalGrid.appendChild(card);\n                } else {\n                    subcategoriesGrid.appendChild(card);\n                }\n            });\n        }\n\n        function createSubcategoryCard(subcategory) {\n            const card = document.createElement('div');\n            card.className = 'subcategory-card';\n            card.onclick = () => openGitHubFolder(`Core ServiceNow APIs/${subcategory}`);\n            \n            const description = apiDescriptions[subcategory] || 'ServiceNow API utilities and operations';\n            \n            card.innerHTML = `\n                <div class=\"subcategory-header\">\n                    <h3 class=\"subcategory-title\">${subcategory}</h3>\n                    <p class=\"subcategory-description\">${description}</p>\n                </div>\n                <div class=\"subcategory-content\">\n                    <button class=\"view-all-btn\">View ${subcategory} Examples on GitHub</button>\n                </div>\n            `;\n            \n            return card;\n        }\n\n        // Initialize page\n        document.addEventListener('DOMContentLoaded', function() {\n            // Initialize syntax highlighting\n            Prism.highlightAll();\n            \n            // Load all subcategories\n            loadAllSubcategories();\n\n            // Close modal when clicking outside\n            document.getElementById('snippet-modal').addEventListener('click', function(e) {\n                if (e.target === this) {\n                    closeSnippetModal();\n                }\n            });\n\n            // Close modal with Escape key\n            document.addEventListener('keydown', function(e) {\n                if (e.key === 'Escape') {\n                    closeSnippetModal();\n                }\n            });\n        });\n    </script>\n    <!-- Footer will be loaded by footer.js -->\n    <script src=\"../assets/footer.js\" data-auto-init data-root-page=\"false\"></script>\n</body>\n</html>"
  },
  {
    "path": "pages/integration.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Integration | ServiceNow Code Snippets</title>\n    <meta name=\"description\" content=\"ServiceNow integration patterns including REST APIs, attachments, mail scripts, MID Server operations, and external system connectivity.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* Reuse the CSS from core-apis.html */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Navigation */\n        .nav {\n            background: var(--bg-primary);\n            border-bottom: 1px solid var(--border-light);\n            padding: 1rem 0;\n            position: sticky;\n            top: 0;\n            z-index: 100;\n            backdrop-filter: blur(10px);\n        }\n\n        .nav-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .nav-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            text-decoration: none;\n            color: var(--text-primary);\n            font-weight: 600;\n        }\n\n        .nav-brand i {\n            color: var(--primary-color);\n        }\n\n        .breadcrumb {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .breadcrumb a {\n            color: var(--primary-color);\n            text-decoration: none;\n        }\n\n        /* Header */\n        .page-header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 3rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .page-header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .page-header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .page-header h1 {\n            font-size: 2.5rem;\n            font-weight: 700;\n            margin-bottom: 1rem;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .page-header-icon {\n            width: 64px;\n            height: 64px;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 2rem;\n        }\n\n        .page-header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            max-width: 600px;\n        }\n\n        /* Main Content */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .subcategories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 2rem;\n        }\n\n        .subcategory-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            overflow: hidden;\n            transition: all 0.3s ease;\n            cursor: pointer;\n        }\n\n        .subcategory-card:hover {\n            transform: translateY(-2px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .subcategory-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .subcategory-title {\n            font-size: 1.2rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .subcategory-description {\n            color: var(--text-secondary);\n            font-size: 0.9rem;\n        }\n\n        .subcategory-content {\n            padding: 1.5rem;\n        }\n\n        .view-all-btn {\n            width: 100%;\n            padding: 1rem;\n            background: var(--primary-color);\n            color: white;\n            border: none;\n            border-radius: var(--border-radius);\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n        }\n\n        .view-all-btn:hover {\n            background: #1557b0;\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .page-header h1 {\n                font-size: 2rem;\n                flex-direction: column;\n                text-align: center;\n            }\n            \n            .nav-content {\n                flex-direction: column;\n                gap: 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .subcategories-grid {\n                grid-template-columns: 1fr;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Navigation -->\n    <nav class=\"nav\">\n        <div class=\"nav-content\">\n            <a href=\"../index.html\" class=\"nav-brand\">\n                <i class=\"fas fa-code\"></i>\n                ServiceNow Code Snippets\n            </a>\n            <div class=\"breadcrumb\">\n                <a href=\"../index.html\">Home</a>\n                <i class=\"fas fa-chevron-right\"></i>\n                <span>Integration</span>\n            </div>\n        </div>\n    </nav>\n\n    <!-- Page Header -->\n    <header class=\"page-header\">\n        <div class=\"page-header-content\">\n            <h1>\n                <div class=\"page-header-icon\">\n                    <i class=\"fas fa-link\"></i>\n                </div>\n                Integration\n            </h1>\n            <p>\n                ServiceNow integration patterns and external system connectivity including REST APIs, attachments, \n                mail scripts, MID Server operations, and data exchange with external systems.\n            </p>\n        </div>\n    </header>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <!-- Subcategories -->\n        <div class=\"subcategories-grid\">\n            <!-- Categories will be loaded dynamically -->\n        </div>\n    </main>\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    \n    <script>\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        function openGitHubFolder(path) {\n            const encodedPath = encodeURIComponent(path);\n            const githubUrl = `https://github.com/${REPO}/tree/${BRANCH}/${encodedPath}`;\n            window.open(githubUrl, '_blank');\n        }\n\n        // Category descriptions for integration\n        const categoryDescriptions = {\n            'Attachments': 'File attachment handling, processing, and manipulation',\n            'AzureAD Integration': 'Azure Active Directory integration patterns',\n            'GraphQL Integration API': 'GraphQL API integration and resolver patterns',\n            'ITSM': 'IT Service Management integration utilities',\n            'Import Set API': 'Data import and transformation via Import Sets',\n            'Import Sets Debug': 'Debugging and troubleshooting import operations',\n            'Import Sets': 'General import set patterns and utilities',\n            'MIDServer': 'MID Server operations and remote system connectivity',\n            'Mail Scripts': 'Email processing and mail server integration',\n            'RESTMessageV2': 'REST API integration and message handling',\n            'Rest Integration Send Attachment Payload': 'REST API attachment handling patterns',\n            'Scripted REST Api': 'Custom REST API endpoints and services',\n            'Scripted SOAP Incident Creation': 'SOAP web service integration patterns'\n        };\n\n        function loadAllSubcategories() {\n            // Hardcoded list of all Integration categories\n            const subcategories = [\n                'Attachments', 'AzureAD Integration', 'GraphQL Integration API', 'ITSM',\n                'Import Set API', 'Import Sets Debug', 'Import Sets', 'MIDServer',\n                'Mail Scripts', 'RESTMessageV2', 'Rest Integration Send Attachment Payload',\n                'Scripted REST Api', 'Scripted SOAP Incident Creation'\n            ];\n            \n            // Generate the subcategory cards\n            const subcategoriesGrid = document.querySelector('.subcategories-grid');\n            \n            // Clear existing content\n            subcategoriesGrid.innerHTML = '';\n            \n            // Create cards for all subcategories\n            subcategories.forEach((subcategory) => {\n                const card = createSubcategoryCard(subcategory);\n                subcategoriesGrid.appendChild(card);\n            });\n        }\n\n        function createSubcategoryCard(subcategory) {\n            const card = document.createElement('div');\n            card.className = 'subcategory-card';\n            card.onclick = () => openGitHubFolder(`Integration/${subcategory}`);\n            \n            const description = categoryDescriptions[subcategory] || 'Integration utilities and patterns';\n            \n            card.innerHTML = `\n                <div class=\"subcategory-header\">\n                    <h3 class=\"subcategory-title\">${subcategory}</h3>\n                    <p class=\"subcategory-description\">${description}</p>\n                </div>\n                <div class=\"subcategory-content\">\n                    <button class=\"view-all-btn\">View ${subcategory} Examples on GitHub</button>\n                </div>\n            `;\n            \n            return card;\n        }\n\n        // Initialize page\n        document.addEventListener('DOMContentLoaded', function() {\n            // Initialize syntax highlighting\n            Prism.highlightAll();\n            \n            // Load all subcategories\n            loadAllSubcategories();\n        });\n    </script>\n    <!-- Footer will be loaded by footer.js -->\n    <script src=\"../assets/footer.js\" data-auto-init data-root-page=\"false\"></script>\n</body>\n</html>"
  },
  {
    "path": "pages/modern-development.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Modern Development | ServiceNow Code Snippets</title>\n    <meta name=\"description\" content=\"Modern ServiceNow development frameworks including Service Portal, NOW Experience, GraphQL, and ECMAScript 2021 features.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* Reuse the CSS from core-apis.html */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Navigation */\n        .nav {\n            background: var(--bg-primary);\n            border-bottom: 1px solid var(--border-light);\n            padding: 1rem 0;\n            position: sticky;\n            top: 0;\n            z-index: 100;\n            backdrop-filter: blur(10px);\n        }\n\n        .nav-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .nav-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            text-decoration: none;\n            color: var(--text-primary);\n            font-weight: 600;\n        }\n\n        .nav-brand i {\n            color: var(--primary-color);\n        }\n\n        .breadcrumb {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .breadcrumb a {\n            color: var(--primary-color);\n            text-decoration: none;\n        }\n\n        /* Header */\n        .page-header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 3rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .page-header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .page-header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .page-header h1 {\n            font-size: 2.5rem;\n            font-weight: 700;\n            margin-bottom: 1rem;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .page-header-icon {\n            width: 64px;\n            height: 64px;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 2rem;\n        }\n\n        .page-header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            max-width: 600px;\n        }\n\n        /* Main Content */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .subcategories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 2rem;\n        }\n\n        .subcategory-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            overflow: hidden;\n            transition: all 0.3s ease;\n            cursor: pointer;\n        }\n\n        .subcategory-card:hover {\n            transform: translateY(-2px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .subcategory-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .subcategory-title {\n            font-size: 1.2rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .subcategory-description {\n            color: var(--text-secondary);\n            font-size: 0.9rem;\n        }\n\n        .subcategory-content {\n            padding: 1.5rem;\n        }\n\n        .view-all-btn {\n            width: 100%;\n            padding: 1rem;\n            background: var(--primary-color);\n            color: white;\n            border: none;\n            border-radius: var(--border-radius);\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n        }\n\n        .view-all-btn:hover {\n            background: #1557b0;\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .page-header h1 {\n                font-size: 2rem;\n                flex-direction: column;\n                text-align: center;\n            }\n            \n            .nav-content {\n                flex-direction: column;\n                gap: 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .subcategories-grid {\n                grid-template-columns: 1fr;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Navigation -->\n    <nav class=\"nav\">\n        <div class=\"nav-content\">\n            <a href=\"../index.html\" class=\"nav-brand\">\n                <i class=\"fas fa-code\"></i>\n                ServiceNow Code Snippets\n            </a>\n            <div class=\"breadcrumb\">\n                <a href=\"../index.html\">Home</a>\n                <i class=\"fas fa-chevron-right\"></i>\n                <span>Modern Development</span>\n            </div>\n        </div>\n    </nav>\n\n    <!-- Page Header -->\n    <header class=\"page-header\">\n        <div class=\"page-header-content\">\n            <h1>\n                <div class=\"page-header-icon\">\n                    <i class=\"fas fa-rocket\"></i>\n                </div>\n                Modern Development\n            </h1>\n            <p>\n                Modern ServiceNow development frameworks and technologies including Service Portal, NOW Experience, \n                GraphQL APIs, and modern JavaScript features for building contemporary applications.\n            </p>\n        </div>\n    </header>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <!-- Subcategories -->\n        <div class=\"subcategories-grid\">\n            <!-- Categories will be loaded dynamically -->\n        </div>\n    </main>\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    \n    <script>\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        function openGitHubFolder(path) {\n            const encodedPath = encodeURIComponent(path);\n            const githubUrl = `https://github.com/${REPO}/tree/${BRANCH}/${encodedPath}`;\n            window.open(githubUrl, '_blank');\n        }\n\n        // Category descriptions for modern development\n        const categoryDescriptions = {\n            'Service Portal': 'Service Portal pages, widgets, and client-side development',\n            'Service Portal Widgets': 'Custom widgets for Service Portal applications',\n            'NOW Experience': 'NOW Experience UI components and development patterns',\n            'GraphQL': 'GraphQL API integration and query patterns',\n            'ECMASCript 2021': 'Modern JavaScript features and ES2021 syntax examples'\n        };\n\n        function loadAllSubcategories() {\n            // Hardcoded list of all Modern Development categories\n            const subcategories = [\n                'Service Portal', 'Service Portal Widgets', 'NOW Experience', 'GraphQL', 'ECMASCript 2021'\n            ];\n            \n            // Generate the subcategory cards\n            const subcategoriesGrid = document.querySelector('.subcategories-grid');\n            \n            // Clear existing content\n            subcategoriesGrid.innerHTML = '';\n            \n            // Create cards for all subcategories\n            subcategories.forEach((subcategory) => {\n                const card = createSubcategoryCard(subcategory);\n                subcategoriesGrid.appendChild(card);\n            });\n        }\n\n        function createSubcategoryCard(subcategory) {\n            const card = document.createElement('div');\n            card.className = 'subcategory-card';\n            card.onclick = () => openGitHubFolder(`Modern Development/${subcategory}`);\n            \n            const description = categoryDescriptions[subcategory] || 'Modern development utilities and patterns';\n            \n            card.innerHTML = `\n                <div class=\"subcategory-header\">\n                    <h3 class=\"subcategory-title\">${subcategory}</h3>\n                    <p class=\"subcategory-description\">${description}</p>\n                </div>\n                <div class=\"subcategory-content\">\n                    <button class=\"view-all-btn\">View ${subcategory} Examples on GitHub</button>\n                </div>\n            `;\n            \n            return card;\n        }\n\n        // Initialize page\n        document.addEventListener('DOMContentLoaded', function() {\n            // Initialize syntax highlighting\n            Prism.highlightAll();\n            \n            // Load all subcategories\n            loadAllSubcategories();\n        });\n    </script>\n    <!-- Footer will be loaded by footer.js -->\n    <script src=\"../assets/footer.js\" data-auto-init data-root-page=\"false\"></script>\n</body>\n</html>"
  },
  {
    "path": "pages/server-side-components.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Server-Side Components | ServiceNow Code Snippets</title>\n    <meta name=\"description\" content=\"Server-executed ServiceNow code components including Background Scripts, Business Rules, Script Includes, and server-side development patterns.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* Reuse the CSS from core-apis.html */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Navigation */\n        .nav {\n            background: var(--bg-primary);\n            border-bottom: 1px solid var(--border-light);\n            padding: 1rem 0;\n            position: sticky;\n            top: 0;\n            z-index: 100;\n            backdrop-filter: blur(10px);\n        }\n\n        .nav-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .nav-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            text-decoration: none;\n            color: var(--text-primary);\n            font-weight: 600;\n        }\n\n        .nav-brand i {\n            color: var(--primary-color);\n        }\n\n        .breadcrumb {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .breadcrumb a {\n            color: var(--primary-color);\n            text-decoration: none;\n        }\n\n        /* Header */\n        .page-header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 3rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .page-header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .page-header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .page-header h1 {\n            font-size: 2.5rem;\n            font-weight: 700;\n            margin-bottom: 1rem;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .page-header-icon {\n            width: 64px;\n            height: 64px;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 2rem;\n        }\n\n        .page-header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            max-width: 600px;\n        }\n\n        /* Main Content */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .subcategories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 2rem;\n        }\n\n        .subcategory-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            overflow: hidden;\n            transition: all 0.3s ease;\n            cursor: pointer;\n        }\n\n        .subcategory-card:hover {\n            transform: translateY(-2px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .subcategory-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .subcategory-title {\n            font-size: 1.2rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .subcategory-description {\n            color: var(--text-secondary);\n            font-size: 0.9rem;\n        }\n\n        .subcategory-content {\n            padding: 1.5rem;\n        }\n\n        .view-all-btn {\n            width: 100%;\n            padding: 1rem;\n            background: var(--primary-color);\n            color: white;\n            border: none;\n            border-radius: var(--border-radius);\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n        }\n\n        .view-all-btn:hover {\n            background: #1557b0;\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .page-header h1 {\n                font-size: 2rem;\n                flex-direction: column;\n                text-align: center;\n            }\n            \n            .nav-content {\n                flex-direction: column;\n                gap: 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .subcategories-grid {\n                grid-template-columns: 1fr;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Navigation -->\n    <nav class=\"nav\">\n        <div class=\"nav-content\">\n            <a href=\"../index.html\" class=\"nav-brand\">\n                <i class=\"fas fa-code\"></i>\n                ServiceNow Code Snippets\n            </a>\n            <div class=\"breadcrumb\">\n                <a href=\"../index.html\">Home</a>\n                <i class=\"fas fa-chevron-right\"></i>\n                <span>Server-Side Components</span>\n            </div>\n        </div>\n    </nav>\n\n    <!-- Page Header -->\n    <header class=\"page-header\">\n        <div class=\"page-header-content\">\n            <h1>\n                <div class=\"page-header-icon\">\n                    <i class=\"fas fa-server\"></i>\n                </div>\n                Server-Side Components\n            </h1>\n            <p>\n                Server-executed ServiceNow code components including Background Scripts, Business Rules, Script Includes, \n                and other server-side development patterns for automating business logic and system operations.\n            </p>\n        </div>\n    </header>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <!-- Subcategories -->\n        <div class=\"subcategories-grid\">\n            <!-- Categories will be loaded dynamically -->\n        </div>\n    </main>\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    \n    <script>\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        function openGitHubFolder(path) {\n            const encodedPath = encodeURIComponent(path);\n            const githubUrl = `https://github.com/${REPO}/tree/${BRANCH}/${encodedPath}`;\n            window.open(githubUrl, '_blank');\n        }\n\n        // Category descriptions for server-side components\n        const categoryDescriptions = {\n            'Background Scripts': 'Administrative scripts for bulk operations and system maintenance',\n            'Business Rules': 'Event-driven server-side logic for database operations',\n            'Script Includes': 'Reusable server-side libraries and utility functions',\n            'Script Actions': 'Custom actions for workflows and business processes',\n            'Scheduled Jobs': 'Automated recurring tasks and maintenance scripts',\n            'Transform Map Scripts': 'Data transformation logic for import operations',\n            'Server Side': 'General server-side development patterns and utilities',\n            'Inbound Actions': 'Email processing and inbound communication handling',\n            'Processors': 'Custom URL endpoints and HTTP request processing'\n        };\n\n        function loadAllSubcategories() {\n            // Hardcoded list of all Server-Side Component categories\n            const subcategories = [\n                'Background Scripts', 'Business Rules', 'Script Includes', 'Script Actions',\n                'Scheduled Jobs', 'Transform Map Scripts', 'Server Side', 'Inbound Actions', 'Processors'\n            ];\n            \n            // Generate the subcategory cards\n            const subcategoriesGrid = document.querySelector('.subcategories-grid');\n            \n            // Clear existing content\n            subcategoriesGrid.innerHTML = '';\n            \n            // Create cards for all subcategories\n            subcategories.forEach((subcategory) => {\n                const card = createSubcategoryCard(subcategory);\n                subcategoriesGrid.appendChild(card);\n            });\n        }\n\n        function createSubcategoryCard(subcategory) {\n            const card = document.createElement('div');\n            card.className = 'subcategory-card';\n            card.onclick = () => openGitHubFolder(`Server-Side Components/${subcategory}`);\n            \n            const description = categoryDescriptions[subcategory] || 'Server-side development utilities and patterns';\n            \n            card.innerHTML = `\n                <div class=\"subcategory-header\">\n                    <h3 class=\"subcategory-title\">${subcategory}</h3>\n                    <p class=\"subcategory-description\">${description}</p>\n                </div>\n                <div class=\"subcategory-content\">\n                    <button class=\"view-all-btn\">View ${subcategory} Examples on GitHub</button>\n                </div>\n            `;\n            \n            return card;\n        }\n\n        // Initialize page\n        document.addEventListener('DOMContentLoaded', function() {\n            // Initialize syntax highlighting\n            Prism.highlightAll();\n            \n            // Load all subcategories\n            loadAllSubcategories();\n        });\n    </script>\n    <!-- Footer will be loaded by footer.js -->\n    <script src=\"../assets/footer.js\" data-auto-init data-root-page=\"false\"></script>\n</body>\n</html>"
  },
  {
    "path": "pages/specialized-areas.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <title>Specialized Areas | ServiceNow Code Snippets</title>\n    <meta name=\"description\" content=\"Specialized ServiceNow development areas including CMDB, ITOM, Performance Analytics, ATF testing, and domain-specific functionality.\">\n    \n    <!-- Fonts -->\n    <link href=\"https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap\" rel=\"stylesheet\">\n    \n    <!-- Syntax Highlighting -->\n    <link rel=\"stylesheet\" href=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/themes/prism-tomorrow.min.css\">\n    \n    <!-- Icons -->\n    <link href=\"https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css\" rel=\"stylesheet\">\n    \n    <style>\n        /* Reuse the CSS from core-apis.html */\n        :root {\n            --primary-color: #1a73e8;\n            --secondary-color: #34a853;\n            --accent-color: #fbbc04;\n            --danger-color: #ea4335;\n            \n            --bg-primary: #ffffff;\n            --bg-secondary: #f8f9fa;\n            --bg-tertiary: #e8f0fe;\n            \n            --text-primary: #202124;\n            --text-secondary: #5f6368;\n            --text-muted: #80868b;\n            \n            --border-color: #dadce0;\n            --border-light: #f1f3f4;\n            \n            --shadow-sm: 0 1px 2px 0 rgba(60,64,67,0.3), 0 1px 3px 1px rgba(60,64,67,0.15);\n            --shadow-md: 0 1px 2px 0 rgba(60,64,67,0.3), 0 2px 6px 2px rgba(60,64,67,0.15);\n            --shadow-lg: 0 2px 4px -1px rgba(60,64,67,0.3), 0 4px 8px 0 rgba(60,64,67,0.15);\n            \n            --border-radius: 8px;\n            --font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n            --font-mono: 'JetBrains Mono', 'SF Mono', Monaco, 'Cascadia Code', monospace;\n        }\n\n        @media (prefers-color-scheme: dark) {\n            :root {\n                --bg-primary: #121212;\n                --bg-secondary: #1e1e1e;\n                --bg-tertiary: #2d2d2d;\n                \n                --text-primary: #e8eaed;\n                --text-secondary: #9aa0a6;\n                --text-muted: #5f6368;\n                \n                --border-color: #3c4043;\n                --border-light: #2d2d2d;\n            }\n        }\n\n        * {\n            margin: 0;\n            padding: 0;\n            box-sizing: border-box;\n        }\n\n        body {\n            font-family: var(--font-family);\n            background-color: var(--bg-primary);\n            color: var(--text-primary);\n            line-height: 1.6;\n        }\n\n        /* Navigation */\n        .nav {\n            background: var(--bg-primary);\n            border-bottom: 1px solid var(--border-light);\n            padding: 1rem 0;\n            position: sticky;\n            top: 0;\n            z-index: 100;\n            backdrop-filter: blur(10px);\n        }\n\n        .nav-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            display: flex;\n            align-items: center;\n            justify-content: space-between;\n        }\n\n        .nav-brand {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            text-decoration: none;\n            color: var(--text-primary);\n            font-weight: 600;\n        }\n\n        .nav-brand i {\n            color: var(--primary-color);\n        }\n\n        .breadcrumb {\n            display: flex;\n            align-items: center;\n            gap: 0.5rem;\n            font-size: 0.9rem;\n            color: var(--text-secondary);\n        }\n\n        .breadcrumb a {\n            color: var(--primary-color);\n            text-decoration: none;\n        }\n\n        /* Header */\n        .page-header {\n            background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));\n            color: white;\n            padding: 3rem 0;\n            position: relative;\n            overflow: hidden;\n        }\n\n        .page-header::before {\n            content: '';\n            position: absolute;\n            top: 0;\n            left: 0;\n            right: 0;\n            bottom: 0;\n            background: url('data:image/svg+xml,<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 100 100\"><defs><pattern id=\"grid\" width=\"10\" height=\"10\" patternUnits=\"userSpaceOnUse\"><path d=\"M 10 0 L 0 0 0 10\" fill=\"none\" stroke=\"rgba(255,255,255,0.1)\" stroke-width=\"0.5\"/></pattern></defs><rect width=\"100\" height=\"100\" fill=\"url(%23grid)\"/></svg>');\n            opacity: 0.3;\n        }\n\n        .page-header-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 0 2rem;\n            position: relative;\n            z-index: 1;\n        }\n\n        .page-header h1 {\n            font-size: 2.5rem;\n            font-weight: 700;\n            margin-bottom: 1rem;\n            display: flex;\n            align-items: center;\n            gap: 1rem;\n        }\n\n        .page-header-icon {\n            width: 64px;\n            height: 64px;\n            background: rgba(255, 255, 255, 0.2);\n            border-radius: var(--border-radius);\n            display: flex;\n            align-items: center;\n            justify-content: center;\n            font-size: 2rem;\n        }\n\n        .page-header p {\n            font-size: 1.2rem;\n            opacity: 0.9;\n            max-width: 600px;\n        }\n\n        /* Main Content */\n        .main-content {\n            max-width: 1200px;\n            margin: 0 auto;\n            padding: 3rem 2rem;\n        }\n\n        .subcategories-grid {\n            display: grid;\n            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));\n            gap: 2rem;\n        }\n\n        .subcategory-card {\n            background: var(--bg-primary);\n            border: 1px solid var(--border-color);\n            border-radius: var(--border-radius);\n            overflow: hidden;\n            transition: all 0.3s ease;\n            cursor: pointer;\n        }\n\n        .subcategory-card:hover {\n            transform: translateY(-2px);\n            box-shadow: var(--shadow-lg);\n            border-color: var(--primary-color);\n        }\n\n        .subcategory-header {\n            background: var(--bg-secondary);\n            padding: 1.5rem;\n            border-bottom: 1px solid var(--border-light);\n        }\n\n        .subcategory-title {\n            font-size: 1.2rem;\n            font-weight: 600;\n            margin-bottom: 0.5rem;\n            color: var(--text-primary);\n        }\n\n        .subcategory-description {\n            color: var(--text-secondary);\n            font-size: 0.9rem;\n        }\n\n        .subcategory-content {\n            padding: 1.5rem;\n        }\n\n        .view-all-btn {\n            width: 100%;\n            padding: 1rem;\n            background: var(--primary-color);\n            color: white;\n            border: none;\n            border-radius: var(--border-radius);\n            font-weight: 500;\n            cursor: pointer;\n            transition: background-color 0.2s ease;\n        }\n\n        .view-all-btn:hover {\n            background: #1557b0;\n        }\n\n        /* Responsive */\n        @media (max-width: 768px) {\n            .page-header h1 {\n                font-size: 2rem;\n                flex-direction: column;\n                text-align: center;\n            }\n            \n            .nav-content {\n                flex-direction: column;\n                gap: 1rem;\n            }\n            \n            .main-content {\n                padding: 2rem 1rem;\n            }\n            \n            .subcategories-grid {\n                grid-template-columns: 1fr;\n            }\n        }\n    </style>\n</head>\n<body>\n    <!-- Navigation -->\n    <nav class=\"nav\">\n        <div class=\"nav-content\">\n            <a href=\"../index.html\" class=\"nav-brand\">\n                <i class=\"fas fa-code\"></i>\n                ServiceNow Code Snippets\n            </a>\n            <div class=\"breadcrumb\">\n                <a href=\"../index.html\">Home</a>\n                <i class=\"fas fa-chevron-right\"></i>\n                <span>Specialized Areas</span>\n            </div>\n        </div>\n    </nav>\n\n    <!-- Page Header -->\n    <header class=\"page-header\">\n        <div class=\"page-header-content\">\n            <h1>\n                <div class=\"page-header-icon\">\n                    <i class=\"fas fa-puzzle-piece\"></i>\n                </div>\n                Specialized Areas\n            </h1>\n            <p>\n                Specialized ServiceNow development areas and domain-specific functionality including CMDB, ITOM, \n                Performance Analytics, testing frameworks, and advanced platform capabilities.\n            </p>\n        </div>\n    </header>\n\n    <!-- Main Content -->\n    <main class=\"main-content\">\n        <!-- Subcategories -->\n        <div class=\"subcategories-grid\">\n            <!-- Categories will be loaded dynamically -->\n        </div>\n    </main>\n\n    <!-- Scripts -->\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-core.min.js\"></script>\n    <script src=\"https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/plugins/autoloader/prism-autoloader.min.js\"></script>\n    \n    <script>\n        // GitHub API configuration\n        const GITHUB_API_BASE = 'https://api.github.com';\n        const REPO = 'ServiceNowDevProgram/code-snippets';\n        const BRANCH = 'main';\n\n        function openGitHubFolder(path) {\n            const encodedPath = encodeURIComponent(path);\n            const githubUrl = `https://github.com/${REPO}/tree/${BRANCH}/${encodedPath}`;\n            window.open(githubUrl, '_blank');\n        }\n\n        // Category descriptions for specialized areas\n        const categoryDescriptions = {\n            'ATF Steps': 'Automated Test Framework custom test steps and utilities',\n            'Advanced Conditions': 'Complex condition logic and advanced filtering patterns',\n            'Agile Development': 'Agile project management and development utilities',\n            'Browser Bookmarklets': 'Browser bookmarklets for ServiceNow development productivity',\n            'Browser Utilities': 'Browser-based utilities and development tools',\n            'CMDB': 'Configuration Management Database utilities and operations',\n            'Dynamic Filters': 'Dynamic filtering and query building patterns',\n            'Fix scripts': 'Data repair and system maintenance scripts',\n            'Flow Actions': 'Custom Flow Designer actions and integrations',\n            'Formula Builder': 'Custom formulas and calculation patterns',\n            'ITOM': 'IT Operations Management utilities and integrations',\n            'Notifications': 'Custom notification templates and delivery patterns',\n            'On-Call Calendar': 'On-call scheduling and calendar management',\n            'Performance Analytics': 'Performance Analytics widgets and data collection',\n            'Record Producer': 'Service Catalog record producer customizations',\n            'Regular Expressions': 'Regular expression patterns and utilities',\n            'Styles': 'CSS styling and UI customization patterns'\n        };\n\n        function loadAllSubcategories() {\n            // Hardcoded list of all Specialized Areas categories\n            const subcategories = [\n                'ATF Steps', 'Advanced Conditions', 'Agile Development', 'Browser Bookmarklets',\n                'Browser Utilities', 'CMDB', 'Dynamic Filters', 'Fix scripts', 'Flow Actions',\n                'Formula Builder', 'ITOM', 'Notifications', 'On-Call Calendar',\n                'Performance Analytics', 'Record Producer', 'Regular Expressions', 'Styles'\n            ];\n            \n            // Generate the subcategory cards\n            const subcategoriesGrid = document.querySelector('.subcategories-grid');\n            \n            // Clear existing content\n            subcategoriesGrid.innerHTML = '';\n            \n            // Create cards for all subcategories\n            subcategories.forEach((subcategory) => {\n                const card = createSubcategoryCard(subcategory);\n                subcategoriesGrid.appendChild(card);\n            });\n        }\n\n        function createSubcategoryCard(subcategory) {\n            const card = document.createElement('div');\n            card.className = 'subcategory-card';\n            card.onclick = () => openGitHubFolder(`Specialized Areas/${subcategory}`);\n            \n            const description = categoryDescriptions[subcategory] || 'Specialized development utilities and patterns';\n            \n            card.innerHTML = `\n                <div class=\"subcategory-header\">\n                    <h3 class=\"subcategory-title\">${subcategory}</h3>\n                    <p class=\"subcategory-description\">${description}</p>\n                </div>\n                <div class=\"subcategory-content\">\n                    <button class=\"view-all-btn\">View ${subcategory} Examples on GitHub</button>\n                </div>\n            `;\n            \n            return card;\n        }\n\n        // Initialize page\n        document.addEventListener('DOMContentLoaded', function() {\n            // Initialize syntax highlighting\n            Prism.highlightAll();\n            \n            // Load all subcategories\n            loadAllSubcategories();\n        });\n    </script>\n    <!-- Footer will be loaded by footer.js -->\n    <script src=\"../assets/footer.js\" data-auto-init data-root-page=\"false\"></script>\n</body>\n</html>"
  },
  {
    "path": "sitemap.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n  <url>\n    <loc>https://servicenowdevprogram.github.io/code-snippets/</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>1.0</priority>\n  </url>\n  \n  <url>\n    <loc>https://servicenowdevprogram.github.io/code-snippets/core-apis.html</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.8</priority>\n  </url>\n  \n  <!-- GitHub repository links for categories -->\n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Core%20ServiceNow%20APIs</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.9</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Server-Side%20Components</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.9</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Client-Side%20Components</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.9</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Modern%20Development</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.9</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Integration</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.9</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/tree/main/Specialized%20Areas</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>weekly</changefreq>\n    <priority>0.9</priority>\n  </url>\n  \n  <!-- Important documentation -->\n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/blob/main/README.md</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>monthly</changefreq>\n    <priority>0.7</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CONTRIBUTING.md</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>monthly</changefreq>\n    <priority>0.7</priority>\n  </url>\n  \n  <url>\n    <loc>https://github.com/ServiceNowDevProgram/code-snippets/blob/main/CLAUDE.md</loc>\n    <lastmod>2024-07-02</lastmod>\n    <changefreq>monthly</changefreq>\n    <priority>0.6</priority>\n  </url>\n</urlset>"
  }
]