mirror of
https://github.com/blackboxprogramming/BlackRoad-Operating-System.git
synced 2026-03-17 01:57:11 -05:00
This implements the "Automate The Company" initiative with comprehensive
Standard Operating Procedures for GitHub + Salesforce + Asana integration.
New directory: sop/
├── workflows/ - End-to-end process documentation
│ ├── new-client-kickoff.md - Flagship workflow from deal → repos → Asana
│ └── release-pipeline.md - Deploy → update Salesforce + Asana
├── playbooks/ - Human-friendly checklists
│ └── brenda-new-client-checklist.md - Non-technical operator guide
├── salesforce/ - Salesforce automation specifications
│ ├── flows/opp-automation-onstagechange.md - Trigger on Closed Won
│ └── orchestrations/new-client-kickoff-orchestration.md - Multi-stage process
├── integrations/ - API integration specifications
│ ├── salesforce-to-github.md - Create repos from Salesforce
│ ├── github-to-salesforce.md - Update Salesforce after deploy
│ └── salesforce-to-asana.md - Create Asana projects from Salesforce
└── templates/ - Reusable templates
├── github-actions/ - CI/CD workflows (ci.yml, deploy.yml, safety.yml)
└── repo-template/ - Standard repo config (PR template, labels, branch protection)
Key Features:
- Event-driven automation (Closed Won → repos + Asana creation)
- GitHub Actions templates for CI/CD baseline
- Salesforce Flow & Orchestration specs
- Complete API integration documentation
- Operator-friendly playbooks
- Two-view approach (operator + engineer)
- No manual status syncing across systems
This provides the complete backbone for next-gen ERP automation.
645 lines
14 KiB
Markdown
645 lines
14 KiB
Markdown
# Integration: Salesforce → Asana
|
|
|
|
**Purpose:** Enable Salesforce to create and manage Asana projects and tasks automatically
|
|
**Direction:** Salesforce calls Asana REST API
|
|
**Authentication:** Personal Access Token (PAT)
|
|
**Status:** Active
|
|
**Last Updated:** 2025-11-17
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
This integration allows Salesforce Flows and Orchestrations to:
|
|
- Create Asana projects
|
|
- Add sections to projects
|
|
- Create tasks with assignments and due dates
|
|
- Update task status
|
|
- Add comments to tasks
|
|
|
|
**Key Use Case:** Auto-create Asana project board when Opportunity closes
|
|
|
|
---
|
|
|
|
## Authentication Setup
|
|
|
|
### Asana Personal Access Token (PAT)
|
|
|
|
**Setup Steps:**
|
|
|
|
1. **Generate PAT:**
|
|
- Go to: https://app.asana.com/0/my-apps
|
|
- Click "Personal access tokens"
|
|
- Click "+ New access token"
|
|
- Name: `Salesforce Integration`
|
|
- Copy token (starts with `1/...`)
|
|
|
|
2. **Store in Salesforce:**
|
|
- Setup → Named Credentials → New
|
|
- Name: `Asana_API`
|
|
- URL: `https://app.asana.com/api/1.0`
|
|
- Identity Type: Named Principal
|
|
- Authentication Protocol: Custom
|
|
- Header: `Authorization: Bearer {YOUR_PAT}`
|
|
|
|
**Alternative:** Use Custom Settings
|
|
|
|
```
|
|
Setup → Custom Settings → New (Protected)
|
|
Name: Asana_Settings__c
|
|
Fields:
|
|
- API_Token__c (Text, Encrypted)
|
|
- Workspace_GID__c (Text)
|
|
- Team_GID__c (Text)
|
|
```
|
|
|
|
---
|
|
|
|
## Required Asana Setup
|
|
|
|
### 1. Get Workspace GID
|
|
|
|
```bash
|
|
curl "https://app.asana.com/api/1.0/workspaces" \
|
|
-H "Authorization: Bearer YOUR_PAT"
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"gid": "1234567890123456",
|
|
"name": "BlackRoad Workspace",
|
|
"resource_type": "workspace"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Store:** `WORKSPACE_GID = "1234567890123456"`
|
|
|
|
---
|
|
|
|
### 2. Get Team GID
|
|
|
|
```bash
|
|
curl "https://app.asana.com/api/1.0/organizations/1234567890123456/teams" \
|
|
-H "Authorization: Bearer YOUR_PAT"
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"gid": "9876543210987654",
|
|
"name": "Engineering",
|
|
"resource_type": "team"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Store:** `TEAM_GID = "9876543210987654"`
|
|
|
|
---
|
|
|
|
### 3. Get User GIDs for Assignments
|
|
|
|
```bash
|
|
curl "https://app.asana.com/api/1.0/users?workspace=1234567890123456" \
|
|
-H "Authorization: Bearer YOUR_PAT"
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"data": [
|
|
{
|
|
"gid": "1111111111111111",
|
|
"name": "Alice Developer",
|
|
"email": "alice@blackroad.com"
|
|
},
|
|
{
|
|
"gid": "2222222222222222",
|
|
"name": "Bob DevOps",
|
|
"email": "bob@blackroad.com"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
**Create Salesforce Mapping:**
|
|
|
|
| Role | Email | Asana GID |
|
|
|------|-------|-----------|
|
|
| DevOps Lead | bob@blackroad.com | 2222222222222222 |
|
|
| Backend Lead | alice@blackroad.com | 1111111111111111 |
|
|
| Customer Success | brenda@blackroad.com | 3333333333333333 |
|
|
|
|
Store in Custom Metadata Type: `Asana_User_Mapping__mdt`
|
|
|
|
---
|
|
|
|
## API Operations
|
|
|
|
### 1. Create Project
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST https://app.asana.com/api/1.0/projects
|
|
```
|
|
|
|
**Headers:**
|
|
```
|
|
Authorization: Bearer {ASANA_PAT}
|
|
Content-Type: application/json
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"workspace": "1234567890123456",
|
|
"team": "9876543210987654",
|
|
"name": "Acme Corp - ACME-1042",
|
|
"notes": "Salesforce Project: https://your-domain.my.salesforce.com/a0X5e000000XYZ1EAO\n\nRepos:\n- Backend: https://github.com/blackboxprogramming/blackroad-ACME-1042-backend\n- Frontend: https://github.com/blackboxprogramming/blackroad-ACME-1042-frontend\n- Ops: https://github.com/blackboxprogramming/blackroad-ACME-1042-ops",
|
|
"color": "light-green",
|
|
"default_view": "board",
|
|
"public": false
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"data": {
|
|
"gid": "5555555555555555",
|
|
"name": "Acme Corp - ACME-1042",
|
|
"permalink_url": "https://app.asana.com/0/5555555555555555/list"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Salesforce Flow Implementation:**
|
|
|
|
```yaml
|
|
Element: HTTP Callout
|
|
Method: POST
|
|
Endpoint: {!$Credential.Asana_API}/projects
|
|
Headers:
|
|
- Authorization: Bearer {!$Credential.Asana_API.Token}
|
|
- Content-Type: application/json
|
|
Body:
|
|
{
|
|
"data": {
|
|
"workspace": "{!$CustomMetadata.Asana_Settings__mdt.Workspace_GID__c}",
|
|
"team": "{!$CustomMetadata.Asana_Settings__mdt.Team_GID__c}",
|
|
"name": "{!varProject.Name}",
|
|
"notes": "Salesforce: {!varProject.Id}\nRepos:\n- {!varProject.Backend_Repo_URL__c}",
|
|
"color": "light-green",
|
|
"default_view": "board"
|
|
}
|
|
}
|
|
|
|
Store Response: varAsanaResponse
|
|
Parse:
|
|
- varAsanaProjectGID = {!varAsanaResponse.data.gid}
|
|
- varAsanaProjectURL = {!varAsanaResponse.data.permalink_url}
|
|
|
|
Update Project Record:
|
|
- Asana_Project_URL__c = {!varAsanaProjectURL}
|
|
- Asana_Project_GID__c = {!varAsanaProjectGID}
|
|
```
|
|
|
|
---
|
|
|
|
### 2. Create Section
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST https://app.asana.com/api/1.0/projects/{PROJECT_GID}/sections
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "Discovery"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"data": {
|
|
"gid": "6666666666666666",
|
|
"name": "Discovery"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Salesforce Implementation:**
|
|
|
|
```yaml
|
|
Element: Loop
|
|
Collection: ["Discovery", "Architecture", "Build", "Testing", "Go-Live"]
|
|
Current Item: varSectionName
|
|
|
|
Inside Loop:
|
|
- HTTP Callout
|
|
- Endpoint: {!$Credential.Asana_API}/projects/{!varAsanaProjectGID}/sections
|
|
- Body: {"data": {"name": "{!varSectionName}"}}
|
|
- Store Response: varSectionResponse
|
|
- Add to Collection: varSectionGIDs[{!varSectionName}] = {!varSectionResponse.data.gid}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Create Task
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST https://app.asana.com/api/1.0/tasks
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"projects": ["5555555555555555"],
|
|
"name": "Confirm domain + DNS with client",
|
|
"notes": "Get final domain, subdomain, and DNS setup requirements from client.\n\nSalesforce Project: https://your-domain.my.salesforce.com/a0X5e000000XYZ1EAO",
|
|
"assignee": "3333333333333333",
|
|
"due_on": "2025-11-20",
|
|
"memberships": [
|
|
{
|
|
"project": "5555555555555555",
|
|
"section": "6666666666666666"
|
|
}
|
|
]
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"data": {
|
|
"gid": "7777777777777777",
|
|
"name": "Confirm domain + DNS with client",
|
|
"permalink_url": "https://app.asana.com/0/5555555555555555/7777777777777777"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Salesforce Implementation:**
|
|
|
|
```yaml
|
|
Element: Loop
|
|
Collection: varTaskDefinitions (custom metadata or JSON)
|
|
Current Item: varTask
|
|
|
|
Inside Loop:
|
|
- Calculate Due Date
|
|
Formula: TODAY() + {!varTask.DaysOffset}
|
|
|
|
- Lookup Assignee GID
|
|
From: Asana_User_Mapping__mdt
|
|
Match: Role = {!varTask.AssigneeRole}
|
|
|
|
- HTTP Callout
|
|
- Endpoint: {!$Credential.Asana_API}/tasks
|
|
- Body:
|
|
{
|
|
"data": {
|
|
"projects": ["{!varAsanaProjectGID}"],
|
|
"name": "{!varTask.Name}",
|
|
"notes": "{!varTask.Description}\n\nSalesforce: {!varProject.Id}",
|
|
"assignee": "{!varAssigneeGID}",
|
|
"due_on": "{!varDueDate}",
|
|
"memberships": [{
|
|
"project": "{!varAsanaProjectGID}",
|
|
"section": "{!varSectionGIDs[varTask.Section]}"
|
|
}]
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. Update Task (Mark Complete)
|
|
|
|
**Endpoint:**
|
|
```
|
|
PUT https://app.asana.com/api/1.0/tasks/{TASK_GID}
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"completed": true
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (200 OK):**
|
|
```json
|
|
{
|
|
"data": {
|
|
"gid": "7777777777777777",
|
|
"completed": true
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 5. Add Comment to Task
|
|
|
|
**Endpoint:**
|
|
```
|
|
POST https://app.asana.com/api/1.0/tasks/{TASK_GID}/stories
|
|
```
|
|
|
|
**Payload:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"text": "✅ Deployed v0.1.3 to production\n\n**Commit:** a1b2c3d4\n**By:** github-user\n**Time:** 2025-11-17 14:30 UTC\n**Link:** https://github.com/org/repo/commit/a1b2c3d4"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (201 Created):**
|
|
```json
|
|
{
|
|
"data": {
|
|
"gid": "8888888888888888",
|
|
"text": "✅ Deployed v0.1.3..."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Task Template Definition
|
|
|
|
**Store in Salesforce Custom Metadata:** `Asana_Task_Template__mdt`
|
|
|
|
| Label | API Name | Section__c | Days_Offset__c | Assignee_Role__c |
|
|
|-------|----------|------------|----------------|------------------|
|
|
| Confirm domain + DNS | Confirm_Domain_DNS | Discovery | 1 | Sales Ops |
|
|
| Gather branding assets | Gather_Branding | Discovery | 1 | Design |
|
|
| Wire up Railway/Cloudflare | Wire_Up_Envs | Architecture | 3 | DevOps |
|
|
| Enable CI/CD secrets | Enable_Secrets | Architecture | 3 | DevOps |
|
|
| Set up database | Setup_Database | Build | 5 | Backend |
|
|
| Implement authentication | Implement_Auth | Build | 7 | Backend |
|
|
| Run E2E tests | Run_E2E_Tests | Testing | 12 | QA |
|
|
| Final client walkthrough | Final_Walkthrough | Go-Live | 18 | Customer Success |
|
|
| Deploy to production | Deploy_Production | Go-Live | 19 | DevOps |
|
|
|
|
**Query in Flow:**
|
|
|
|
```apex
|
|
[SELECT Label, Section__c, Days_Offset__c, Assignee_Role__c, Description__c
|
|
FROM Asana_Task_Template__mdt
|
|
ORDER BY Days_Offset__c ASC]
|
|
```
|
|
|
|
---
|
|
|
|
## Complete Salesforce Flow Example
|
|
|
|
**Flow Name:** `Asana_Project_Setup`
|
|
|
|
**Input Variables:**
|
|
- `ProjectRecordId` (Text)
|
|
- `AsanaProjectGID` (Text) - from Create Project step
|
|
|
|
**Steps:**
|
|
|
|
```yaml
|
|
1. Get Project Record
|
|
- Object: Project__c
|
|
- Filter: Id = {!ProjectRecordId}
|
|
|
|
2. Create Asana Project
|
|
- HTTP Callout (as documented)
|
|
- Store: varAsanaProjectGID
|
|
|
|
3. Create Sections
|
|
- Loop: ["Discovery", "Architecture", "Build", "Testing", "Go-Live"]
|
|
- HTTP Callout per section
|
|
- Store GIDs in Map: varSectionGIDs
|
|
|
|
4. Get Task Templates
|
|
- Get Records: Asana_Task_Template__mdt
|
|
- Store: varTaskTemplates
|
|
|
|
5. Loop: Create Tasks
|
|
- For Each: varTaskTemplates
|
|
- Calculate due date: TODAY() + DaysOffset
|
|
- Lookup assignee GID from Asana_User_Mapping__mdt
|
|
- HTTP Callout to create task
|
|
- Link task to correct section
|
|
|
|
6. Update Project Record
|
|
- Asana_Project_URL__c = {!varAsanaResponse.data.permalink_url}
|
|
- Asana_Project_GID__c = {!varAsanaProjectGID}
|
|
```
|
|
|
|
---
|
|
|
|
## User Mapping Setup
|
|
|
|
**Custom Metadata Type:** `Asana_User_Mapping__mdt`
|
|
|
|
**Fields:**
|
|
- `Role__c` (Text) - "DevOps", "Backend", "Customer Success", etc.
|
|
- `Email__c` (Email)
|
|
- `Asana_GID__c` (Text)
|
|
|
|
**Records:**
|
|
|
|
| Label | Role__c | Email__c | Asana_GID__c |
|
|
|-------|---------|----------|--------------|
|
|
| DevOps Team | DevOps | devops@blackroad.com | 2222222222222222 |
|
|
| Backend Team | Backend | backend@blackroad.com | 1111111111111111 |
|
|
| Customer Success | Customer Success | brenda@blackroad.com | 3333333333333333 |
|
|
| QA Team | QA | qa@blackroad.com | 4444444444444444 |
|
|
|
|
**Query in Flow:**
|
|
|
|
```apex
|
|
[SELECT Asana_GID__c
|
|
FROM Asana_User_Mapping__mdt
|
|
WHERE Role__c = :assigneeRole
|
|
LIMIT 1]
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Common Errors
|
|
|
|
| Status Code | Error | Cause | Solution |
|
|
|-------------|-------|-------|----------|
|
|
| 400 | Bad Request | Invalid GID or payload | Verify workspace/team GIDs |
|
|
| 401 | Unauthorized | Invalid token | Regenerate PAT in Asana |
|
|
| 403 | Forbidden | No access to workspace | Check PAT has workspace access |
|
|
| 404 | Not Found | Project/task doesn't exist | Verify GID is correct |
|
|
| 429 | Too Many Requests | Rate limit exceeded | Implement exponential backoff |
|
|
|
|
### Salesforce Fault Path
|
|
|
|
```yaml
|
|
Fault Path:
|
|
- Element: Create Case
|
|
Subject: "Asana Integration Error: {!$Flow.FaultMessage}"
|
|
Description: "Failed to create Asana project for: {!varProject.Name}\n\nError: {!$Flow.FaultMessage}"
|
|
Priority: Medium
|
|
Type: "Automation Bug"
|
|
|
|
- Element: Send Email
|
|
To: ops@blackroad.com
|
|
Subject: "Asana Automation Failed - Manual Project Needed"
|
|
Body: "Project: {!varProject.Name}\nKey: {!varProject.Project_Key__c}\n\nPlease create Asana project manually."
|
|
```
|
|
|
|
---
|
|
|
|
## Rate Limits
|
|
|
|
**Asana API Rate Limits:**
|
|
- 1,500 requests per minute per user
|
|
- Burst: Up to 60 requests in the first second
|
|
|
|
**Per Asana Project Creation:**
|
|
- 1 request: Create project
|
|
- 5 requests: Create sections
|
|
- 8-10 requests: Create tasks
|
|
- **Total:** ~15 requests
|
|
|
|
**Best Practices:**
|
|
- Can create ~100 projects per minute
|
|
- Add delay between operations if hitting limits
|
|
- Use exponential backoff on 429 errors
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Manual Test (curl)
|
|
|
|
```bash
|
|
# 1. Create project
|
|
curl -X POST "https://app.asana.com/api/1.0/projects" \
|
|
-H "Authorization: Bearer YOUR_PAT" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{
|
|
"data": {
|
|
"workspace": "1234567890123456",
|
|
"name": "Test Project - DELETE ME"
|
|
}
|
|
}'
|
|
|
|
# Response: Get project GID (e.g., 5555555555555555)
|
|
|
|
# 2. Create section
|
|
curl -X POST "https://app.asana.com/api/1.0/projects/5555555555555555/sections" \
|
|
-H "Authorization: Bearer YOUR_PAT" \
|
|
-d '{"data": {"name": "To Do"}}'
|
|
|
|
# 3. Create task
|
|
curl -X POST "https://app.asana.com/api/1.0/tasks" \
|
|
-H "Authorization: Bearer YOUR_PAT" \
|
|
-d '{
|
|
"data": {
|
|
"projects": ["5555555555555555"],
|
|
"name": "Test task"
|
|
}
|
|
}'
|
|
|
|
# 4. Clean up - delete project
|
|
curl -X DELETE "https://app.asana.com/api/1.0/projects/5555555555555555" \
|
|
-H "Authorization: Bearer YOUR_PAT"
|
|
```
|
|
|
|
### Salesforce Sandbox Test
|
|
|
|
1. Create test Project record
|
|
2. Run flow: `Asana_Project_Setup`
|
|
3. Verify:
|
|
- Project created in Asana
|
|
- Sections present
|
|
- Tasks created with correct assignees
|
|
- Due dates calculated correctly
|
|
4. Clean up test project in Asana
|
|
|
|
---
|
|
|
|
## Monitoring
|
|
|
|
**Track These Metrics:**
|
|
|
|
| Metric | Target | Alert Threshold |
|
|
|--------|--------|-----------------|
|
|
| Asana API Success Rate | > 98% | < 95% |
|
|
| Avg Project Creation Time | < 30s | > 60s |
|
|
| Failed Project Creations | < 2% | > 5% |
|
|
| Tasks Created Per Project | ~8-10 | < 5 or > 15 |
|
|
|
|
**Salesforce Custom Object:** `Asana_API_Log__c`
|
|
|
|
**Fields:**
|
|
- Operation__c (Create Project, Create Task, etc.)
|
|
- Project__c (Lookup)
|
|
- Status__c (Success, Failed)
|
|
- Response_Time__c
|
|
- Error_Message__c
|
|
- Timestamp__c
|
|
|
|
---
|
|
|
|
## Security Best Practices
|
|
|
|
1. **PAT Security:**
|
|
- Never share PAT
|
|
- Use encrypted custom settings
|
|
- Rotate every 90 days
|
|
- Generate separate PAT per integration (if multiple)
|
|
|
|
2. **Project Visibility:**
|
|
- Set `public: false` for client projects
|
|
- Only share with relevant team members
|
|
- Review permissions quarterly
|
|
|
|
3. **Audit Logging:**
|
|
- Log all Asana API calls in Salesforce
|
|
- Review monthly
|
|
- Track who accessed projects
|
|
|
|
---
|
|
|
|
## Related Documentation
|
|
|
|
- [Asana API Docs: Projects](https://developers.asana.com/docs/projects)
|
|
- [Asana API Docs: Tasks](https://developers.asana.com/docs/tasks)
|
|
- [Salesforce Orchestration: New Client Kickoff](../salesforce/orchestrations/new-client-kickoff-orchestration.md)
|
|
- [Workflow: New Client Kickoff](../workflows/new-client-kickoff.md)
|
|
- [Integration: Salesforce → GitHub](./salesforce-to-github.md)
|
|
|
|
---
|
|
|
|
## Changelog
|
|
|
|
| Date | Version | Change | Author |
|
|
|------|---------|--------|--------|
|
|
| 2025-11-17 | 1.0 | Initial specification | Cece (Claude) |
|