diff --git a/README.md b/README.md index 541b959..bc659e6 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Atomic Skills for Claude Code [![Claude Code Plugin](https://img.shields.io/badge/Claude%20Code-Plugin-blue)](https://docs.claude.com/en/docs/claude-code/plugins) -[![Skills](https://img.shields.io/badge/Skills-447-green)](skills/) +[![Skills](https://img.shields.io/badge/Skills-454-green)](skills/) [![License](https://img.shields.io/badge/License-MIT-yellow)](LICENSE) **Context-efficient development knowledge via progressive skill discovery** @@ -42,7 +42,7 @@ This creates an challenging tradeoff between comprehensive coverage and context ## The Solution: Atomic Skills + Progressive Discovery -### Atomic Skills (447 total) +### Atomic Skills (454 total) Each skill is **focused, self-contained, and composable**: - Average 320 lines - small enough to load quickly - Single responsibility - covers one clear topic @@ -53,7 +53,7 @@ Each skill is **focused, self-contained, and composable**: ### Three-Tier Architecture -**Tier 1: Gateway Skills (31 auto-discovered Agent Skills)** +**Tier 1: Gateway Skills (32 auto-discovered Agent Skills)** Lightweight entry points that activate automatically based on keywords: - `discover-api` → triggers on "REST", "GraphQL", "authentication" - `discover-database` → triggers on "PostgreSQL", "MongoDB", "Redis" @@ -61,7 +61,7 @@ Lightweight entry points that activate automatically based on keywords: Each gateway is ~200 lines with quick reference and loading commands. -**Tier 2: Category Indexes (30 detailed references)** +**Tier 2: Category Indexes (31 detailed references)** Full skill listings with descriptions, use cases, and workflows: - `api/INDEX.md` → All 7 API skills with integration patterns - `database/INDEX.md` → All 11 database skills with decision trees @@ -91,7 +91,7 @@ Complete implementation guides loaded only when needed: ## What's Covered -### Core Development (125 skills) +### Core Development (132 skills) **Languages & Frameworks**: - **Backend**: Python (uv), Zig, Rust, Go (31 skills) @@ -110,6 +110,7 @@ Complete implementation guides loaded only when needed: - **Databases**: Postgres, MongoDB, Redis, Redpanda, Iceberg, DuckDB (11 skills) - **Caching**: Multi-layer (browser, HTTP, CDN, Redis), invalidation strategies (7 skills) - **API Design**: REST, GraphQL, auth, rate limiting, versioning (7 skills) +- **Salesforce**: sf CLI, GUS/Agile Accelerator, SOQL, work items, Chatter, automation (7 skills) - **Data Engineering**: ETL, streaming, batch processing (5 skills) - **Testing**: Unit, integration, e2e, TDD, coverage (6 skills) @@ -269,13 +270,14 @@ cat skills/database/redpanda-streaming.md │ ├── README.md # This file │ -└── skills/ # 283 atomic skills, 31 gateways, 30 categories +└── skills/ # 290 atomic skills, 32 gateways, 31 categories │ ├── README.md # Skills catalog │ - ├── Gateway Skills (31 auto-discovered Agent Skills) + ├── Gateway Skills (32 auto-discovered Agent Skills) │ ├── discover-api/ # REST, GraphQL, authentication │ ├── discover-database/ # PostgreSQL, MongoDB, Redis + │ ├── discover-salesforce/ # Salesforce CLI, GUS, SOQL │ ├── discover-frontend/ # React, Next.js, UI components │ ├── discover-ml/ # Machine learning, models │ ├── discover-math/ # Mathematics, algorithms @@ -287,15 +289,17 @@ cat skills/database/redpanda-streaming.md │ ├── discover-workflow/ # Beads, context strategies │ └── ... 20 more gateways │ - ├── Category Indexes (30 detailed references) + ├── Category Indexes (31 detailed references) │ ├── api/INDEX.md # All API skills overview │ ├── database/INDEX.md # All database skills overview + │ ├── salesforce/INDEX.md # All Salesforce skills overview │ ├── ml/INDEX.md # All ML skills overview │ └── ... 27 more indexes │ └── Skills by Category ├── api/ # REST, GraphQL, auth (7) ├── database/ # Postgres, MongoDB, Redis, streaming (11) + ├── salesforce/ # sf CLI, GUS, SOQL, work items, Chatter (7) ├── frontend/ # React, Next.js, performance (8) ├── mobile/ # iOS/Swift, SwiftUI, SwiftData (10) ├── testing/ # Unit, integration, e2e (6) @@ -349,6 +353,7 @@ cat skills/database/redpanda-streaming.md | **Debug** | GDB, LLDB, pdb, DevTools, Valgrind | 14 | discover-debugging | | **Cloud** | AWS, GCP, Modal, Vercel, Cloudflare | 27 | discover-cloud | | **Database** | Postgres, Mongo, Redis, Redpanda, Iceberg | 11 | discover-database | +| **Salesforce** | sf CLI, GUS, SOQL, Chatter, Automation | 7 | discover-salesforce | | **Caching** | Redis, HTTP, CDN, Service Workers | 7 | discover-caching | | **ML/AI** | DSPy, HuggingFace, Arize, GraphRAG | 33 | discover-ml | | **Rust/PyO3** | DSPy integration, RAG, agents, async, production | 19 | N/A | @@ -545,4 +550,4 @@ Feel free to fork and adapt for your own use. Pull requests welcome. --- -**447 atomic skills** • **41 gateway Agent Skills** • **35+ categories** • **100% CI-validated** +**454 atomic skills** • **32 gateway Agent Skills** • **31 categories** • **100% CI-validated** diff --git a/skills/discover-salesforce/SKILL.md b/skills/discover-salesforce/SKILL.md new file mode 100644 index 0000000..885692b --- /dev/null +++ b/skills/discover-salesforce/SKILL.md @@ -0,0 +1,292 @@ +--- +name: discover-salesforce +description: Automatically discover Salesforce CLI (sf) skills when working with Salesforce orgs, Agile Accelerator (GUS), SOQL queries, work items (WI, wis, wi, WIs), user stories, sprints, epics, or Chatter. Activates for Salesforce integration and automation tasks. +--- + +# Salesforce Skills Discovery + +Provides automatic access to comprehensive Salesforce CLI (sf) operations and Agile Accelerator (GUS) workflows. + +## When This Skill Activates + +This skill auto-activates when you're working with: +- Salesforce CLI (sf) commands +- Agile Accelerator (GUS) work items (WI), user stories, bugs +- SOQL queries and data operations +- Salesforce orgs and authentication +- Creating or updating Salesforce records +- Sprints, epics, and builds +- Chatter posts and comments +- Bulk data operations +- Salesforce REST API integration +- Any mention of WI (Work Items) + +## Available Skills + +### Quick Reference + +The Salesforce category contains **7 focused skills**: + +1. **sf-org-auth** - Authentication, org management, and user information +2. **sf-soql-queries** - SOQL queries and data retrieval +3. **sf-record-operations** - Creating and updating individual records +4. **sf-work-items** - Managing work items, sprints, epics, and builds (GUS) +5. **sf-chatter** - Chatter posts, comments, and feed interactions +6. **sf-bulk-operations** - Bulk updates, exports, and large-scale operations +7. **sf-automation** - Git integration, WI inference, and automated workflows + +**Note**: All examples dynamically detect your default org using `sf config get target-org`. You should set your default org with `sf config set target-org=`. Examples in the skills show how to fetch the default org dynamically to avoid hardcoding org aliases. + +### Load Full Category Details + +For complete descriptions and workflows: + +```bash +cat ~/.claude/skills/salesforce/INDEX.md +``` + +This loads the full Salesforce category index with: +- Detailed skill descriptions +- Usage triggers for each skill +- Common workflow combinations +- Cross-references to related skills + +### Load Specific Skills + +Load individual skills as needed: + +```bash +# Authentication and org management +cat ~/.claude/skills/salesforce/sf-org-auth.md + +# SOQL queries and data retrieval +cat ~/.claude/skills/salesforce/sf-soql-queries.md + +# Create/update individual records +cat ~/.claude/skills/salesforce/sf-record-operations.md + +# Work items, sprints, and epics +cat ~/.claude/skills/salesforce/sf-work-items.md + +# Chatter posts and comments +cat ~/.claude/skills/salesforce/sf-chatter.md + +# Bulk operations and exports +cat ~/.claude/skills/salesforce/sf-bulk-operations.md + +# Git integration and automation +cat ~/.claude/skills/salesforce/sf-automation.md +``` + +## Common Workflows + +### Create User Story +**Sequence**: Get user → Query IDs → Create work item → Add Chatter update + +```bash +cat ~/.claude/skills/salesforce/sf-org-auth.md # Get current user +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query for IDs +cat ~/.claude/skills/salesforce/sf-work-items.md # Create work item +cat ~/.claude/skills/salesforce/sf-chatter.md # Add Chatter post +``` + +### Sprint Planning +**Sequence**: Create sprint → Query backlog → Bulk assign items + +```bash +cat ~/.claude/skills/salesforce/sf-work-items.md # Create sprint +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query backlog +cat ~/.claude/skills/salesforce/sf-bulk-operations.md # Bulk assign +``` + +### Bulk Status Update +**Sequence**: Query work items → Export CSV → Bulk update → Verify + +```bash +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query items +cat ~/.claude/skills/salesforce/sf-bulk-operations.md # Bulk update +``` + +### Git Integration +**Sequence**: Extract WI from branch → Query details → Update status → Post to Chatter + +```bash +cat ~/.claude/skills/salesforce/sf-automation.md # WI inference +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Query WI +cat ~/.claude/skills/salesforce/sf-work-items.md # Update status +cat ~/.claude/skills/salesforce/sf-chatter.md # Post update +``` + +### Work Item Reporting +**Sequence**: Query with related data → Export to CSV → Analyze + +```bash +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Complex queries +cat ~/.claude/skills/salesforce/sf-bulk-operations.md # Export data +``` + +## Skill Selection Guide + +**Use sf-org-auth when:** +- Logging into Salesforce orgs +- Managing multiple org connections +- Getting current user email/ID dynamically +- Switching between environments + +**Use sf-soql-queries when:** +- Querying Salesforce data +- Finding record IDs (Users, Epics, Sprints, etc.) +- Building reports or dashboards +- Working with related objects + +**Use sf-record-operations when:** +- Creating 1-5 records individually +- Updating specific records by ID +- Working with custom objects +- Using REST API for advanced operations + +**Use sf-work-items when:** +- Creating user stories, bugs, or tasks +- Managing sprints and sprint planning +- Working with epics and product tags +- Sprint reporting + +**Use sf-chatter when:** +- Posting updates to work items +- Adding comments to records +- Automating notifications from CI/CD + +**Use sf-bulk-operations when:** +- Updating 10+ records at once +- Exporting large datasets (>2000 records) +- Bulk status changes +- Avoiding API rate limits + +**Use sf-automation when:** +- Extracting WI numbers from git branches +- Automating Chatter posts from commits +- Creating git hooks for Salesforce +- CI/CD integration with GUS + +## Integration with Other Skills + +Salesforce skills commonly combine with: + +**API skills** (`discover-api`): +- REST API integration with Salesforce +- OAuth authentication for Salesforce +- Custom API endpoints using Salesforce data +- Webhook handling for Salesforce events + +**Workflow skills** (`discover-workflow`): +- Automated work item creation from CI/CD +- Status update automation based on deployments +- Scheduled bulk operations +- Integration with external systems + +**Data skills** (`discover-data`): +- ETL operations with Salesforce data +- Data transformation and analysis +- Export/import workflows +- Data quality and validation checks + +**Collaboration skills** (`discover-collaboration`): +- Syncing GitHub issues with GUS work items +- Automated Chatter updates from CI/CD +- Cross-platform project management +- Integration dashboards and reporting + +**Testing skills** (`discover-testing`): +- Integration testing for Salesforce integrations +- Automated testing of Salesforce workflows +- Validation of Salesforce data operations +- CI/CD integration with Salesforce deployments + +## Usage Instructions + +1. **Auto-activation**: This skill loads automatically when Claude Code detects Salesforce-related work +2. **Browse skills**: Run `cat ~/.claude/skills/salesforce/INDEX.md` for full category overview +3. **Load specific skills**: Use bash commands above to load individual skills +4. **Follow patterns**: Each skill contains 5-10 patterns for common operations +5. **Combine skills**: Load related skills for comprehensive Salesforce integration + +## Progressive Loading + +This gateway skill (~300 lines, ~3K tokens) enables progressive loading: +- **Level 1**: Gateway loads automatically (you're here now) +- **Level 2**: Load category INDEX.md (~350 lines, ~3.5K tokens) for full overview +- **Level 3**: Load specific skill (~150-220 lines, ~1.5-2K tokens each) for detailed guidance + +Total context: 3K + 3.5K + (1.5-2K per skill) = efficient skill discovery and usage. + +## Quick Start Examples + +**"Authenticate to Salesforce"**: +```bash +cat ~/.claude/skills/salesforce/sf-org-auth.md +``` + +**"Query my work items"**: +```bash +cat ~/.claude/skills/salesforce/sf-soql-queries.md +``` + +**"Create a user story in GUS"**: +```bash +cat ~/.claude/skills/salesforce/sf-work-items.md +``` + +**"Update status for multiple work items"**: +```bash +cat ~/.claude/skills/salesforce/sf-bulk-operations.md +``` + +**"Post update to Chatter"**: +```bash +cat ~/.claude/skills/salesforce/sf-chatter.md +``` + +**"Extract WI from git branch"**: +```bash +cat ~/.claude/skills/salesforce/sf-automation.md +``` + +**"Export work items for reporting"**: +```bash +cat ~/.claude/skills/salesforce/sf-bulk-operations.md +``` + +## Best Practices + +**Always:** +- **Use `sf` CLI tool directly** for all Salesforce operations +- Infer WI number from git branch name when not explicitly provided +- Dynamically fetch user email/ID and default org instead of hardcoding +- Use `--target-org "$DEFAULT_ORG"` after fetching it dynamically to avoid ambiguity +- Query for IDs before creating related records +- Use bulk operations for 10+ record updates +- Verify target org before destructive operations +- Include all required fields when creating records +- Validate query results before using extracted values + +**Never:** +- Hardcode record IDs (they vary across orgs) +- Hardcode user emails (fetch dynamically) +- Update records without verifying they exist first +- Use single updates in loops (use bulk operations) +- Forget API name suffixes (`__c` for custom fields, `__r` for relationships) +- Commit authentication tokens or credentials +- Skip validation of jq output (check for null/empty) + +**Security:** +- ⚠️ Always verify target org before updates +- ⚠️ Query before update to verify record existence +- ⚠️ Be cautious with bulk delete operations +- ⚠️ Use `--target-org` flag for all operations +- ⚠️ Never commit credentials to version control +- ⚠️ Don't post sensitive data to Chatter +- ⚠️ Test automation in sandbox before production + +--- + +**Next Steps**: Run `cat ~/.claude/skills/salesforce/INDEX.md` to see full category details, or load specific skills using the commands above. diff --git a/skills/salesforce/INDEX.md b/skills/salesforce/INDEX.md new file mode 100644 index 0000000..037bdbd --- /dev/null +++ b/skills/salesforce/INDEX.md @@ -0,0 +1,410 @@ +# Salesforce Skills + +Comprehensive skills for working with Salesforce CLI (sf) and Agile Accelerator (GUS). + +## Category Overview + +**Total Skills**: 10 +**Focus**: Salesforce CLI, Agile Accelerator (GUS), SOQL, Record Management, Chatter +**Use Cases**: Creating work items, managing sprints, querying data, bulk operations, API integration, automation +**Default Org**: Examples dynamically detect your default org using `sf config get target-org` - set it with `sf config set target-org=` + +## Skills in This Category + +### sf-org-auth.md +**Description**: Authenticate and manage Salesforce orgs using sf CLI +**Lines**: ~348 +**Use When**: +- Logging into Salesforce orgs +- Managing multiple org connections +- Retrieving current user information +- Getting org details and configuration +- Troubleshooting authentication issues + +**Key Concepts**: Web/JWT login, org management, user information, multi-org workflows + +--- + +### sf-soql-queries.md (NAVIGATION FILE) +**Description**: Navigation guide to SOQL query skills (basics, advanced, troubleshooting) +**Lines**: ~230 +**Use When**: +- Deciding which SOQL skill file to use +- Understanding the SOQL skill organization +- Quick reference for field names and critical rules + +**Key Concepts**: Skill navigation, quick start guides, common workflows + +**⚠️ NOTE**: This is a navigation file. Use the specialized skill files below for actual queries: +- `sf-soql-basics.md` - Core concepts and basic queries +- `sf-soql-advanced.md` - Complex queries and aggregation +- `sf-soql-troubleshooting.md` - Error handling and anti-patterns + +--- + +### sf-soql-basics.md +**Description**: Basic SOQL queries, field discovery, and fundamental patterns for Salesforce +**Lines**: ~402 +**Use When**: +- Starting with SOQL queries (learn basics first) +- Querying simple data (users, work items by ID, basic lookups) +- Learning field verification (mandatory describe workflow) +- Looking up team memberships +- Finding record IDs + +**Key Concepts**: MANDATORY field discovery, basic SOQL syntax, relationship names, verified field names, output formats + +--- + +### sf-soql-advanced.md +**Description**: Advanced SOQL queries for GUS - work items, aggregation, date filters, and tooling API +**Lines**: ~292 +**Use When**: +- Querying work items by various criteria (assignee, sprint, epic, status) +- Aggregating data (counts, sums, grouped results) +- Filtering by dates (date ranges, relative dates) +- Querying metadata (Apex classes, triggers using Tooling API) +- Complex multi-condition queries + +**Key Concepts**: Work item queries, aggregation (COUNT, SUM), date filters, tooling API + +--- + +### sf-soql-troubleshooting.md +**Description**: Common SOQL errors, anti-patterns, and troubleshooting for Salesforce queries +**Lines**: ~507 +**Use When**: +- Debugging SOQL query errors (INVALID_FIELD, relationship errors, operator errors) +- Understanding common mistakes (why queries fail and how to fix them) +- Learning anti-patterns (what NOT to do in SOQL queries) +- Troubleshooting field verification (field doesn't exist errors) +- Fixing relationship queries (wrong relationship name errors) + +**Key Concepts**: Critical violations, common field errors, query performance, troubleshooting workflow + +--- + +### sf-record-operations.md +**Description**: Create and update Salesforce records using sf CLI +**Lines**: ~438 +**Use When**: +- Creating new Salesforce records +- Updating existing records +- Working with individual records (not bulk) +- Setting field values on records +- Using REST API for custom operations + +**Key Concepts**: Creating records, updating by ID/field match, REST API, validation + +--- + +### sf-work-items.md +**Description**: Manage Agile Accelerator (GUS) work items, sprints, and epics +**Lines**: ~402 +**Use When**: +- Creating user stories, bugs, or tasks in GUS +- Managing sprints and sprint planning +- Working with epics and product tags +- Managing scheduled builds +- Sprint planning and backlog management + +**Key Concepts**: Work item types, sprints, epics, builds, status workflows, sprint reporting + +--- + +### sf-chatter.md +**Description**: Create and manage Chatter posts and comments in Salesforce +**Lines**: ~418 +**Use When**: +- Posting updates to Chatter feeds +- Adding comments to work items +- Automating Chatter notifications +- Creating progress updates +- Documenting decisions on records + +**Key Concepts**: FeedItem, FeedComment, post types, automated updates, querying feeds + +--- + +### sf-bulk-operations.md +**Description**: Perform bulk data operations on Salesforce objects +**Lines**: ~356 +**Use When**: +- Updating multiple records at once +- Exporting large datasets +- Bulk status changes +- Mass data migrations +- Avoiding API limit issues + +**Key Concepts**: Bulk API, CSV operations, bulk updates/exports, bulk creation + +--- + +### sf-automation.md +**Description**: Automate Salesforce operations with git integration and CI/CD +**Lines**: ~437 +**Use When**: +- Integrating Salesforce with git workflows +- Inferring WI numbers from branch names +- Automating Chatter updates from CI/CD +- Building automated workflows +- Creating git hooks for Salesforce operations + +**Key Concepts**: WI inference, git integration, CI/CD workflows, automated status transitions + +--- + +## Common Workflows + +### Create User Story +**Goal**: Create a new user story in Agile Accelerator + +**Sequence**: +1. `sf-org-auth.md` - Get current user info +2. `sf-soql-queries.md` - Query for Epic, Sprint, and User IDs +3. `sf-work-items.md` - Create work item with proper fields +4. `sf-chatter.md` - Add Chatter post for updates + +**Example**: Creating a feature story linked to current sprint + +--- + +### Sprint Planning +**Goal**: Set up and populate a new sprint + +**Sequence**: +1. `sf-work-items.md` - Create sprint record +2. `sf-soql-queries.md` - Query backlog items +3. `sf-bulk-operations.md` - Bulk assign items to sprint +4. `sf-work-items.md` - Update story points and priorities + +**Example**: Planning two-week sprint with 20 stories + +--- + +### Bulk Status Update +**Goal**: Update status for multiple work items + +**Sequence**: +1. `sf-soql-queries.md` - Query work items by criteria +2. `sf-bulk-operations.md` - Export to CSV +3. `sf-bulk-operations.md` - Bulk update using CSV +4. `sf-soql-queries.md` - Verify updates with query + +**Example**: Moving all sprint items from "Ready" to "In Progress" + +--- + +### Automated Git Workflow +**Goal**: Automate WI updates based on git events + +**Sequence**: +1. `sf-automation.md` - Extract WI from branch name +2. `sf-soql-queries.md` - Query work item details +3. `sf-work-items.md` - Update work item status +4. `sf-chatter.md` - Post commit info to Chatter + +**Example**: Auto-update WI to "In Progress" on first commit + +--- + +### Work Item Reporting +**Goal**: Export work items for analysis + +**Sequence**: +1. `sf-soql-queries.md` - Query work items with related data +2. `sf-bulk-operations.md` - Export to CSV using bulk API +3. Process data for reporting (Excel, BI tools) + +**Example**: Sprint burndown report or velocity tracking + +--- + +## Skill Combinations + +### With API Skills (`discover-api`) +- REST API calls to Salesforce +- Custom API integrations +- Webhook handling for Salesforce events +- Authentication using OAuth + +**Common combos**: +- `sf-record-operations.md` + `api/rest-api-design.md` +- `sf-org-auth.md` + `api/api-authentication.md` + +--- + +### With Workflow Skills (`discover-workflow`) +- Automated work item creation +- Status update automation +- Integration with CI/CD pipelines +- Scheduled bulk operations + +**Common combos**: +- `sf-automation.md` + `workflow/automation-scripting.md` +- `sf-bulk-operations.md` + `cicd/ci-optimization.md` + +--- + +### With Data Skills (`discover-data`) +- ETL operations with Salesforce data +- Data transformation and analysis +- Export/import workflows +- Data quality checks + +**Common combos**: +- `sf-bulk-operations.md` + `data/data-transformation.md` +- `sf-soql-queries.md` + `data/data-validation.md` + +--- + +### With Collaboration Skills (`discover-collaboration`) +- Syncing GitHub issues with GUS +- Automated Chatter updates +- Cross-platform project management +- Integration dashboards + +**Common combos**: +- `sf-automation.md` + `collaboration/github/github-issues-projects.md` +- `sf-chatter.md` + `collaboration/github/github-actions-workflows.md` + +--- + +## Quick Selection Guide + +**Authentication & Setup**: +- Starting fresh → `sf-org-auth.md` +- Need user info → `sf-org-auth.md` + +**Querying Data**: +- New to SOQL → `sf-soql-basics.md` +- Finding records → `sf-soql-basics.md` +- Getting IDs → `sf-soql-basics.md` +- Complex queries → `sf-soql-advanced.md` +- Query errors → `sf-soql-troubleshooting.md` +- Not sure which → `sf-soql-queries.md` (navigation) + +**Single Records**: +- Create/update 1-5 records → `sf-record-operations.md` +- Work with custom objects → `sf-record-operations.md` + +**GUS Work Items**: +- Create user stories → `sf-work-items.md` +- Sprint planning → `sf-work-items.md` +- Epic management → `sf-work-items.md` + +**Communication**: +- Post updates → `sf-chatter.md` +- Automated notifications → `sf-chatter.md` + +**Bulk Operations**: +- Update 10+ records → `sf-bulk-operations.md` +- Export large datasets → `sf-bulk-operations.md` + +**Automation**: +- Git integration → `sf-automation.md` +- CI/CD workflows → `sf-automation.md` +- WI from branch → `sf-automation.md` + +--- + +## Common Objects + +**Work Items (ADM_Work__c)**: +- User Stories, Bugs, Tasks, Investigations +- Key fields: Subject__c, Status__c, Priority__c, Assignee__c + +**Epics (ADM_Epic__c)**: +- Feature groupings +- Key fields: Name, Health__c, Priority__c, Description__c +- Note: Uses Health__c (not Status__c) + +**Sprints (ADM_Sprint__c)**: +- Iteration tracking +- Key fields: Name, Start_Date__c, End_Date__c + +**Builds (ADM_Build__c)**: +- Release tracking +- Key fields: Name, External_ID__c + +**Chatter (FeedItem, FeedComment)**: +- Posts and comments +- Key fields: ParentId, Body, CommentBody + +**Users (User)**: +- Salesforce users +- Key fields: Name, Email, Id + +--- + +## Best Practices + +**Authentication**: +- Use meaningful aliases for orgs (e.g., prod, staging, dev) +- Set a default org with `sf config set target-org=` +- Dynamically fetch default org and user email/ID instead of hardcoding +- Validate org connection before operations + +**Querying**: +- Always use LIMIT to avoid timeouts +- Query for IDs before creating related records +- Use `--result-format json` for scripting +- Validate query results before using extracted values + +**Record Operations**: +- Include all required fields when creating records +- Validate record existence before updates +- Use bulk operations for 10+ records + +**Work Items**: +- Link work items to sprints and epics +- Use story points for estimation +- Update status as work progresses + +**Chatter**: +- Keep posts clear and concise +- Don't post sensitive information +- Validate ParentId before posting + +**Bulk Operations**: +- Test with small dataset first +- Backup data before bulk deletes +- Use appropriate LIMIT in export queries + +**Automation**: +- Include WI number in branch names +- Validate WI exists before operations +- Test automation in sandbox first +- Add error handling and fallbacks + +--- + +## Loading Skills + +All skills are available in the `skills/salesforce/` directory: + +```bash +# Load specific skill +cat ~/.claude/skills/salesforce/sf-org-auth.md +cat ~/.claude/skills/salesforce/sf-soql-queries.md # Navigation file +cat ~/.claude/skills/salesforce/sf-soql-basics.md # Basic SOQL queries +cat ~/.claude/skills/salesforce/sf-soql-advanced.md # Advanced SOQL queries +cat ~/.claude/skills/salesforce/sf-soql-troubleshooting.md # SOQL error handling +cat ~/.claude/skills/salesforce/sf-record-operations.md +cat ~/.claude/skills/salesforce/sf-work-items.md +cat ~/.claude/skills/salesforce/sf-chatter.md +cat ~/.claude/skills/salesforce/sf-bulk-operations.md +cat ~/.claude/skills/salesforce/sf-automation.md +``` + +**Pro tip**: Start with `sf-org-auth.md` to authenticate, then use `sf-soql-basics.md` to learn queries and find IDs, then create/update records with the appropriate skill. + +--- + +**Related Categories**: +- `discover-api` - REST API patterns and authentication +- `discover-workflow` - Automation and scripting +- `discover-data` - Data transformation and ETL +- `discover-collaboration` - GitHub and project management +- `discover-testing` - Integration testing strategies +- `discover-cicd` - CI/CD pipeline integration diff --git a/skills/salesforce/sf-automation.md b/skills/salesforce/sf-automation.md new file mode 100644 index 0000000..64a95f3 --- /dev/null +++ b/skills/salesforce/sf-automation.md @@ -0,0 +1,437 @@ +--- +name: salesforce-automation +description: Automate Salesforce operations with git integration and CI/CD. Use for automating GUS work item updates, inferring WI numbers from branches, and git workflows. +keywords: salesforce, gus, automation, git, CI/CD, work item, branch, workflow, hook, automated updates, WI inference +--- + +# Salesforce Automation + +**Scope**: Git integration, WI inference, and automated workflows +**Lines**: ~437 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Integrating Salesforce with git workflows +- Inferring WI numbers from branch names +- Automating Chatter updates from CI/CD +- Building automated workflows +- Creating git hooks for Salesforce operations +- Syncing git and GUS automatically + +--- + +## Core Concepts + +### Concept 1: WI Number Inference + +Extract WI numbers from git branch names automatically. + +**Common branch patterns**: +- `W-12345678` +- `wi-12345678` +- `12345678-feature-name` +- `feature/W-12345678` +- `bugfix/12345678-fix-issue` + +### Concept 2: Git Integration Points + +**Pre-commit**: Validate WI exists before commit +**Post-commit**: Post to Chatter after commit +**Pre-push**: Update WI status before push +**Post-merge**: Close WI after merge to main + +--- + +## Patterns + +### Pattern 1: Inferring WI Number from Git Branch + +**Use case**: Automatically determine WI number from current git branch name + +**IMPORTANT**: The WI number (e.g., W-12345678) is stored in the `Name` field, NOT the `Id` field. You must query by `Name` to get the actual Salesforce record `Id`. + +```bash +# ❌ Bad: Asking user for WI number when it's in the branch +echo "What's the WI number?" + +# ✅ Good: Extract WI number from git branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +# Validate WI number was found +if [ -z "$WI_NUMBER" ]; then + echo "Error: No WI number found in branch name: $BRANCH" + exit 1 +fi + +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Query work item by Name field (NOT Id) to get the record details +QUERY_RESULT=$(sf data query \ + --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" \ + --json) + +# Check if query returned results +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item W-${WI_NUMBER} not found in GUS" + exit 1 +fi + +# Get the Salesforce record Id from the WI Name +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +# Validate we got a valid ID +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Error: Failed to extract Salesforce ID for W-${WI_NUMBER}" + exit 1 +fi + +echo "Found work item: W-${WI_NUMBER} (ID: ${WORK_ITEM_ID})" +``` + +**Benefits**: +- No need to manually specify WI numbers +- Reduces errors from copying/pasting wrong WI numbers +- Enables automation based on git workflow +- Makes scripts more context-aware + +**Key Points**: +- WI number (W-12345678) is in the `Name` field, NOT the `Id` field +- Always query `WHERE Name = 'W-...'` to find work items +- Extract the `Id` field from query results for record operations +- Use `--json` flag for easier parsing with `jq` + +### Pattern 2: Automated Chatter Updates from Git + +**Use case**: Post commit information to Chatter automatically + +```bash +#!/bin/bash +# post-commit hook + +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Get WI from branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -n "$WI_NUMBER" ]; then + # Get work item ID + WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" \ + --json | jq -r '.result.records[0].Id') + + if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + # Get latest commit + COMMIT_MSG=$(git log -1 --pretty=%B) + COMMIT_HASH=$(git log -1 --pretty=%h) + + # Post to Chatter + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='Commit ${COMMIT_HASH}: ${COMMIT_MSG}'" \ + --target-org "$DEFAULT_ORG" + + echo "Posted commit to W-${WI_NUMBER}" + fi +fi +``` + +### Pattern 3: CI/CD Integration + +**Use case**: Update work items from CI/CD pipeline + +```bash +#!/bin/bash +# ci-build.sh + +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Extract WI from branch or PR +WI_NUMBER=$(echo "$CI_BRANCH_NAME" | grep -oE '[0-9]{8}' | head -1) + +if [ -z "$WI_NUMBER" ]; then + echo "No WI number found, skipping GUS update" + exit 0 +fi + +# Get work item +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" \ + --json | jq -r '.result.records[0].Id') + +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Work item W-${WI_NUMBER} not found" + exit 0 +fi + +# Post build status +if [ "$BUILD_STATUS" = "success" ]; then + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='✅ Build #${BUILD_NUMBER} passed +All tests: ${TEST_TOTAL}/${TEST_TOTAL} ✓ +Coverage: ${COVERAGE}% +Ready for deployment + +Build: ${BUILD_URL}'" \ + --target-org "$DEFAULT_ORG" + + # Update status if still in progress + CURRENT_STATUS=$(sf data query \ + --query "SELECT Status__c FROM ADM_Work__c WHERE Id = '${WORK_ITEM_ID}'" \ + --target-org "$DEFAULT_ORG" \ + --json | jq -r '.result.records[0].Status__c') + + if [ "$CURRENT_STATUS" = "In Progress" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review'" \ + --target-org "$DEFAULT_ORG" + + echo "Moved W-${WI_NUMBER} to Code Review" + fi +else + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='❌ Build #${BUILD_NUMBER} failed +Failed tests: ${TEST_FAILED}/${TEST_TOTAL} +See: ${BUILD_URL} +Please investigate.'" \ + --target-org "$DEFAULT_ORG" +fi +``` + +### Pattern 4: Pre-push Validation + +**Use case**: Validate WI status before allowing push + +```bash +#!/bin/bash +# pre-push hook + +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -n "$WI_NUMBER" ]; then + # Check if WI exists and is in valid state + WI_STATUS=$(sf data query \ + --query "SELECT Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" \ + --json | jq -r '.result.records[0].Status__c') + + if [ "$WI_STATUS" = "null" ] || [ -z "$WI_STATUS" ]; then + echo "Error: Work item W-${WI_NUMBER} not found in GUS" + echo "Please create the work item before pushing" + exit 1 + fi + + if [ "$WI_STATUS" = "Closed" ]; then + echo "Error: Work item W-${WI_NUMBER} is already closed" + echo "Please reopen or create a new work item" + exit 1 + fi + + echo "✓ Work item W-${WI_NUMBER} validated (Status: ${WI_STATUS})" +fi +``` + +### Pattern 5: Automated Status Transitions + +**Use case**: Auto-update WI status based on git events + +```bash +#!/bin/bash +# auto-status-update.sh + +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -z "$WI_NUMBER" ]; then + echo "No WI number in branch" + exit 0 +fi + +# Get work item +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id, Status__c FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" \ + --json | jq -r '.result.records[0].Id') + +CURRENT_STATUS=$(sf data query \ + --query "SELECT Status__c FROM ADM_Work__c WHERE Id = '${WORK_ITEM_ID}'" \ + --target-org "$DEFAULT_ORG" \ + --json | jq -r '.result.records[0].Status__c') + +# Transition logic based on event +case "$1" in + start) + if [ "$CURRENT_STATUS" = "New" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org "$DEFAULT_ORG" + echo "Started work on W-${WI_NUMBER}" + fi + ;; + + review) + if [ "$CURRENT_STATUS" = "In Progress" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review'" \ + --target-org "$DEFAULT_ORG" + echo "Moved W-${WI_NUMBER} to Code Review" + fi + ;; + + merged) + if [ "$CURRENT_STATUS" != "Closed" ]; then + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Fixed'" \ + --target-org "$DEFAULT_ORG" + echo "Marked W-${WI_NUMBER} as Fixed" + fi + ;; +esac +``` + +--- + +## Quick Reference + +### WI Number Extraction + +```bash +# Extract 8-digit WI number +WI_NUMBER=$(git rev-parse --abbrev-ref HEAD | grep -oE '[0-9]{8}' | head -1) + +# Validate extracted +[ -z "$WI_NUMBER" ] && echo "No WI found" && exit 1 + +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Query by Name field +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" --json | jq -r '.result.records[0].Id') +``` + +### Common Git Hooks + +``` +pre-commit - Validate WI exists +post-commit - Post to Chatter +pre-push - Check WI status +post-merge - Update status to Fixed +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Include WI number in branch names +✅ DO: Validate WI exists before operations +✅ DO: Use error handling in automation +✅ DO: Post meaningful updates to Chatter +✅ DO: Test automation in sandbox first +✅ DO: Add fallbacks for missing WI numbers +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Assume branch always has WI number +❌ DON'T: Skip validation of query results +❌ DON'T: Hardcode org or user information +❌ DON'T: Spam Chatter with every commit +❌ DON'T: Auto-close WIs without verification +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Validate all git inputs (branch names, commit messages) +- ⚠️ Don't expose tokens in git hooks +- ⚠️ Test automation thoroughly before production use +- ⚠️ Use appropriate error handling to avoid data corruption + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication for automation +- `sf-soql-queries.md` - Query WI details +- `sf-work-items.md` - Work with GUS objects +- `sf-chatter.md` - Post automated updates + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-bulk-operations.md b/skills/salesforce/sf-bulk-operations.md new file mode 100644 index 0000000..0caa93d --- /dev/null +++ b/skills/salesforce/sf-bulk-operations.md @@ -0,0 +1,356 @@ +--- +name: salesforce-bulk-operations +description: Perform bulk data operations on Salesforce objects. Use for mass updates to GUS work items, bulk exports, and large data operations. +keywords: salesforce, gus, bulk, mass update, export, import, CSV, bulk API, large datasets, multiple records +--- + +# Salesforce Bulk Operations + +**Scope**: Bulk data updates, exports, and large-scale operations +**Lines**: ~356 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Updating multiple records at once +- Exporting large datasets +- Bulk status changes +- Mass data migrations +- Batch processing operations +- Avoiding API limit issues + +--- + +## Core Concepts + +### Concept 1: Bulk API vs Single Operations + +**Use Bulk API when**: +- Updating 10+ records +- Exporting large datasets (>2000 records) +- Performing repetitive operations +- Avoiding API limits + +**Use Single Operations when**: +- Updating 1-5 records +- Need immediate feedback +- Complex validation required + +### Concept 2: CSV-Based Operations + +Bulk operations use CSV files: +- Header row with field names +- One record per line +- Include `Id` for updates +- All fields as text + +--- + +## Patterns + +### Pattern 1: Bulk Status Updates + +**Use case**: Update multiple work items to a new status + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Step 1: Query work items to update +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c + WHERE Sprint__c = 'a1Fxx000002EFGH' + AND Status__c = 'Ready for Development'" \ + --result-format csv \ + --target-org "$DEFAULT_ORG" > work_items.csv + +# Step 2: Create CSV for bulk update +# Manually edit or use script to create: +# work_items_update.csv: +# Id,Status__c +# a07xx00000ABCD1,In Progress +# a07xx00000ABCD2,In Progress +# a07xx00000ABCD3,In Progress + +# Step 3: Bulk update using CSV +sf data update bulk \ + --sobject ADM_Work__c \ + --file work_items_update.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" + +echo "Bulk update complete" +``` + +### Pattern 2: Bulk Data Export + +**Use case**: Export large datasets for analysis or backup + +```bash +# Export all work items for a sprint +sf data export bulk \ + --sobject ADM_Work__c \ + --query "SELECT Id, Name, Subject__c, Status__c, Priority__c, + Assignee__r.Name, Assignee__r.Email, Story_Points__c, + Sprint__r.Name, Epic__r.Name, CreatedDate, LastModifiedDate + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42'" \ + --output-file sprint_42_items.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" + +echo "Exported to sprint_42_items.csv" + +# Export all open bugs +sf data export bulk \ + --sobject ADM_Work__c \ + --query "SELECT Id, Name, Subject__c, Status__c, Priority__c, + Assignee__r.Name, Found_in_Build__r.Name, CreatedDate + FROM ADM_Work__c + WHERE Type__c = 'Bug' + AND Status__c NOT IN ('Fixed', 'Not a Bug', 'Closed')" \ + --output-file open_bugs.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" + +# Export user list +sf data export bulk \ + --sobject User \ + --query "SELECT Id, Name, Email, Profile.Name, IsActive + FROM User + WHERE IsActive = true" \ + --output-file active_users.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 3: Script-Driven Bulk Updates + +**Use case**: Generate CSV updates programmatically + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Query and transform data +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + AND Status__c = 'Ready for Development'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" > work_items.json + +# Generate CSV with jq +echo "Id,Status__c,Assignee__c" > bulk_update.csv +jq -r '.result.records[] | [.Id, "In Progress", "005xx000001X8Uz"] | @csv' work_items.json >> bulk_update.csv + +# Bulk update +sf data update bulk \ + --sobject ADM_Work__c \ + --file bulk_update.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" + +echo "Updated $(wc -l < bulk_update.csv) records" +``` + +### Pattern 4: Bulk Assignment to Sprint + +**Use case**: Assign multiple backlog items to a sprint + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Get sprint ID +SPRINT_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 43'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# Query backlog items +sf data query \ + --query "SELECT Id, Name, Subject__c, Story_Points__c + FROM ADM_Work__c + WHERE Status__c = 'Ready for Development' + AND Sprint__c = null + ORDER BY Priority__c + LIMIT 20" \ + --result-format csv \ + --target-org "$DEFAULT_ORG" > backlog.csv + +# Create assignment CSV +echo "Id,Sprint__c" > sprint_assignment.csv +tail -n +2 backlog.csv | cut -d',' -f1 | while read id; do + echo "${id},${SPRINT_ID}" >> sprint_assignment.csv +done + +# Bulk assign +sf data update bulk \ + --sobject ADM_Work__c \ + --file sprint_assignment.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" + +echo "Assigned $(tail -n +2 sprint_assignment.csv | wc -l) items to Sprint 43" +``` + +### Pattern 5: Bulk Creation + +**Use case**: Create multiple records from CSV + +```bash +# Create CSV file +# new_work_items.csv: +# Subject__c,Status__c,Type__c,Priority__c,Story_Points__c +# "Implement feature A",New,User Story,P2,3 +# "Implement feature B",New,User Story,P2,5 +# "Fix bug in login",New,Bug,P1,2 + +# Bulk create +sf data create bulk \ + --sobject ADM_Work__c \ + --file new_work_items.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" + +echo "Created records from CSV" +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Bulk update +sf data update bulk --sobject --file --wait --target-org + +# Bulk export +sf data export bulk --sobject --query "" --output-file --wait --target-org + +# Bulk create +sf data create bulk --sobject --file --wait --target-org + +# Bulk delete +sf data delete bulk --sobject --file --wait --target-org +``` + +### CSV Format + +**For Updates** (requires Id): +```csv +Id,Field1,Field2 +a07xx00000ABCD1,Value1,Value2 +a07xx00000ABCD2,Value3,Value4 +``` + +**For Creates** (no Id): +```csv +Field1,Field2,Field3 +Value1,Value2,Value3 +Value4,Value5,Value6 +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Use bulk operations for 10+ records +✅ DO: Test with small dataset first +✅ DO: Include --wait flag to monitor completion +✅ DO: Backup data before bulk deletes +✅ DO: Validate CSV format before upload +✅ DO: Use appropriate LIMIT in export queries +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Use single updates in loops +❌ DON'T: Skip validation of CSV data +❌ DON'T: Bulk delete without backup +❌ DON'T: Export without LIMIT (can timeout) +❌ DON'T: Update production without testing +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Loop with single updates +for id in $(cat work_item_ids.txt); do + sf data update record --sobject ADM_Work__c --record-id "$id" --values "Status__c='Closed'" +done + +# ✅ CORRECT: Use bulk update +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +echo "Id,Status__c" > bulk_update.csv +cat work_item_ids.txt | while read id; do + echo "${id},Closed" >> bulk_update.csv +done + +sf data update bulk \ + --sobject ADM_Work__c \ + --file bulk_update.csv \ + --wait 10 \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Always backup before bulk deletes +- ⚠️ Test bulk operations in sandbox first +- ⚠️ Validate CSV data before upload +- ⚠️ Use appropriate WHERE clauses in exports +- ⚠️ Be cautious with bulk updates in production + +--- + +## Related Skills + +- `sf-soql-queries.md` - Query data for bulk operations +- `sf-record-operations.md` - Single record operations +- `sf-work-items.md` - Bulk sprint/epic management + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-chatter.md b/skills/salesforce/sf-chatter.md new file mode 100644 index 0000000..4bc434b --- /dev/null +++ b/skills/salesforce/sf-chatter.md @@ -0,0 +1,418 @@ +--- +name: salesforce-chatter +description: Create and manage Chatter posts and comments in Salesforce. Use for posting updates to GUS work items, epics, and records. +keywords: chatter, salesforce, gus, post, comment, feed, FeedItem, FeedComment, update, notification, work item comments +--- + +# Salesforce Chatter Operations + +**Scope**: Creating Chatter posts, comments, and feed interactions +**Lines**: ~418 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Posting updates to Chatter feeds +- Adding comments to work items +- Automating Chatter notifications +- Creating progress updates +- Documenting decisions on records +- Sharing information with team members + +--- + +## Core Concepts + +### Concept 1: Chatter Objects + +**FeedItem** - Main Chatter posts +**FeedComment** - Comments on posts +**ParentId** - The record to post on (Work Item, Epic, User, etc.) + +**Key Fields**: +``` +FeedItem: + ParentId - Record ID to post on (required) + Body - Post text content (required) + Type - TextPost (default), LinkPost, ContentPost + +FeedComment: + FeedItemId - The post ID to comment on (required) + CommentBody - Comment text (required) +``` + +### Concept 2: Post Types + +**TextPost** (default): +- Plain text or basic formatting +- Most common type +- Simple status updates + +**LinkPost**: +- Shares URLs with preview +- Requires LinkUrl field + +**ContentPost**: +- Shares Salesforce files +- Requires ContentDocumentId + +--- + +## Patterns + +### Pattern 1: Creating Basic Chatter Posts + +**Use case**: Post updates to work item feeds + +```bash +# Simple text post +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' Body='Started work on this feature'" \ + --target-org "$DEFAULT_ORG" + +# Multi-line post with details +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' Body='Phase 1.2 complete: +- S3 Storage Service implemented +- 32 tests passing +- File size validation added +- CI configuration updated'" \ + --target-org "$DEFAULT_ORG" + +# Post with structured update +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' \ + Body='Implementation Status: + +✅ Backend API complete +✅ Database schema migrated +⏳ Frontend integration in progress +⏳ Testing pending + +Ready for review by EOD.'" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 2: Adding Comments to Posts + +**Use case**: Comment on existing Chatter posts + +```bash +# Create a comment +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='LGTM - approved for merge'" \ + --target-org "$DEFAULT_ORG" + +# Comment with @mention (use User ID for @mention) +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' CommentBody='Thanks for the update! Please coordinate with {005xx000001X8Uz} for testing.'" \ + --target-org "$DEFAULT_ORG" + +# Comment with feedback +sf data create record \ + --sobject FeedComment \ + --values "FeedItemId='0D5xx00000FGHIJ' \ + CommentBody='Great work! A few questions: +1. Did you test edge cases? +2. Is documentation updated? +3. Any performance concerns?'" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 3: Automated Status Updates + +**Use case**: Post Chatter updates from CI/CD or automation + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Post build success notification +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' \ + Body='✅ Build #${BUILD_NUMBER} passed +All tests: 156/156 ✓ +Coverage: 94% +Ready for deployment'" \ + --target-org "$DEFAULT_ORG" + +# Post failure notification +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' \ + Body='❌ Build #${BUILD_NUMBER} failed +Failed tests: 3/156 +See: ${BUILD_URL} +Please investigate.'" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 4: Post from Git Branch Context + +**Use case**: Automatically post to WI based on current git branch + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Extract WI number from branch +BRANCH=$(git rev-parse --abbrev-ref HEAD) +WI_NUMBER=$(echo "$BRANCH" | grep -oE '[0-9]{8}' | head -1) + +if [ -n "$WI_NUMBER" ]; then + # Query for work item + QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-${WI_NUMBER}'" \ + --target-org "$DEFAULT_ORG" \ + --json) + + WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + + if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + # Post update to work item + sf data create record \ + --sobject FeedItem \ + --values "ParentId=${WORK_ITEM_ID} \ + Body='Feature implementation completed and ready for review + +Branch: ${BRANCH} +Commits: $(git log --oneline -n 5 | head -5)'" \ + --target-org "$DEFAULT_ORG" + + echo "Posted update to W-${WI_NUMBER}" + else + echo "Work item W-${WI_NUMBER} not found" + fi +else + echo "No WI number found in branch: $BRANCH" +fi +``` + +### Pattern 5: Querying Chatter Feed (REST API) + +**Use case**: Read Chatter posts and comments + +**IMPORTANT**: FeedItem has strict query restrictions in SOQL. You cannot query FeedItem with `WHERE ParentId` or `WHERE CreatedById` filters. Use the Chatter REST API instead. + +```bash +# ❌ This SOQL query FAILS with "Implementation restriction: FeedItem requires a filter by Id" +# sf data query --query "SELECT Id, Body FROM FeedItem WHERE ParentId = 'a07xx00000ABCDE'" + +# ✅ CORRECT: Use Chatter REST API to query feed for a record +sf api request rest \ + "/services/data/v61.0/chatter/feeds/record/a07xx00000ABCDE/feed-elements" \ + --target-org "$DEFAULT_ORG" | jq -r '.elements[] | "[\(.createdDate)] \(.actor.name): \(.body.text)"' + +# Get full feed details with formatting +sf api request rest \ + "/services/data/v61.0/chatter/feeds/record/a07xx00000ABCDE/feed-elements?pageSize=10" \ + --target-org "$DEFAULT_ORG" | jq '.' + +# Query user's feed (user profile posts) +sf api request rest \ + "/services/data/v61.0/chatter/feeds/user-profile/005xx000001X8Uz/feed-elements" \ + --target-org "$DEFAULT_ORG" + +# Query comments on a specific feed item +sf api request rest \ + "/services/data/v61.0/chatter/feed-elements/0D5xx00000FGHIJ/capabilities/comments/items" \ + --target-org "$DEFAULT_ORG" | jq -r '.items[] | "[\(.createdDate)] \(.user.name): \(.body.text)"' + +# FeedComment CAN be queried with SOQL (unlike FeedItem) +sf data query \ + --query "SELECT CommentBody, CreatedBy.Name, CreatedDate + FROM FeedComment + WHERE FeedItemId = '0D5xx00000FGHIJ' + ORDER BY CreatedDate ASC" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 6: Link Posts + +**Use case**: Share URLs with previews + +```bash +# Post with link +sf data create record \ + --sobject FeedItem \ + --values "ParentId='a07xx00000ABCDE' \ + Type='LinkPost' \ + Body='Check out the documentation' \ + LinkUrl='https://docs.example.com/feature-guide'" \ + --target-org "$DEFAULT_ORG" + +# Post PR link +PR_URL="https://github.com/org/repo/pull/123" +sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' \ + Type='LinkPost' \ + Body='Pull request ready for review' \ + LinkUrl='${PR_URL}'" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Create post +sf data create record --sobject FeedItem --values "ParentId= Body=''" --target-org + +# Create comment +sf data create record --sobject FeedComment --values "FeedItemId= CommentBody=''" --target-org + +# Query feed (USE REST API - SOQL has strict limitations) +sf api request rest "/services/data/v61.0/chatter/feeds/record//feed-elements" --target-org + +# Query comments (SOQL works for FeedComment) +sf data query --query "SELECT CommentBody FROM FeedComment WHERE FeedItemId = ''" --target-org +``` + +**IMPORTANT**: FeedItem cannot be queried with `WHERE ParentId` or `WHERE CreatedById` in SOQL. Always use Chatter REST API for reading feeds. + +### Key Fields + +**FeedItem (Posts)**: +``` +ParentId - Record to post on (required) +Body - Post text (required, max 10000 chars) +Type - TextPost, LinkPost, ContentPost +LinkUrl - URL for LinkPost +Visibility - AllUsers, InternalUsers (default: AllUsers) +``` + +**FeedComment (Comments)**: +``` +FeedItemId - Post to comment on (required) +CommentBody - Comment text (required, max 10000 chars) +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Keep posts clear and concise +✅ DO: Use structured formatting for readability +✅ DO: Include relevant context and links +✅ DO: Validate ParentId exists before posting +✅ DO: Handle errors gracefully in automation +✅ DO: Use meaningful update messages +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Query FeedItem with SOQL WHERE clauses (use REST API) +❌ DON'T: Post sensitive information (passwords, tokens) +❌ DON'T: Spam feeds with automated posts +❌ DON'T: Use Chatter for private/confidential data +❌ DON'T: Forget to validate work item IDs +❌ DON'T: Post without checking if record exists +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Query FeedItem with WHERE ParentId (will fail) +sf data query \ + --query "SELECT Id, Body FROM FeedItem WHERE ParentId = 'a07xx00000ABCDE'" \ + --target-org "$DEFAULT_ORG" +# Error: Implementation restriction: FeedItem requires a filter by Id + +# ✅ CORRECT: Use Chatter REST API instead +sf api request rest \ + "/services/data/v61.0/chatter/feeds/record/a07xx00000ABCDE/feed-elements" \ + --target-org "$DEFAULT_ORG" +``` + +```bash +# ❌ NEVER: Post without validating ParentId +sf data create record \ + --sobject FeedItem \ + --values "ParentId='UNKNOWN_ID' Body='Update'" \ + --target-org "$DEFAULT_ORG" + +# ✅ CORRECT: Validate record exists +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +if [ -n "$WORK_ITEM_ID" ] && [ "$WORK_ITEM_ID" != "null" ]; then + sf data create record \ + --sobject FeedItem \ + --values "ParentId='${WORK_ITEM_ID}' Body='Update'" \ + --target-org "$DEFAULT_ORG" +else + echo "Error: Work item not found" +fi +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Never post sensitive data (credentials, tokens, PII) +- ⚠️ Chatter posts are visible to users with record access +- ⚠️ Don't use Chatter for confidential information +- ⚠️ Validate ParentId to avoid posting to wrong records +- ⚠️ Be mindful of @mentions and notifications + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication for Chatter operations +- `sf-soql-queries.md` - Query Chatter feeds +- `sf-work-items.md` - Post updates to work items +- `sf-automation.md` - Automate Chatter from git/CI + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-org-auth.md b/skills/salesforce/sf-org-auth.md new file mode 100644 index 0000000..7238a64 --- /dev/null +++ b/skills/salesforce/sf-org-auth.md @@ -0,0 +1,348 @@ +--- +name: salesforce-org-auth +description: Authenticate and manage Salesforce orgs using sf CLI. Use for login, org management, user information, and GUS authentication. +keywords: salesforce, gus, authentication, login, org, user info, sf cli, connect, session +--- + +# Salesforce Org Authentication + +**Scope**: Org authentication, connection management, and user information retrieval +**Lines**: ~348 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Logging into Salesforce orgs +- Managing multiple org connections +- Switching between orgs +- Retrieving current user information +- Getting org details and configuration +- Troubleshooting authentication issues + +--- + +## Core Concepts + +### Concept 1: Authentication Methods + +**Available Methods**: +- **Web login flow**: Interactive browser-based OAuth +- **JWT bearer flow**: Non-interactive for CI/CD +- **Access token**: Direct token authentication +- **SFDX auth URL**: Import existing authentication + +```bash +# Web login (most common - interactive) +sf org login web --alias my-org + +# Web login with custom instance +sf org login web --alias production --instance-url https://login.salesforce.com + +# JWT bearer flow (for CI/CD) +sf org login jwt --client-id YOUR_CONSUMER_KEY \ + --jwt-key-file server.key \ + --username user@example.com \ + --alias ci-org + +# Access token login +sf org login access-token --instance-url https://my-org.my.salesforce.com \ + --alias my-org +``` + +### Concept 2: Managing Multiple Orgs + +**Org Management Commands**: + +```bash +# List all authenticated orgs +sf org list + +# List with JSON output for scripting +sf org list --json + +# Get default org dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + # If no config default, check for org marked as default + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + # Fall back to first available org + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Display current org details (only if DEFAULT_ORG is set) +if [ -n "$DEFAULT_ORG" ]; then + sf org display --target-org "$DEFAULT_ORG" +else + echo "No default org found. Set one with: sf config set target-org=" +fi + +# Display with verbose output (includes auth URL) +sf org display --target-org "$DEFAULT_ORG" --verbose + +# Set default org +sf config set target-org=my-org + +# Open org in browser +sf org open --target-org "$DEFAULT_ORG" + +# Logout from org +sf org logout --target-org "$DEFAULT_ORG" + +# Logout from all orgs +sf org logout --all +``` + +--- + +## Patterns + +### Pattern 1: Getting Current User Information + +**Use case**: Dynamically retrieve the logged-in user's email and other details for queries + +**IMPORTANT**: Never hardcode user emails or org aliases. Always fetch the current user's email and default org dynamically from the authenticated org. + +```bash +# ❌ Bad: Hardcoding user email or org alias +USER_EMAIL="user@example.com" +DEFAULT_ORG="gus" + +# ✅ Good: Get default org dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') + +# If no default org is set, check for marked default or use first available +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Get current user email from default org +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +# Alternative: Get user details from org display user +USER_INFO=$(sf org display user --target-org "$DEFAULT_ORG" --json) +USER_EMAIL=$(echo "$USER_INFO" | jq -r '.result.email') +USER_ID=$(echo "$USER_INFO" | jq -r '.result.id') +USER_NAME=$(echo "$USER_INFO" | jq -r '.result.username') + +# Get user's org details +ORG_INFO=$(sf org display --target-org "$DEFAULT_ORG" --json) +ORG_ID=$(echo "$ORG_INFO" | jq -r '.result.id') +INSTANCE_URL=$(echo "$ORG_INFO" | jq -r '.result.instanceUrl') + +echo "Default Org: $DEFAULT_ORG" +echo "Logged in as: $USER_EMAIL" +echo "User ID: $USER_ID" +echo "Org ID: $ORG_ID" +echo "Instance: $INSTANCE_URL" +``` + +**Benefits**: +- Works across different users and orgs without code changes +- No need to hardcode usernames, emails, or org aliases +- Scripts are portable and can be shared with team members +- Safer - prevents accidentally using wrong user credentials or org +- Automatically adapts to the user's default org configuration + +**Key Points**: +- Use `sf config get target-org --json` to get the default org dynamically +- Use `sf org list --json` to get username from authenticated orgs +- Use `sf org display user --json` for complete user details including ID +- Fall back to most recently used org if no default org is set +- Use User ID (`Assignee__c`) in queries instead of email when possible (more efficient) +- Always validate that the user info and org were retrieved successfully before using + +### Pattern 2: Error Handling for Authentication + +**Use case**: Validate authentication and provide helpful error messages + +```bash +# Get default org with validation +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') + +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +if [ -z "$DEFAULT_ORG" ] || [ "$DEFAULT_ORG" = "null" ]; then + echo "Error: No default org configured and no authenticated orgs found." + echo "Run: sf org login web --alias my-org" + echo "Then: sf config set target-org=my-org" + exit 1 +fi + +# Get user email with validation +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +if [ -z "$USER_EMAIL" ] || [ "$USER_EMAIL" = "null" ]; then + echo "Error: Could not retrieve user email for org: $DEFAULT_ORG" + echo "Run: sf org login web --alias $DEFAULT_ORG" + exit 1 +fi + +echo "Querying work items for: $USER_EMAIL (org: $DEFAULT_ORG)" + +# Check if org is still connected +ORG_STATUS=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .connectedStatus') + +if [ "$ORG_STATUS" != "Connected" ]; then + echo "Error: Org '$DEFAULT_ORG' is not connected. Status: $ORG_STATUS" + echo "Please re-authenticate: sf org login web" + exit 1 +fi +``` + +### Pattern 3: Multi-Org Workflows + +**Use case**: Work with multiple orgs (dev, staging, production) + +```bash +# Setup multiple orgs with meaningful aliases +sf org login web --alias dev +sf org login web --alias staging +sf org login web --alias prod + +# Set default org +sf config set target-org=dev + +# Function to query across multiple orgs +query_all_orgs() { + local query="$1" + for org in dev staging prod; do + echo "=== Results from $org ===" + sf data query --query "$query" --target-org "$org" || echo "Failed for $org" + done +} + +# Use the function +query_all_orgs "SELECT Id, Name FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 5" + +# Get org info for all authenticated orgs +sf org list --json | jq -r '.result.nonScratchOrgs[] | "\(.alias): \(.username) - \(.instanceUrl)"' +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Authentication +sf org login web --alias # Login via browser +sf org list # List all orgs +sf org display --target-org # Show org details +sf org open --target-org # Open org in browser +sf org logout --target-org # Logout from org + +# User Information +sf org display user --target-org # Show current user details +sf org display user --json # JSON output for scripting + +# Configuration +sf config set target-org= # Set default org +sf config get target-org # Get default org +``` + +### Org Information Available via JSON + +From `sf org list --json`: +- `username` - User's email/username +- `alias` - Org alias +- `orgId` - Organization ID +- `instanceUrl` - Salesforce instance URL +- `connectedStatus` - Connection status +- `lastUsed` - Last access timestamp + +From `sf org display user --json`: +- `id` - User's Salesforce ID (use for queries) +- `username` - Username +- `email` - User's email +- `profileName` - User's profile +- `alias` - Org alias + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Use meaningful aliases for orgs (e.g., prod, staging, dev) +✅ DO: Set a default org with sf config set target-org +✅ DO: Dynamically fetch default org and user email/ID instead of hardcoding +✅ DO: Check org connection status before operations +✅ DO: Use --json flag for programmatic processing +✅ DO: Validate extracted values before using them +✅ DO: Store multiple org connections for different environments +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Hardcode user emails, org aliases, or org URLs +❌ DON'T: Assume an org is still authenticated +❌ DON'T: Skip validation of jq output (check for null/empty) +❌ DON'T: Use generic aliases like "org1" (use descriptive names) +❌ DON'T: Commit auth URLs or tokens to version control +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Hardcode user credentials or org aliases +USER_EMAIL="user@example.com" +USER_ID="005xx000001X8Uz" +DEFAULT_ORG="gus" + +# ✅ CORRECT: Fetch dynamically +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') +USER_ID=$(sf org display user --target-org "$DEFAULT_ORG" --json | jq -r '.result.id') +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Never commit authentication tokens or auth URLs +- ⚠️ Use `--verbose` flag carefully (exposes auth URL) +- ⚠️ Validate org alias before operations +- ⚠️ Logout from shared/public machines +- ⚠️ Use JWT for CI/CD instead of storing passwords +- ⚠️ Rotate access tokens regularly + +--- + +## Related Skills + +- `sf-soql-queries.md` - Query data using authenticated orgs +- `sf-record-operations.md` - Create/update records +- `sf-work-items.md` - Work with GUS objects +- `sf-bulk-operations.md` - Bulk data operations + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-record-operations.md b/skills/salesforce/sf-record-operations.md new file mode 100644 index 0000000..c0d0d44 --- /dev/null +++ b/skills/salesforce/sf-record-operations.md @@ -0,0 +1,438 @@ +--- +name: salesforce-record-operations +description: Create and update Salesforce records using sf CLI. Use for creating/updating GUS work items, epics, sprints, and any Salesforce object. +keywords: salesforce, gus, create, update, record, work item, epic, sprint, sf data, REST API, CRUD operations +--- + +# Salesforce Record Operations + +**Scope**: Creating, updating, and managing individual Salesforce records +**Lines**: ~438 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Creating new Salesforce records +- Updating existing records +- Working with individual records (not bulk operations) +- Setting field values on records +- Using REST API for custom operations + +--- + +## Core Concepts + +### Concept 1: Creating Records + +**Basic Syntax**: +```bash +sf data create record \ + --sobject \ + --values "Field1=Value1 Field2=Value2" \ + --target-org +``` + +**Examples**: + +```bash +# Create a simple record +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement new feature' Status__c='New' Type__c='User Story'" \ + --target-org "$DEFAULT_ORG" + +# Create with multiple fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Fix critical bug' \ + Status__c='New' \ + Priority__c='P1' \ + Type__c='Bug' \ + Description__c='

Bug details here

'" \ + --target-org "$DEFAULT_ORG" + +# Create an Epic +sf data create record \ + --sobject ADM_Epic__c \ + --values "Name='Q1 2024 Features' Health__c='On Track' Description__c='Major features for Q1'" \ + --target-org "$DEFAULT_ORG" +``` + +### Concept 2: Updating Records + +**Update by Record ID**: +```bash +sf data update record \ + --sobject \ + --record-id \ + --values "Field1=NewValue" \ + --target-org +``` + +**Update by Field Match**: +```bash +sf data update record \ + --sobject \ + --where "Field=Value" \ + --values "Field1=NewValue" \ + --target-org +``` + +**Examples**: + +```bash +# Update by ID +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Status__c='In Progress'" \ + --target-org "$DEFAULT_ORG" + +# Update multiple fields +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Status__c='Code Review' Assignee__c='005xx000001X8Uz' Story_Points__c=5" \ + --target-org "$DEFAULT_ORG" + +# Update by field match +sf data update record \ + --sobject ADM_Work__c \ + --where "Name='W-12345678'" \ + --values "Status__c='In Progress'" \ + --target-org "$DEFAULT_ORG" +``` + +### Concept 3: REST API Direct Calls + +**Use case**: Make custom REST API calls for operations not covered by standard commands + +```bash +# Get record details via REST API +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ + --target-org "$DEFAULT_ORG" + +# Update multiple fields via PATCH +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c/a07xx00000ABCDE" \ + --method PATCH \ + --body '{"Status__c": "Fixed", "Resolved_On__c": "2024-01-15"}' \ + --target-org "$DEFAULT_ORG" + +# Query using REST API +sf api request rest \ + "/services/data/v56.0/query?q=SELECT+Id,Name+FROM+ADM_Work__c+LIMIT+10" \ + --target-org "$DEFAULT_ORG" + +# Create record via REST API +sf api request rest \ + "/services/data/v56.0/sobjects/ADM_Work__c" \ + --method POST \ + --body '{"Subject__c": "New Story", "Status__c": "New", "Type__c": "User Story"}' \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Patterns + +### Pattern 1: Create Record with Query for IDs + +**Use case**: Create records with relationships to other objects + +```bash +# ❌ Bad: Hardcoding IDs +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Feature' Assignee__c='005xx000001X8Uz' Sprint__c='a1Fxx000002EFGH'" \ + --target-org "$DEFAULT_ORG" + +# ✅ Good: Query for IDs first +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Get user ID (using dynamic user email) +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# Get sprint ID +SPRINT_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# Get epic ID +EPIC_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# Create work item with relationships +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement OAuth' \ + Status__c='New' \ + Priority__c='P1' \ + Type__c='User Story' \ + Story_Points__c=8 \ + Assignee__c='${USER_ID}' \ + Sprint__c='${SPRINT_ID}' \ + Epic__c='${EPIC_ID}'" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 2: Update with Validation + +**Use case**: Update records only if they exist + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Query to verify record exists +QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG") + +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') + +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item W-12345678 not found" + exit 1 +fi + +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +# Update the record +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org "$DEFAULT_ORG" + +echo "Updated work item W-12345678 to In Progress" +``` + +### Pattern 3: Complex Field Updates with HTML + +**Use case**: Update rich text fields with HTML content + +```bash +# Update work item description with HTML +sf data update record \ + --sobject ADM_Work__c \ + --record-id a07xx00000ABCDE \ + --values "Description__c='

Overview

  • Item 1
  • Item 2

Additional details here.

'" \ + --target-org "$DEFAULT_ORG" + +# Create work item with formatted description +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Database migration' \ + Description__c='

Steps:

  1. Backup current data
  2. Run migration script
  3. Verify integrity
' \ + Status__c='New' \ + Type__c='User Story'" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 4: Atomic Updates with Error Handling + +**Use case**: Update multiple fields safely with rollback capability + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Capture current state before update +CURRENT_STATE=$(sf data query \ + --query "SELECT Id, Status__c, Assignee__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG") + +WORK_ITEM_ID=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Id') +OLD_STATUS=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Status__c') +OLD_ASSIGNEE=$(echo "$CURRENT_STATE" | jq -r '.result.records[0].Assignee__c') + +# Attempt update +if sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review' Assignee__c='${NEW_ASSIGNEE_ID}'" \ + --target-org "$DEFAULT_ORG"; then + echo "Update successful" +else + echo "Update failed! Rolling back..." + # Rollback to previous state + sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='${OLD_STATUS}' Assignee__c='${OLD_ASSIGNEE}'" \ + --target-org "$DEFAULT_ORG" + exit 1 +fi +``` + +--- + +## Quick Reference + +### Common Commands + +```bash +# Create record +sf data create record --sobject --values "Field=Value" --target-org + +# Update by ID +sf data update record --sobject --record-id --values "Field=Value" --target-org + +# Update by field match +sf data update record --sobject --where "Field=Value" --values "Field=NewValue" --target-org + +# REST API call +sf api request rest "" --method --target-org +``` + +### Required Fields by Object + +**ADM_Work__c (Work Items)**: +``` +Subject__c (required) +Status__c (required) +Type__c (required) +Priority__c (recommended) +``` + +**ADM_Epic__c (Epics)**: +``` +Name (required) +Health__c (recommended: On Track, At Risk, Off Track, Completed, Canceled) +Priority__c (optional: P0, P1, P2, P3) +Description__c (optional, HTML rich text) +Note: Epics do NOT have Status__c - use Health__c +``` + +**ADM_Sprint__c (Sprints)**: +``` +Name (required) +Start_Date__c (recommended) +End_Date__c (recommended) +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Query for IDs before creating related records +✅ DO: Include all required fields when creating records +✅ DO: Validate record existence before updates +✅ DO: Use HTML formatting for rich text fields +✅ DO: Handle errors and provide meaningful messages +✅ DO: Use --result-format json for scripting +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Hardcode record IDs (query for them) +❌ DON'T: Create records without required fields +❌ DON'T: Update records without verifying they exist +❌ DON'T: Forget __c suffix on custom fields +❌ DON'T: Skip error handling in scripts +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Update without checking existence +sf data update record \ + --sobject ADM_Work__c \ + --record-id UNKNOWN_ID \ + --values "Status__c='Fixed'" + +# ✅ CORRECT: Query first, then update +RECORD_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name='W-12345678'" \ + --result-format json --target-org gus | jq -r '.result.records[0].Id') + +if [ -z "$RECORD_ID" ] || [ "$RECORD_ID" = "null" ]; then + echo "Error: Record not found" + exit 1 +fi + +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$RECORD_ID" \ + --values "Status__c='Fixed'" \ + --target-org "$DEFAULT_ORG" +``` + +```bash +# ❌ Don't: Missing required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='New Story'" \ + --target-org "$DEFAULT_ORG" + +# ✅ Correct: Include all required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='New Story' Status__c='New' Type__c='User Story' Priority__c='P2'" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Always verify target org before updates +- ⚠️ Query before update to verify record existence +- ⚠️ Validate all input values +- ⚠️ Be cautious with delete operations (use bulk with care) +- ⚠️ Don't expose sensitive data in field values + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication for operations +- `sf-soql-queries.md` - Query for record IDs +- `sf-work-items.md` - Work with GUS-specific objects +- `sf-bulk-operations.md` - Bulk updates for multiple records + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) diff --git a/skills/salesforce/sf-soql-advanced.md b/skills/salesforce/sf-soql-advanced.md new file mode 100644 index 0000000..9b4e6fe --- /dev/null +++ b/skills/salesforce/sf-soql-advanced.md @@ -0,0 +1,292 @@ +--- +name: salesforce-soql-advanced +description: Advanced SOQL queries for GUS - work items, aggregation, date filters, and tooling API. For complex queries beyond basic lookups. +keywords: salesforce, gus, soql, advanced, work items, aggregation, date filters, tooling api, epic queries, sprint queries, complex queries +--- + +# Salesforce SOQL Advanced Queries + +**Scope**: Advanced SOQL patterns for work items, aggregation, date filtering, and tooling API +**Lines**: ~292 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) + +--- + +## When to Use This Skill + +Activate this skill when: +- **Querying work items by various criteria** - Assignee, sprint, epic, status +- **Aggregating data** - Counts, sums, grouped results +- **Filtering by dates** - Date ranges, relative dates (THIS_WEEK, LAST_N_DAYS) +- **Querying metadata** - Apex classes, triggers using Tooling API +- **Complex multi-condition queries** - Multiple filters and relationships + +**Prerequisites**: You should understand basic SOQL from `sf-soql-basics.md` first. + +**⚠️ CRITICAL**: Always run `sf sobject describe` before querying - see `sf-soql-basics.md` for field verification workflow. + +--- + +## Advanced Patterns + +### Pattern 1: Querying Work Items + +**Use case**: Find work items by various criteria (assignee, sprint, epic, status) + +```bash +# Get default org and current user's work items dynamically +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c, Type__c, Sprint__c + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + AND Status__c != 'Closed' + ORDER BY Priority__c, CreatedDate DESC" \ + --target-org "$DEFAULT_ORG" + +# Query by work item name (WI number) +sf data query \ + --query "SELECT Id, Name, Subject__c, Status__c FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --target-org "$DEFAULT_ORG" + +# Query by sprint +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Assignee__r.Name, Story_Points__c + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + ORDER BY Status__c, Priority__c" \ + --target-org "$DEFAULT_ORG" + +# Query by epic +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Sprint__r.Name + FROM ADM_Work__c + WHERE Epic__r.Name LIKE '%Q1 Features%' + AND Status__c NOT IN ('Fixed', 'Closed')" \ + --target-org "$DEFAULT_ORG" + +# Query by type and priority +sf data query \ + --query "SELECT Name, Subject__c, Assignee__r.Name, Sprint__r.Name + FROM ADM_Work__c + WHERE Type__c = 'Bug' + AND Priority__c = 'P1' + AND Status__c NOT IN ('Fixed', 'Not a Bug')" \ + --target-org "$DEFAULT_ORG" + +# Query user's epics +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +sf data query \ + --query "SELECT Id, Name, Description__c, Health__c, Priority__c, Owner.Name, LastModifiedDate + FROM ADM_Epic__c + WHERE Owner.Email = '${USER_EMAIL}' + ORDER BY LastModifiedDate DESC" \ + --target-org "$DEFAULT_ORG" + +# Query work items with epics (filter non-null with jq) +# IMPORTANT: Avoid != in queries due to shell escaping - filter with jq instead +sf data query \ + --query "SELECT Id, Name, Subject__c, Epic__c, Epic__r.Name, Epic__r.Id + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + ORDER BY LastModifiedDate DESC + LIMIT 50" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name) - \(.Name): \(.Subject__c)"' | sort -u +``` + +**Note on filtering null values**: Due to shell escaping issues with `!=` (the `!` triggers history expansion), it's best to query all records and filter with jq using `select(.Epic__r)` to check for non-null relationships. + +### Pattern 2: Complex Queries with Aggregation + +**Use case**: Get counts, sums, and grouped data + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# Count work items by status +sf data query \ + --query "SELECT Status__c, COUNT(Id) total FROM ADM_Work__c GROUP BY Status__c" \ + --target-org "$DEFAULT_ORG" + +# Sum story points by sprint (filter null sprints with jq) +sf data query \ + --query "SELECT Sprint__r.Name, SUM(Story_Points__c) total_points + FROM ADM_Work__c + GROUP BY Sprint__r.Name" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Sprint__r) | "\(.Sprint__r.Name): \(.total_points) points"' + +# Count by assignee +sf data query \ + --query "SELECT Assignee__r.Name, COUNT(Id) work_count + FROM ADM_Work__c + WHERE Status__c IN ('New', 'In Progress') + GROUP BY Assignee__r.Name + ORDER BY COUNT(Id) DESC" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 3: Querying with Date Filters + +**Use case**: Find records by date ranges (relative and absolute) + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# Work items created this week +sf data query \ + --query "SELECT Name, Subject__c, CreatedDate FROM ADM_Work__c WHERE CreatedDate = THIS_WEEK" \ + --target-org "$DEFAULT_ORG" + +# Work items updated in last 7 days +sf data query \ + --query "SELECT Name, Subject__c, LastModifiedDate FROM ADM_Work__c WHERE LastModifiedDate = LAST_N_DAYS:7" \ + --target-org "$DEFAULT_ORG" + +# Work items created in date range (absolute dates) +sf data query \ + --query "SELECT Name, Subject__c, CreatedDate + FROM ADM_Work__c + WHERE CreatedDate >= 2024-01-01T00:00:00Z + AND CreatedDate <= 2024-01-31T23:59:59Z" \ + --target-org "$DEFAULT_ORG" + +# Sprints active in date range +sf data query \ + --query "SELECT Name, Start_Date__c, End_Date__c + FROM ADM_Sprint__c + WHERE Start_Date__c <= 2024-12-03 + AND End_Date__c >= 2024-12-03" \ + --target-org "$DEFAULT_ORG" +``` + +**Date Literals Reference**: +``` +TODAY Current day +THIS_WEEK Current week +THIS_MONTH Current month +LAST_N_DAYS:n Last n days +NEXT_N_DAYS:n Next n days +LAST_WEEK Previous week +THIS_QUARTER Current quarter +``` + +### Pattern 4: Using Tooling API + +**Use case**: Query metadata objects (Apex classes, triggers, custom fields) + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# Query Apex classes +sf data query \ + --query "SELECT Name, ApiVersion, LengthWithoutComments FROM ApexClass" \ + --use-tooling-api \ + --target-org "$DEFAULT_ORG" + +# Query Apex triggers +sf data query \ + --query "SELECT Name, TableEnumOrId, Status FROM ApexTrigger" \ + --use-tooling-api \ + --target-org "$DEFAULT_ORG" + +# Query custom fields on a specific object +sf data query \ + --query "SELECT DeveloperName, DataType, TableEnumOrId FROM CustomField WHERE TableEnumOrId = 'ADM_Work__c'" \ + --use-tooling-api \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Best Practices for Advanced Queries + +**Advanced Query Guidelines**: +``` +✅ DO: Use aggregation (COUNT, SUM) instead of querying all records and counting in code +✅ DO: Use date literals (THIS_WEEK, LAST_N_DAYS:7) for relative date queries +✅ DO: Filter null values with jq when ! causes shell escaping issues +✅ DO: Use IN and NOT IN for multiple value matching +✅ DO: Use ORDER BY to sort results server-side +✅ DO: Add LIMIT to aggregation queries to avoid timeouts +✅ DO: Use relationship fields (Epic__r.Name) in WHERE clauses +``` + +**Common Mistakes**: +``` +❌ DON'T: Use != in double-quoted strings (shell escapes the !) +❌ DON'T: Query without LIMIT on large objects (can timeout) +❌ DON'T: Forget to filter null relationships - use jq select() +❌ DON'T: Hardcode date values - use date literals when possible +❌ DON'T: Use LIKE with % on both sides (performance issue) +``` + +--- + +## Advanced GUS Object Fields (Verified) + +**ADM_Epic__c (Epics)**: +``` +Id, Name, Description__c, Health__c (NOT Status__c!) +Priority__c, OwnerId (→User) +CreatedDate, LastModifiedDate +``` + +**ADM_Sprint__c (Sprints)**: +``` +Id, Name, Start_Date__c, End_Date__c +Scrum_Team__c (→ADM_Scrum_Team__c) +CreatedDate, LastModifiedDate +``` + +**ADM_Build__c (Builds)**: +``` +Id, Name, CreatedDate, LastModifiedDate +``` + +**ADM_Product_Tag__c (Product Tags)**: +``` +Id, Name, CreatedDate, LastModifiedDate +``` + +**IMPORTANT**: These are verified but may vary by org. Always run `sf sobject describe` to verify fields in YOUR org before querying. + +--- + +## Common SOQL Operators for Advanced Queries + +``` += Equal to +!= Not equal to (avoid in double quotes - shell escapes !) +< Less than +> Greater than +<= Less than or equal +>= Greater than or equal +LIKE Pattern match (use % for wildcard) +IN Match any value in list +NOT IN Don't match any value in list +``` + +--- + +## Related Skills + +- `sf-soql-basics.md` - Start here for field discovery and basic queries +- `sf-soql-troubleshooting.md` - Error handling and anti-patterns +- `sf-work-items.md` - Creating and managing work items +- `sf-bulk-operations.md` - Bulk data operations and exports +- `sf-org-auth.md` - Authentication and user info + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-soql-basics.md b/skills/salesforce/sf-soql-basics.md new file mode 100644 index 0000000..48b2402 --- /dev/null +++ b/skills/salesforce/sf-soql-basics.md @@ -0,0 +1,402 @@ +--- +name: salesforce-soql-basics +description: Basic SOQL queries, field discovery, and fundamental patterns for Salesforce. Start here for querying GUS work items, users, and basic data retrieval. +keywords: salesforce, gus, soql, query, basics, field verification, sf sobject describe, beginner, getting started, team lookup, user query +--- + +# Salesforce SOQL Basics + +**Scope**: Basic SOQL syntax, mandatory field discovery, and fundamental query patterns +**Lines**: ~402 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) + +--- + +## When to Use This Skill + +Activate this skill when: +- **Starting with SOQL queries** - Learn the basics first +- **Querying simple data** - Users, work items by ID, basic lookups +- **Learning field verification** - Understand the mandatory describe workflow +- **Looking up team memberships** - Find which teams a user belongs to +- **Finding record IDs** - Locate User IDs, Epic IDs, etc. + +**⚠️ CRITICAL REQUIREMENT**: Before writing ANY SOQL query, you MUST first run `sf sobject describe --sobject ` to verify field names. DO NOT skip this step. DO NOT assume field names exist, even if they seem obvious (like Department, Title, Team__c on User object). + +**For advanced queries**: See `sf-soql-advanced.md` +**For troubleshooting**: See `sf-soql-troubleshooting.md` + +--- + +## Core Concepts + +### Concept 1: MANDATORY Field Discovery Before Querying + +**CRITICAL RULE**: You MUST use `sf sobject describe` BEFORE writing any SOQL query with fields beyond Id and Name. This is NOT optional. + +**MANDATORY WORKFLOW**: +1. **FIRST**: Always run `sf sobject describe --sobject ` +2. **SECOND**: Verify the exact field names exist in the output +3. **THIRD**: Only then write your SOQL query using verified field names + +**NEVER query fields without verifying them first**, even if they seem obvious (Department, Title, Team__c, etc.). + +```bash +# STEP 1: ALWAYS describe the object FIRST +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" + +# STEP 2: Search for specific fields (e.g., team-related) +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team + +# STEP 3: Find relationship fields +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' + +# STEP 4: Only after verification, write your query using confirmed field names +``` + +**Why this matters**: Guessing field names leads to errors like: +- `No such column 'Team__c' on entity 'User'` (field doesn't exist) +- `No such column 'Department' on entity 'User'` (field may not exist) +- Wrong field type or reference target +- Missing junction objects for many-to-many relationships + +**Common fields that DON'T exist**: +- User.Team__c (use ADM_Scrum_Team_Member__c junction object instead) +- User.Department (may not exist in all orgs) +- User.Division (may not exist in all orgs) + +### Concept 2: Basic SOQL Syntax + +**Query Structure**: +```sql +SELECT fields FROM object WHERE conditions ORDER BY field LIMIT n +``` + +**Field Selection**: +- Standard fields: `Id, Name, CreatedDate` +- Custom fields: Use `__c` suffix (e.g., `Status__c, Subject__c`) +- Related fields: Use `__r` for relationships (e.g., `Assignee__r.Email`) + +```bash +# Get default org (add this at the start of scripts) +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# Basic query +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c LIMIT 10" \ + --target-org "$DEFAULT_ORG" + +# Query with conditions +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c WHERE Status__c = 'New'" \ + --target-org "$DEFAULT_ORG" + +# Query with related fields +sf data query \ + --query "SELECT Id, Subject__c, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Status__c = 'In Progress'" \ + --target-org "$DEFAULT_ORG" + +# Query with ordering and limit +sf data query \ + --query "SELECT Id, Name, CreatedDate FROM ADM_Work__c ORDER BY CreatedDate DESC LIMIT 20" \ + --target-org "$DEFAULT_ORG" +``` + +### Concept 3: Relationship Names - CRITICAL RULES + +**CRITICAL**: Relationship names (the `__r` suffix) are DIFFERENT from object names. NEVER assume the relationship name matches the object name. + +**Common Mistake**: +```sql +-- ❌ WRONG: Using object name as relationship +SELECT ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c + +-- ✅ CORRECT: Using actual relationship name from describe +SELECT Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c +``` + +**How to get the correct relationship name**: +```bash +# ALWAYS use sf sobject describe to get the relationship name +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' + +# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r","referenceTo":["ADM_Scrum_Team__c"]} +``` + +**Key Rule**: The relationship name is in the `relationshipName` field from `sf sobject describe`, NOT derived from the object name. + +### Concept 4: Verified Field Names for Common GUS Objects + +**IMPORTANT**: These are VERIFIED via `sf sobject describe` but you should still verify them in YOUR org before querying. + +**ADM_Work__c (Work Items)**: +``` +Id, Name, Subject__c, Status__c, Priority__c, Type__c +Story_Points__c, Assignee__c (→User), Sprint__c (→ADM_Sprint__c) +Epic__c (→ADM_Epic__c), Found_in_Build__c (→ADM_Build__c) +Product_Tag__c (→ADM_Product_Tag__c), Description__c +CreatedDate, LastModifiedDate +``` + +**ADM_Scrum_Team_Member__c (Team Membership - Junction Object)**: +``` +Id, Name (auto-number: STM-######) +Member_Name__c (→User - ID field, use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field with team name) +Internal_Email__c (member's email address) +Role__c (member's role on team) +Allocation__c, Department__c, Functional_Area__c +Active__c (boolean indicating active membership) +CreatedDate, LastModifiedDate + +CRITICAL NOTES: +- Member_Name__c is a User ID lookup field - use = for exact match, NOT LIKE +- Name is auto-numbered (STM-######), not searchable by member name +- Use Internal_Email__c for email, NOT Email__c (doesn't exist) +- No User__c field exists - use Member_Name__c instead +``` + +**User (Standard Object)**: +``` +Id, Name, Email, Username, IsActive, ProfileId +Note: No Team__c field - use ADM_Scrum_Team_Member__c junction object +``` + +**Related Field Notation (VERIFIED)**: +``` +# From ADM_Work__c +Assignee__r.Name # User name (field: Assignee__c → User) +Assignee__r.Email # User email (field: Assignee__c → User) +Sprint__r.Name # Sprint name (field: Sprint__c → ADM_Sprint__c) +Epic__r.Name # Epic name (field: Epic__c → ADM_Epic__c) + +# From ADM_Scrum_Team_Member__c +Scrum_Team__r.Name # Team name (field: Scrum_Team__c → ADM_Scrum_Team__c) + # NOTE: Relationship is Scrum_Team__r, NOT ADM_Scrum_Team__r! + +# CRITICAL: Relationship names come from the relationshipName field in sf sobject describe, +# NOT from the object name! Always verify with describe before using. +``` + +### Concept 5: Output Formats + +**Available Formats**: +- `human` - Table format (default) +- `json` - JSON output for scripting +- `csv` - CSV format for spreadsheets + +```bash +# Human-readable table (default) +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 5" \ + --target-org "$DEFAULT_ORG" + +# JSON output for scripting +sf data query \ + --query "SELECT Id, Name FROM ADM_Work__c LIMIT 5" \ + --target-org "$DEFAULT_ORG" \ + --result-format json + +# CSV output for Excel +sf data query \ + --query "SELECT Id, Name, Status__c FROM ADM_Work__c LIMIT 100" \ + --target-org "$DEFAULT_ORG" \ + --result-format csv > work_items.csv +``` + +--- + +## Basic Patterns + +### Pattern 1: Discovering Object Fields (MANDATORY FIRST STEP) + +**Use case**: Find available fields on an object before querying (avoids INVALID_FIELD errors) + +**MANDATORY**: You MUST ALWAYS use `sf sobject describe` as the FIRST STEP before writing ANY query. This is NOT optional, even for fields that seem obvious. + +**WORKFLOW - FOLLOW THESE STEPS IN ORDER**: + +**STEP 1: Describe the object to see available fields** +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# MANDATORY: Describe the object FIRST +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" +``` + +**STEP 2: Verify the specific fields you want to query exist** +```bash +# Search for specific field names (e.g., team-related) +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i team + +# If grep returns nothing, the field doesn't exist - find the junction object instead +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User") | {name: .name, label: .label, relationshipName: .relationshipName}' +``` + +**STEP 3: Extract verified field names programmatically (RECOMMENDED)** +```bash +# Get all custom fields (fields ending in __c) +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.name | endswith("__c")) | {name: .name, label: .label, type: .type}' + +# Find relationship fields (__r notation) +sf sobject describe --sobject ADM_Work__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.relationshipName != null) | {name: .name, relationshipName: .relationshipName, referenceTo: .referenceTo}' +``` + +**STEP 4: Only after verification, write your query** +```bash +# Example: Finding team membership fields +# After discovering that User doesn't have Team__c, we found ADM_Scrum_Team_Member__c has Member_Name__c +USER_ID="005EE000001JW5FYAW" + +# Query teams through the verified junction object +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +**Benefits**: +- Avoids INVALID_FIELD errors from guessing field names +- Discovers correct relationship field names (__r notation) +- Finds junction objects for many-to-many relationships +- Identifies custom vs standard fields + +### Pattern 2: Querying Team Memberships (Common Example) + +**Use case**: Find which Scrum teams a user belongs to + +**COMMON ERRORS** (see sf-soql-troubleshooting.md for details): +- Using non-existent `User__c` field (correct field is `Member_Name__c`) +- Using `LIKE` on ID fields (must use `=` for exact match) +- Looking for `Email__c` field (correct field is `Internal_Email__c`) + +**CORRECT APPROACH** (following mandatory field verification): +```bash +# STEP 1: ALWAYS describe the object first +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.custom == true) | {name: .name, type: .type, referenceTo: .referenceTo}' + +# STEP 2: Query using verified field names +USER_ID="005EE000001JW5FYAW" + +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Member_Name__c, Role__c, Internal_Email__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" + +# Get just team names as a list +sf data query \ + --query "SELECT Scrum_Team__r.Name + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | .Scrum_Team__r.Name' +``` + +### Pattern 3: Finding Record IDs + +**Use case**: Locate record IDs for references (Users, Epics, Sprints, Product Tags) + +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# Find user ID by name +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Name LIKE '%John Doe%'" \ + --target-org "$DEFAULT_ORG" + +# Find user ID by email (use dynamic user email) +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') +sf data query \ + --query "SELECT Id, Name, Email FROM User WHERE Email = '${USER_EMAIL}'" \ + --target-org "$DEFAULT_ORG" + +# Get ID with jq for scripting +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +echo "User ID: $USER_ID" +``` + +--- + +## Best Practices + +**Essential Practices (IN ORDER OF IMPORTANCE)**: +``` +✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY, NOT OPTIONAL +✅ DO: Verify field names exist in describe output before writing SELECT statements +✅ DO: Get relationship names from the relationshipName field in describe (NOT from object names) +✅ DO: Use --result-format json for scripting +✅ DO: Use LIMIT to avoid timeouts on large datasets +✅ DO: Validate query results before using extracted values +``` + +**Common Mistakes to Avoid (CRITICAL)**: +``` +❌ DON'T: EVER query fields without running sf sobject describe first - THIS CAUSES MOST ERRORS +❌ DON'T: Assume relationship names match object names (use relationshipName from describe) +❌ DON'T: Use ADM_Scrum_Team__r when the actual relationship is Scrum_Team__r +❌ DON'T: Assume ANY field exists without verification (not even Department, Title, or Team__c) +❌ DON'T: Guess field names without verifying (ALWAYS use sf sobject describe) +❌ DON'T: Use LIKE on ID/reference fields (use = for exact match) +``` + +--- + +## Quick Reference + +### Common SOQL Operators +``` += Equal to +!= Not equal to (avoid in double quotes - shell escapes !) +< Less than +> Greater than +<= Less than or equal +>= Greater than or equal +LIKE Pattern match (use % for wildcard, NOT for ID fields) +IN Match any value in list +NOT IN Don't match any value in list +``` + +### Common Fields (Verified) + +**Quick Reference - Most Used Fields**: +``` +ADM_Work__c: Subject__c, Status__c, Priority__c, Type__c, Assignee__c, Sprint__c, Epic__c +ADM_Scrum_Team_Member__c: Member_Name__c, Scrum_Team__c, Internal_Email__c, Role__c +User: Name, Email, Username (no Team__c - use ADM_Scrum_Team_Member__c) +``` + +**IMPORTANT**: When uncertain about fields, always use `sf sobject describe --sobject ` to verify field existence and names before querying. + +--- + +## Related Skills + +- `sf-soql-advanced.md` - Complex queries, aggregation, date filters, tooling API +- `sf-soql-troubleshooting.md` - Anti-patterns, common errors, and solutions +- `sf-org-auth.md` - Authentication and user info +- `sf-record-operations.md` - Create/update records +- `sf-work-items.md` - Work with GUS objects + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-soql-queries.md b/skills/salesforce/sf-soql-queries.md new file mode 100644 index 0000000..d41b17e --- /dev/null +++ b/skills/salesforce/sf-soql-queries.md @@ -0,0 +1,232 @@ +--- +name: salesforce-soql-queries +description: Query Salesforce data using SOQL and sf CLI. Use for GUS work items, epics, sprints, teams, Agile Accelerator queries, and any Salesforce object queries. +keywords: salesforce, gus, soql, query, work items, epic, sprint, team, agile accelerator, ADM_Work__c, ADM_Epic__c, ADM_Sprint__c, ADM_Scrum_Team_Member__c, user, field verification +--- + +# Salesforce SOQL Queries (Navigation) + +**Scope**: Navigation guide to SOQL skill files +**Last Updated**: 2025-12-03 +**Format Version**: 2.0 (Split into focused skill files) + +--- + +## Overview + +This skill has been split into three focused files for better organization and faster loading: + +1. **sf-soql-basics.md** - Start here for core concepts and basic queries +2. **sf-soql-advanced.md** - Complex queries, aggregation, and advanced patterns +3. **sf-soql-troubleshooting.md** - Error handling, anti-patterns, and debugging + +--- + +## Which Skill File Should I Use? + +### Use sf-soql-basics.md When: +- **Starting with SOQL queries** - Learn the basics first +- **Querying simple data** - Users, work items by ID, basic lookups +- **Learning field verification** - Understand the mandatory describe workflow +- **Looking up team memberships** - Find which teams a user belongs to +- **Finding record IDs** - Locate User IDs, Epic IDs, etc. + +**Key Concepts**: MANDATORY field discovery, basic SOQL syntax, relationship names, verified field names, output formats + +**Example Patterns**: +- Discovering object fields (MANDATORY FIRST STEP) +- Querying team memberships +- Finding record IDs +- Basic work item queries + +--- + +### Use sf-soql-advanced.md When: +- **Querying work items by various criteria** - Assignee, sprint, epic, status +- **Aggregating data** - Counts, sums, grouped results +- **Filtering by dates** - Date ranges, relative dates (THIS_WEEK, LAST_N_DAYS) +- **Querying metadata** - Apex classes, triggers using Tooling API +- **Complex multi-condition queries** - Multiple filters and relationships + +**Key Concepts**: Work item queries, aggregation (COUNT, SUM), date filters, tooling API + +**Example Patterns**: +- Querying work items by assignee, sprint, epic, type +- Complex queries with aggregation (counts, sums, grouped data) +- Date filtering (relative and absolute) +- Using Tooling API for metadata + +--- + +### Use sf-soql-troubleshooting.md When: +- **Debugging SOQL query errors** - INVALID_FIELD, relationship errors, operator errors +- **Understanding common mistakes** - Why queries fail and how to fix them +- **Learning anti-patterns** - What NOT to do in SOQL queries +- **Troubleshooting field verification** - Field doesn't exist errors +- **Fixing relationship queries** - Wrong relationship name errors + +**Key Concepts**: Critical violations, common field errors, query performance, troubleshooting workflow + +**Critical Violations Covered**: +1. Querying fields without verification (MOST COMMON) +2. Assuming relationship names match object names (VERY COMMON) +3. Using shell special characters (! in queries) +4. Using LIKE on ID/reference fields +5. Not validating query results + +--- + +## Quick Start Guide + +### I'm New to SOQL +**Start with**: [sf-soql-basics.md](sf-soql-basics.md) +- Read Concept 1 (MANDATORY field discovery) +- Read Concept 2 (Basic SOQL syntax) +- Read Concept 3 (Relationship names) +- Try Pattern 1 (Discovering object fields) + +### I Need to Query Work Items +**Start with**: [sf-soql-basics.md](sf-soql-basics.md) for simple queries, then [sf-soql-advanced.md](sf-soql-advanced.md) for complex filtering +- sf-soql-basics.md: Basic work item queries by ID or simple criteria +- sf-soql-advanced.md: Pattern 1 (Querying work items by assignee, sprint, epic) + +### I'm Getting Errors +**Start with**: [sf-soql-troubleshooting.md](sf-soql-troubleshooting.md) +- Critical Violation #1: Field doesn't exist errors +- Critical Violation #2: Relationship name errors +- Critical Violation #3: Shell escaping errors +- Critical Violation #4: LIKE on ID fields errors +- Common Field Errors section + +### I Need Aggregation or Complex Queries +**Start with**: [sf-soql-advanced.md](sf-soql-advanced.md) +- Pattern 2 (Complex queries with aggregation) +- Pattern 3 (Querying with date filters) +- Pattern 4 (Using Tooling API) + +--- + +## Common Workflows + +### Workflow 1: Query User's Teams +1. **sf-soql-basics.md** - Concept 1: Run sf sobject describe for ADM_Scrum_Team_Member__c +2. **sf-soql-basics.md** - Pattern 2: Query team memberships +3. **sf-soql-troubleshooting.md** - If errors occur, check Critical Violations + +### Workflow 2: Find Work Items by Epic +1. **sf-soql-basics.md** - Pattern 3: Find Epic ID by name +2. **sf-soql-advanced.md** - Pattern 1: Query work items by epic +3. **sf-soql-troubleshooting.md** - If errors occur, check field verification + +### Workflow 3: Sprint Reporting +1. **sf-soql-basics.md** - Pattern 3: Find Sprint ID +2. **sf-soql-advanced.md** - Pattern 2: Aggregate story points by status +3. **sf-soql-advanced.md** - Pattern 3: Filter by date range + +--- + +## Critical Rules (Apply to All SOQL Queries) + +**⚠️ MANDATORY FIELD VERIFICATION**: +``` +BEFORE writing ANY SOQL query, you MUST: +1. Run `sf sobject describe --sobject ` +2. Verify the exact field names exist in the output +3. Only then write your SOQL query using verified field names + +This is NOT optional. This is MANDATORY. +``` + +**⚠️ RELATIONSHIP NAMES**: +``` +Relationship names (the __r suffix) are DIFFERENT from object names. +NEVER assume the relationship name matches the object name. + +Example: +- Field: Scrum_Team__c +- Object: ADM_Scrum_Team__c +- Relationship: Scrum_Team__r (NOT ADM_Scrum_Team__r!) + +ALWAYS use sf sobject describe to get the relationshipName field. +``` + +**⚠️ COMMON MISTAKES**: +``` +❌ DON'T: Query fields without sf sobject describe first +❌ DON'T: Assume relationship names match object names +❌ DON'T: Use LIKE on ID/reference fields (use = instead) +❌ DON'T: Use != in CLI queries (use <> instead - shell escapes !) +❌ DON'T: Check relationship name fields for null (check ID field instead) +❌ DON'T: Assume ANY field exists without verification +``` + +--- + +## Verified Field Names (Quick Reference) + +**IMPORTANT**: These are verified but may vary by org. ALWAYS run `sf sobject describe` to verify fields in YOUR org before querying. + +**ADM_Work__c (Work Items)**: +``` +Id, Name, Subject__c, Status__c, Priority__c, Type__c +Story_Points__c, Assignee__c (→User), Sprint__c (→ADM_Sprint__c) +Epic__c (→ADM_Epic__c), Found_in_Build__c (→ADM_Build__c) +Product_Tag__c (→ADM_Product_Tag__c), Description__c +``` + +**ADM_Scrum_Team_Member__c (Team Membership)**: +``` +Id, Name (auto-number: STM-######) +Member_Name__c (→User - use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field) +Internal_Email__c (NOT Email__c!) +Role__c, Active__c +``` + +**User (Standard Object)**: +``` +Id, Name, Email, Username, IsActive +Note: No Team__c field - use ADM_Scrum_Team_Member__c junction object +``` + +**Related Field Notation (VERIFIED)**: +``` +Assignee__r.Name # User name +Assignee__r.Email # User email +Sprint__r.Name # Sprint name +Epic__r.Name # Epic name +Scrum_Team__r.Name # Team name (NOT ADM_Scrum_Team__r!) +``` + +--- + +## Related Skills + +- `sf-org-auth.md` - Authentication and user info +- `sf-record-operations.md` - Create/update records +- `sf-work-items.md` - Work with GUS objects +- `sf-bulk-operations.md` - Export large datasets +- `sf-chatter.md` - Post Chatter updates +- `sf-automation.md` - Git integration and automation + +--- + +## File Organization + +**Skill Files**: +- [sf-soql-basics.md](sf-soql-basics.md) - ~402 lines, v1.0 +- [sf-soql-advanced.md](sf-soql-advanced.md) - ~292 lines, v1.0 +- [sf-soql-troubleshooting.md](sf-soql-troubleshooting.md) - ~507 lines, v1.0 +- [sf-soql-queries.md](sf-soql-queries.md) - ~232 lines (navigation file), v2.0 + +**Version History**: +- v2.0 (2025-12-03): Split into focused skill files (basics, advanced, troubleshooting) +- v1.2 (2025-12-03): Added mandatory field verification and relationship name rules +- v1.1 (2025-12-03): Added keywords for GUS/team/epic discoverability +- v1.0 (2025-12-02): Initial unified skill file + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 2.0 (Navigation file for split skills) diff --git a/skills/salesforce/sf-soql-troubleshooting.md b/skills/salesforce/sf-soql-troubleshooting.md new file mode 100644 index 0000000..dbcfac7 --- /dev/null +++ b/skills/salesforce/sf-soql-troubleshooting.md @@ -0,0 +1,507 @@ +--- +name: salesforce-soql-troubleshooting +description: Common SOQL errors, anti-patterns, and troubleshooting for Salesforce queries. Debug field verification, relationship errors, and query failures. +keywords: salesforce, soql, troubleshooting, errors, anti-patterns, debug, invalid field, relationship errors, query failures, field verification errors +--- + +# Salesforce SOQL Troubleshooting + +**Scope**: Common SOQL errors, anti-patterns, and solutions +**Lines**: ~507 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) + +--- + +## When to Use This Skill + +Activate this skill when: +- **Debugging SOQL query errors** - INVALID_FIELD, relationship errors, operator errors +- **Understanding common mistakes** - Why queries fail and how to fix them +- **Learning anti-patterns** - What NOT to do in SOQL queries +- **Troubleshooting field verification** - Field doesn't exist errors +- **Fixing relationship queries** - Wrong relationship name errors + +**Prerequisites**: You should understand basic SOQL from `sf-soql-basics.md` first. + +--- + +## Critical Violations (Most Common Errors) + +### Critical Violation #1: Querying Fields Without Verification (MOST COMMON ERROR) + +**Error Message**: `No such column 'Team__c' on entity 'User'` or `No such column 'Department' on entity 'User'` + +**Why This Happens**: +- Field names vary across Salesforce orgs and implementations +- Custom fields that exist in one org may not exist in another +- Assuming field names without verification WILL cause failures +- User.Team__c, User.Department, User.Division are NOT guaranteed to exist + +**Example of the Error**: +```bash +# ❌ NEVER: Query fields without first running sf sobject describe +# This is the #1 cause of INVALID_FIELD errors +sf data query --query "SELECT Id, Name, Username, Email, Department, Division, Title, Team__c FROM User WHERE Username = 'user@example.com'" --target-org gus +# Error: No such column 'Team__c' on entity 'User' +# Error: No such column 'Department' on entity 'User' (may not exist in all orgs) +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# ✅ STEP 1: Describe to see what fields actually exist +sf sobject describe --sobject User --target-org "$DEFAULT_ORG" | grep -i "team\|department\|division\|title" + +# ✅ STEP 2: If fields don't exist, find alternative approach (e.g., junction objects) +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.referenceTo[]? == "User")' + +# ✅ STEP 3: Query only with verified fields +sf data query \ + --query "SELECT Id, Name, Username, Email FROM User WHERE Username = 'user@example.com'" \ + --target-org "$DEFAULT_ORG" +``` + +**Prevention**: ALWAYS run `sf sobject describe` FIRST before ANY query. This is MANDATORY, not optional. + +--- + +### Critical Violation #2: Assuming Relationship Names Match Object Names (VERY COMMON) + +**Error Message**: `Didn't understand relationship 'ADM_Scrum_Team__r' in field path` + +**Why This Happens**: +- Relationship names are defined in the `relationshipName` field, NOT derived from object names +- Field `Scrum_Team__c` references object `ADM_Scrum_Team__c` but has relationship name `Scrum_Team__r` +- The `ADM_` prefix is NOT part of the relationship name +- ALWAYS use `sf sobject describe` to get the exact `relationshipName` value + +**Example of the Error**: +```bash +# ❌ NEVER: Assume relationship name matches object name +sf data query --query "SELECT Id, Name, ADM_Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" --target-org gus +# Error: Didn't understand relationship 'ADM_Scrum_Team__r' in field path +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# ✅ STEP 1: Get the actual relationship name from describe +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Scrum_Team__c") | {field: .name, relationshipName: .relationshipName}' +# Output: {"field":"Scrum_Team__c","relationshipName":"Scrum_Team__r"} + +# ✅ STEP 2: Use the verified relationship name (Scrum_Team__r, NOT ADM_Scrum_Team__r) +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c = '005xx'" \ + --target-org "$DEFAULT_ORG" +``` + +**Key Rule**: The relationship name is in the `relationshipName` field from `sf sobject describe`, NOT derived from the object name. + +--- + +### Critical Violation #3: Using Shell Special Characters + +**Error Message**: `unexpected token: '\'` or `MALFORMED_QUERY` + +**Why This Happens**: +In bash/zsh, `!` triggers history expansion even in double quotes, causing the shell to escape it as `\!`. SOQL doesn't recognize this escaped form. + +**Example of the Error**: +```bash +# ❌ NEVER: Use != in double-quoted strings (shell escapes the !) +sf data query --query "SELECT Id FROM ADM_Work__c WHERE Epic__c != null" --target-org gus +# Error: unexpected token: '\' +# Error: MALFORMED_QUERY ... unexpected token: '\\' +``` + +**Correct Solutions**: + +**Solution 1: Use `<>` instead of `!=` (RECOMMENDED - Most Reliable)** +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# ✅ BEST: Use <> operator instead of != to avoid shell escaping +sf data query --query "SELECT Id, Name, Epic__r.Name FROM ADM_Work__c WHERE Epic__c <> null AND Status__c NOT IN ('Closed', 'Duplicate') LIMIT 50" \ + --result-format json --target-org "$DEFAULT_ORG" + +# ✅ BEST: Use <> for checking if a reference field has a value +sf data query --query "SELECT Id, Name FROM ADM_Work__c WHERE Scrum_Team__c <> null" \ + --target-org "$DEFAULT_ORG" +``` + +**Solution 2: Check ID fields instead of relationship name fields (More Efficient)** +```bash +# ✅ GOOD: Check the ID field (Scrum_Team__c) instead of relationship name field (Scrum_Team__r.Name) +# This is more efficient and avoids querying the related object +sf data query \ + --query "SELECT Id, Name, Scrum_Team__r.Name FROM ADM_Work__c WHERE Scrum_Team__c <> null" \ + --target-org "$DEFAULT_ORG" + +# ❌ LESS EFFICIENT: Checking relationship name field +# Don't check Scrum_Team__r.Name <> null when you can check Scrum_Team__c <> null +``` + +**Solution 3: Query all records and filter with jq (Alternative)** +```bash +# ✅ ALTERNATIVE: Filter null values using jq instead +sf data query --query "SELECT Id, Epic__c, Epic__r.Name FROM ADM_Work__c WHERE Assignee__c = '005xx000001X8Uz' LIMIT 50" \ + --result-format json --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | "\(.Epic__r.Name)"' +``` + +**Solution 4: Use relationship fields and check with jq (Alternative)** +```bash +# Check for non-null relationships using jq select() +sf data query \ + --query "SELECT Id, Name, Epic__r.Id FROM ADM_Work__c LIMIT 50" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[] | select(.Epic__r) | .Name' +``` + +**Solution 5: Use a query file (For Complex Queries)** +```bash +# ✅ Create a file with your query to avoid shell escaping entirely +cat > query.soql <<'EOF' +SELECT Id, Name, Subject__c +FROM ADM_Work__c +WHERE Scrum_Team__c != null +AND Status__c NOT IN ('Closed', 'Duplicate') +LIMIT 50 +EOF + +sf data query --file query.soql --target-org "$DEFAULT_ORG" --json +``` + +**Key Takeaways**: +- **Always use `<>` instead of `!=`** in CLI queries to avoid shell escaping +- **Check ID fields (`Field__c`)** not relationship name fields (`Field__r.Name`) for null checks - it's more efficient +- **Use query files** for complex queries to avoid all shell escaping issues + +--- + +### Critical Violation #4: Using LIKE on ID/Reference Fields + +**Error Message**: `invalid operator on id field` + +**Why This Happens**: +- Reference fields (fields ending in __c that link to other objects) store IDs, not text +- LIKE is only valid for text fields (string, textarea) +- You must query the referenced object first to get the ID, then use = for exact match +- Check field type with `sf sobject describe` - if `type: "reference"`, use = not LIKE + +**Example of the Error**: +```bash +# ❌ NEVER: Use LIKE operator on reference/ID fields +sf data query --query "SELECT Id, Name FROM ADM_Scrum_Team_Member__c WHERE Member_Name__c LIKE '%Polillo%'" --target-org gus +# Error: invalid operator on id field +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# ✅ STEP 1: Find the User ID first +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Name LIKE '%Polillo%' LIMIT 1" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# ✅ STEP 2: Use the ID with = operator (NOT LIKE) +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Role__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" +``` + +**How to Check Field Type**: +```bash +# Verify if field is a reference type (requires = not LIKE) +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "Member_Name__c") | {name: .name, type: .type, referenceTo: .referenceTo}' +# Output: {"name":"Member_Name__c","type":"reference","referenceTo":["User"]} +``` + +--- + +### Critical Violation #5: Not Validating Query Results + +**Error Message**: Downstream errors with `null` values or empty results + +**Why This Happens**: +- Queries may return zero results +- Extracting values from empty results returns "null" as a string +- Using "null" in subsequent operations causes failures +- Always validate before using extracted values + +**Example of the Error**: +```bash +# ❌ NEVER: Query without checking results +WORK_ITEM_ID=$(sf data query --query "..." --json | jq -r '.result.records[0].Id') +# If no results, this returns "null" and breaks downstream operations +``` + +**Correct Solution**: +```bash +# Get default org +DEFAULT_ORG=$(sf config get target-org --json 2>/dev/null | jq -r '.result[0].value // empty' || sf org list --json 2>/dev/null | jq -r '.result.nonScratchOrgs[0].alias // empty') + +# ✅ CORRECT: Validate query results +QUERY_RESULT=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG") + +RECORD_COUNT=$(echo "$QUERY_RESULT" | jq -r '.result.totalSize') + +if [ "$RECORD_COUNT" -eq 0 ]; then + echo "Error: Work item not found" + exit 1 +fi + +WORK_ITEM_ID=$(echo "$QUERY_RESULT" | jq -r '.result.records[0].Id') + +if [ -z "$WORK_ITEM_ID" ] || [ "$WORK_ITEM_ID" = "null" ]; then + echo "Error: Failed to extract ID" + exit 1 +fi + +echo "Work Item ID: $WORK_ITEM_ID" +``` + +--- + +## Common Field Errors + +### Error: Field Doesn't Exist on Junction Object + +**Scenario**: Querying ADM_Scrum_Team_Member__c with wrong field names + +**Common Mistakes**: +```bash +# ❌ Field User__c doesn't exist (correct: Member_Name__c) +SELECT Id, User__c FROM ADM_Scrum_Team_Member__c + +# ❌ Field Email__c doesn't exist (correct: Internal_Email__c) +SELECT Id, Email__c FROM ADM_Scrum_Team_Member__c + +# ❌ Name field is auto-numbered STM-######, not searchable by member name +SELECT Id FROM ADM_Scrum_Team_Member__c WHERE Name LIKE '%Polillo%' +``` + +**Verified Fields** (from sf sobject describe): +``` +Member_Name__c (→User - use = not LIKE) +Scrum_Team__c (→ADM_Scrum_Team__c) +Scrum_Team_Name__c (formula field) +Internal_Email__c (NOT Email__c) +Role__c, Allocation__c, Department__c, Active__c +``` + +**Correct Approach**: +```bash +# Always describe first +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +sf sobject describe --sobject ADM_Scrum_Team_Member__c --target-org "$DEFAULT_ORG" | \ + jq '.fields[] | select(.custom == true) | {name: .name, type: .type}' + +# Then query with verified fields +USER_ID="005EE000001JW5FYAW" +sf data query \ + --query "SELECT Id, Name, Scrum_Team_Name__c, Member_Name__c, Role__c, Internal_Email__c + FROM ADM_Scrum_Team_Member__c + WHERE Member_Name__c = '${USER_ID}'" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +### Error: Epic Field Name Wrong + +**Scenario**: ADM_Epic__c uses `Health__c` not `Status__c` + +**Common Mistake**: +```bash +# ❌ WRONG: Epic doesn't have Status__c field +SELECT Id, Name, Status__c FROM ADM_Epic__c +# Error: No such column 'Status__c' on entity 'ADM_Epic__c' +``` + +**Correct Solution**: +```bash +# ✅ CORRECT: Epic uses Health__c field +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +# Verify first +sf sobject describe --sobject ADM_Epic__c --target-org "$DEFAULT_ORG" | grep -i "status\|health" + +# Query with correct field +sf data query \ + --query "SELECT Id, Name, Health__c, Priority__c FROM ADM_Epic__c" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Query Performance Issues + +### Issue: Query Timeout + +**Symptom**: Query takes too long or times out + +**Causes**: +- No LIMIT clause on large objects +- Complex WHERE clauses without indexes +- Querying too many fields +- Using LIKE with % on both sides + +**Solutions**: +```bash +# ❌ BAD: No LIMIT on large object +SELECT Id, Name, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' + +# ✅ GOOD: Add LIMIT +SELECT Id, Name, Subject__c FROM ADM_Work__c WHERE Status__c = 'New' LIMIT 200 + +# ❌ BAD: LIKE with % on both sides (slow) +SELECT Id FROM ADM_Work__c WHERE Subject__c LIKE '%feature%' + +# ✅ BETTER: LIKE with % on right only (uses index) +SELECT Id FROM ADM_Work__c WHERE Subject__c LIKE 'feature%' + +# ✅ BEST: Use indexed fields in WHERE clause +SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678' +``` + +--- + +### Issue: Too Many SOQL Queries + +**Symptom**: Hitting SOQL query limits + +**Causes**: +- Querying in loops +- Not using relationship queries +- Multiple queries when one would suffice + +**Solutions**: +```bash +# ❌ BAD: Two separate queries +sf data query --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-123'" --target-org "$DEFAULT_ORG" +sf data query --query "SELECT Name, Email FROM User WHERE Id = '005xx'" --target-org "$DEFAULT_ORG" + +# ✅ GOOD: Single query with relationship +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +sf data query \ + --query "SELECT Id, Name, Assignee__r.Name, Assignee__r.Email FROM ADM_Work__c WHERE Name = 'W-123'" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Best Practices Summary + +**Essential Practices (IN ORDER OF IMPORTANCE)**: +``` +✅ DO: ALWAYS run `sf sobject describe` FIRST before ANY query - THIS IS MANDATORY +✅ DO: Verify field names exist in describe output before writing SELECT statements +✅ DO: Get relationship names from relationshipName field (NOT from object names) +✅ DO: Use = operator for ID/reference fields (NOT LIKE) +✅ DO: Filter null values with jq instead of != in queries +✅ DO: Validate query results before using extracted values +✅ DO: Use LIMIT to avoid timeouts on large datasets +✅ DO: Use --result-format json for scripting +``` + +**Common Mistakes to Avoid (CRITICAL)**: +``` +❌ DON'T: EVER query fields without running sf sobject describe first +❌ DON'T: Assume relationship names match object names +❌ DON'T: Use ADM_Scrum_Team__r when actual relationship is Scrum_Team__r +❌ DON'T: Assume ANY field exists without verification +❌ DON'T: Use != in CLI queries (use <> instead - shell escapes !) +❌ DON'T: Check Scrum_Team__r.Name <> null when you can check Scrum_Team__c <> null +❌ DON'T: Use LIKE on ID/reference fields (use = instead) +❌ DON'T: Query without LIMIT (can timeout) +❌ DON'T: Use SELECT * (not supported in SOQL) +❌ DON'T: Skip validation of jq output (check for null) +``` + +--- + +## Troubleshooting Workflow + +**When a query fails, follow these steps**: + +1. **Check the error message** - What field or relationship failed? + +2. **Run sf sobject describe** - Verify field exists and get exact name: + ```bash + sf sobject describe --sobject --target-org "$DEFAULT_ORG" + ``` + +3. **Check field type** - Is it reference, string, or formula? + ```bash + sf sobject describe --sobject --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "FieldName__c") | {name: .name, type: .type}' + ``` + +4. **Get relationship name** - If querying related fields: + ```bash + sf sobject describe --sobject --target-org "$DEFAULT_ORG" --json | \ + jq -r '.result.fields[] | select(.name == "FieldName__c") | {field: .name, relationshipName: .relationshipName}' + ``` + +5. **Test with minimal query** - Remove complexity and add back: + ```bash + sf data query --query "SELECT Id, Name FROM Object__c LIMIT 5" --target-org "$DEFAULT_ORG" + ``` + +6. **Add fields one by one** - Identify which field causes the error + +7. **Check for shell escaping** - Use single quotes or escape special characters + +8. **Validate results** - Check totalSize before extracting values + +--- + +## Related Skills + +- `sf-soql-basics.md` - Start here for field discovery and basic queries +- `sf-soql-advanced.md` - Complex queries, aggregation, date filters, tooling API +- `sf-work-items.md` - Creating and managing work items +- `sf-org-auth.md` - Authentication and user info +- `sf-record-operations.md` - Create and update records + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Split from sf-soql-queries v1.2) diff --git a/skills/salesforce/sf-work-items.md b/skills/salesforce/sf-work-items.md new file mode 100644 index 0000000..a38a676 --- /dev/null +++ b/skills/salesforce/sf-work-items.md @@ -0,0 +1,402 @@ +--- +name: salesforce-work-items +description: Manage Agile Accelerator (GUS) work items, sprints, and epics. Use for creating stories, bugs, tasks, managing sprints, epics, teams, and builds. +keywords: gus, agile accelerator, work items, story, bug, task, sprint, epic, team, scrum, build, ADM_Work__c, ADM_Epic__c, ADM_Sprint__c, ADM_Scrum_Team__c, salesforce +--- + +# Salesforce Agile Accelerator Work Items + +**Scope**: Creating and managing work items, sprints, epics, and builds in GUS +**Lines**: ~402 +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic) + +--- + +## When to Use This Skill + +Activate this skill when: +- Creating user stories, bugs, or tasks in GUS +- Managing sprints and sprint planning +- Working with epics and product tags +- Managing scheduled builds +- Organizing work item hierarchies +- Sprint planning and backlog management + +--- + +## Core Concepts + +### Concept 1: Work Item Types + +**ADM_Work__c Object** - Main work tracking object + +**Common Types**: +- `User Story` - Feature development +- `Bug` - Defects and issues +- `Task` - General work items +- `Investigation` - Research tasks + +**Key Fields**: +``` +Name - WI number (e.g., W-12345678) +Subject__c - Title/summary (required) +Status__c - Workflow state (required) +Type__c - Work item type (required) +Priority__c - P0, P1, P2, P3, P4 +Story_Points__c - Estimation +Assignee__c - User ID +Sprint__c - Sprint ID +Epic__c - Epic ID +Description__c - HTML rich text +``` + +### Concept 2: Sprints and Epics + +**ADM_Sprint__c** - Sprint/iteration tracking +**ADM_Epic__c** - Epic grouping +**ADM_Build__c** - Scheduled builds/releases +**ADM_Product_Tag__c** - Product categorization + +--- + +## Patterns + +### Pattern 1: Creating User Stories with Dependencies + +**Use case**: Create complete user story with proper relationships + +```bash +# Step 1: Get default org and query for required IDs +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') +USER_ID=$(sf data query \ + --query "SELECT Id FROM User WHERE Email = '${USER_EMAIL}'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +SPRINT_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Sprint__c WHERE Name = 'Sprint 42'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +EPIC_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +# Step 2: Create user story with all required fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement user authentication' \ + Status__c='New' \ + Priority__c='P1' \ + Story_Points__c=5 \ + Epic__c='${EPIC_ID}' \ + Sprint__c='${SPRINT_ID}' \ + Assignee__c='${USER_ID}' \ + Type__c='User Story' \ + Description__c='

As a user, I want to log in securely

'" \ + --target-org "$DEFAULT_ORG" +``` + +**Benefits**: +- Complete work item with proper tracking +- Links to Epic and Sprint for planning +- Includes story points for velocity tracking + +### Pattern 2: Sprint Planning + +**Use case**: Create sprint and assign work items + +```bash +# Create a new Sprint +SPRINT_RESULT=$(sf data create record \ + --sobject ADM_Sprint__c \ + --values "Name='Sprint 42' Start_Date__c=2024-01-15 End_Date__c=2024-01-29" \ + --target-org "$DEFAULT_ORG" \ + --json) + +SPRINT_ID=$(echo "$SPRINT_RESULT" | jq -r '.result.id') + +echo "Created Sprint 42 with ID: $SPRINT_ID" + +# Query backlog items +sf data query \ + --query "SELECT Id, Name, Subject__c, Story_Points__c + FROM ADM_Work__c + WHERE Status__c = 'Ready for Development' + AND Sprint__c = null + ORDER BY Priority__c, Story_Points__c" \ + --target-org "$DEFAULT_ORG" \ + --result-format json > backlog.json + +# Review items and manually create CSV for assignment +# backlog_assignment.csv: +# Id,Sprint__c +# a07xx00000ABCD1, +# a07xx00000ABCD2, + +# Bulk assign items to sprint (see sf-bulk-operations.md) +``` + +### Pattern 3: Managing Epics + +**Use case**: Create and organize epics + +```bash +# Create an Epic +sf data create record \ + --sobject ADM_Epic__c \ + --values "Name='Q1 2024 Authentication Features' \ + Priority__c='P1' \ + Health__c='On Track' \ + Description__c='

Complete authentication overhaul

'" \ + --target-org "$DEFAULT_ORG" + +# Query work items in epic +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Story_Points__c, Assignee__r.Name + FROM ADM_Work__c + WHERE Epic__r.Name LIKE '%Authentication%' + ORDER BY Status__c, Priority__c" \ + --target-org "$DEFAULT_ORG" + +# Update epic health +EPIC_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Epic__c WHERE Name LIKE '%Authentication%'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Epic__c \ + --record-id "$EPIC_ID" \ + --values "Health__c='At Risk' Description__c='

Blocked on security review

'" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 4: Managing Builds + +**Use case**: Create and track scheduled builds + +```bash +# Create a Build (Scheduled Build) +sf data create record \ + --sobject ADM_Build__c \ + --values "Name='Release 1.0' External_ID__c='R010' Target_Date__c=2024-02-01" \ + --target-org "$DEFAULT_ORG" + +# Link work items to build +BUILD_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Build__c WHERE Name = 'Release 1.0'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Work__c \ + --where "Status__c='Fixed' AND Scheduled_Build__c = null" \ + --values "Scheduled_Build__c='${BUILD_ID}'" \ + --target-org "$DEFAULT_ORG" + +# Query work items in build +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Priority__c + FROM ADM_Work__c + WHERE Scheduled_Build__r.Name = 'Release 1.0' + ORDER BY Priority__c" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 5: Work Item Status Workflows + +**Use case**: Move work items through standard workflow states + +```bash +# Common status transitions for User Stories +# New → In Progress → Code Review → QA Review → Fixed → Closed + +# Start work +WORK_ITEM_ID=$(sf data query \ + --query "SELECT Id FROM ADM_Work__c WHERE Name = 'W-12345678'" \ + --result-format json \ + --target-org "$DEFAULT_ORG" | jq -r '.result.records[0].Id') + +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='In Progress'" \ + --target-org "$DEFAULT_ORG" + +# Move to code review +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Code Review'" \ + --target-org "$DEFAULT_ORG" + +# Mark as fixed +sf data update record \ + --sobject ADM_Work__c \ + --record-id "$WORK_ITEM_ID" \ + --values "Status__c='Fixed' Resolved_On__c=$(date +%Y-%m-%d)" \ + --target-org "$DEFAULT_ORG" +``` + +### Pattern 6: Sprint Reporting + +**Use case**: Get sprint metrics and burndown data + +```bash +# Get sprint summary +sf data query \ + --query "SELECT + Status__c, + COUNT(Id) total_items, + SUM(Story_Points__c) total_points + FROM ADM_Work__c + WHERE Sprint__r.Name = 'Sprint 42' + GROUP BY Status__c" \ + --target-org "$DEFAULT_ORG" + +# Get team velocity +sf data query \ + --query "SELECT + Sprint__r.Name, + SUM(Story_Points__c) completed_points + FROM ADM_Work__c + WHERE Sprint__r.Start_Date__c >= 2024-01-01 + AND Status__c IN ('Fixed', 'Closed') + GROUP BY Sprint__r.Name + ORDER BY Sprint__r.Name" \ + --target-org "$DEFAULT_ORG" + +# Get individual workload +DEFAULT_ORG=$(sf config get target-org --json | jq -r '.result[0].value // empty') +if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[] | select(.isDefaultUsername == true) | .alias' | head -1) + if [ -z "$DEFAULT_ORG" ]; then + DEFAULT_ORG=$(sf org list --json | jq -r '.result.nonScratchOrgs[0].alias // empty') + fi +fi + +USER_EMAIL=$(sf org list --json | jq -r --arg org "$DEFAULT_ORG" '.result.nonScratchOrgs[] | select(.alias == $org or .username == $org) | .username') + +sf data query \ + --query "SELECT Name, Subject__c, Status__c, Story_Points__c, Sprint__r.Name + FROM ADM_Work__c + WHERE Assignee__r.Email = '${USER_EMAIL}' + AND Status__c NOT IN ('Fixed', 'Closed') + ORDER BY Priority__c, Sprint__r.Name" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Quick Reference + +### Common Objects and Fields + +**Work Items (ADM_Work__c)**: +``` +Status Values: New, In Progress, Code Review, QA Review, Fixed, Closed +Priority: P0, P1, P2, P3, P4 +Type: User Story, Bug, Task, Investigation +``` + +**Epics (ADM_Epic__c)**: +``` +Health: On Track, At Risk, Off Track, Completed, Canceled +Priority: P0, P1, P2, P3 (optional) +Note: Epics do NOT have a Status__c field - use Health__c instead +``` + +**Sprints (ADM_Sprint__c)**: +``` +Key Fields: Name, Start_Date__c, End_Date__c +No status field - use dates +``` + +**Builds (ADM_Build__c)**: +``` +Key Fields: Name, External_ID__c, Target_Date__c +``` + +--- + +## Best Practices + +**Essential Practices:** +``` +✅ DO: Include all required fields when creating work items +✅ DO: Link work items to sprints and epics +✅ DO: Use story points for estimation +✅ DO: Query for IDs before creating relationships +✅ DO: Use descriptive subjects for work items +✅ DO: Update status as work progresses +``` + +**Common Mistakes to Avoid:** +``` +❌ DON'T: Create work items without type or status +❌ DON'T: Hardcode sprint or epic IDs +❌ DON'T: Forget to assign work items +❌ DON'T: Skip story points on user stories +❌ DON'T: Leave work items in stale states +``` + +--- + +## Anti-Patterns + +### Critical Violations + +```bash +# ❌ NEVER: Create incomplete work items +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Feature'" \ + --target-org "$DEFAULT_ORG" + +# ✅ CORRECT: Include all required and recommended fields +sf data create record \ + --sobject ADM_Work__c \ + --values "Subject__c='Implement feature X' \ + Status__c='New' \ + Type__c='User Story' \ + Priority__c='P2' \ + Story_Points__c=3" \ + --target-org "$DEFAULT_ORG" +``` + +--- + +## Security Considerations + +**Security Notes**: +- ⚠️ Verify user has permission to create/update work items +- ⚠️ Don't expose sensitive information in subject or description +- ⚠️ Validate sprint and epic IDs before assignment +- ⚠️ Be careful when bulk updating work item statuses + +--- + +## Related Skills + +- `sf-org-auth.md` - Get current user for assignments +- `sf-soql-queries.md` - Query work items and related objects +- `sf-record-operations.md` - Create and update individual records +- `sf-bulk-operations.md` - Bulk status updates +- `sf-chatter.md` - Add updates to work items +- `sf-automation.md` - Automate with git integration + +--- + +**Last Updated**: 2025-12-03 +**Format Version**: 1.0 (Atomic)