Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 59 additions & 1 deletion actions/setup/js/update_project.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -955,8 +955,25 @@ async function main() {
if (!result.success) return;

const updateProjectItems = result.items.filter(item => item.type === "update_project");
if (updateProjectItems.length === 0) return;

// Check if views are configured in frontmatter
const configuredViews = process.env.GH_AW_PROJECT_VIEWS;
let viewsToCreate = [];
if (configuredViews) {
try {
viewsToCreate = JSON.parse(configuredViews);
if (Array.isArray(viewsToCreate) && viewsToCreate.length > 0) {
core.info(`Found ${viewsToCreate.length} configured view(s) in frontmatter`);
}
} catch (parseError) {
core.warning(`Failed to parse GH_AW_PROJECT_VIEWS: ${getErrorMessage(parseError)}`);
}
}

// If no update_project items and no configured views, nothing to do
if (updateProjectItems.length === 0 && viewsToCreate.length === 0) return;

// Process update_project items from agent output
for (let i = 0; i < updateProjectItems.length; i++) {
const output = updateProjectItems[i];
try {
Expand All @@ -968,6 +985,47 @@ async function main() {
logGraphQLError(error, `Processing update_project item ${i + 1}`);
}
}

// Create views from frontmatter configuration if any
// Views are created after items are processed to ensure the project exists
if (viewsToCreate.length > 0) {
// Get project URL from the first update_project item or fail if none
const projectUrl = updateProjectItems.length > 0 ? updateProjectItems[0].project : null;

if (!projectUrl) {
core.warning("Cannot create configured views: no project URL found in update_project items. Views require at least one update_project operation to determine the target project.");
return;
}

core.info(`Creating ${viewsToCreate.length} configured view(s) on project: ${projectUrl}`);

for (let i = 0; i < viewsToCreate.length; i++) {
const viewConfig = viewsToCreate[i];
try {
// Create a synthetic output item for view creation
const viewOutput = {
type: "update_project",
project: projectUrl,
operation: "create_view",
view: {
name: viewConfig.name,
layout: viewConfig.layout,
filter: viewConfig.filter,
visible_fields: viewConfig.visible_fields,
description: viewConfig.description,
},
};

await updateProject(viewOutput);
core.info(`✓ Created view ${i + 1}/${viewsToCreate.length}: ${viewConfig.name} (${viewConfig.layout})`);
} catch (err) {
// prettier-ignore
const error = /** @type {Error & { errors?: Array<{ type?: string, message: string, path?: unknown, locations?: unknown }>, request?: unknown, data?: unknown }} */ (err);
core.error(`Failed to create configured view ${i + 1}: ${viewConfig.name}`);
logGraphQLError(error, `Creating configured view: ${viewConfig.name}`);
}
}
}
}

module.exports = { updateProject, parseProjectInput, generateCampaignId, main };
19 changes: 18 additions & 1 deletion docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 21 additions & 0 deletions docs/src/content/docs/guides/campaigns/project-management.md
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,27 @@ The campaign generator creates three views automatically:
2. **Task Tracker** (Table view) - Detailed tracking with filtering
3. **Progress Board** (Board view) - Kanban-style progress tracking

### Declarative view configuration

Views can be declared directly in workflow frontmatter using the `views` property:

```yaml
safe-outputs:
update-project:
github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
views:
- name: "Sprint Board"
layout: board
filter: "is:issue is:open"
- name: "Task Tracker"
layout: table
filter: "is:issue,is:pull_request"
- name: "Timeline"
layout: roadmap
```

Views are automatically created when the workflow runs. This declarative approach is simpler than programmatic view creation and ensures views are configured consistently across workflow runs.

**Customization tips:**

**Multi-Workflow Campaign**: Use Roadmap grouped by Worker/Workflow for timeline distribution, Task Tracker sliced by Priority+Status for urgent items, Progress Board grouped by Status for progress tracking.
Expand Down
14 changes: 14 additions & 0 deletions docs/src/content/docs/reference/frontmatter-full.md
Original file line number Diff line number Diff line change
Expand Up @@ -1671,6 +1671,20 @@ safe-outputs:
# (optional)
github-token: "${{ secrets.GITHUB_TOKEN }}"

# Optional array of project views to create automatically. Each view must have a
# name and layout. Views are created during workflow execution after processing
# agent output items.
# (optional)
views:
- name: "Sprint Board"
layout: board # table, board, or roadmap
filter: "is:issue is:open" # optional filter query
- name: "Task Tracker"
layout: table
filter: "is:issue,is:pull_request"
- name: "Campaign Timeline"
layout: roadmap

# Option 2: Enable project management with default configuration (max=10)
update-project: null

Expand Down
51 changes: 51 additions & 0 deletions docs/src/content/docs/reference/safe-outputs.md
Original file line number Diff line number Diff line change
Expand Up @@ -321,6 +321,14 @@ safe-outputs:
update-project:
max: 20 # max operations (default: 10)
github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
views: # optional: auto-create views
- name: "Sprint Board"
layout: board
filter: "is:issue is:open"
- name: "Task Tracker"
layout: table
- name: "Campaign Roadmap"
layout: roadmap
```

Agent must provide full project URL (e.g., `https://github.com/orgs/myorg/projects/42`). Optional `campaign_id` applies `campaign:<id>` labels for [Campaign Workflows](/gh-aw/guides/campaigns/). Exposes outputs: `project-id`, `project-number`, `project-url`, `campaign-id`, `item-id`.
Expand Down Expand Up @@ -348,6 +356,49 @@ fields:
> [!NOTE]
> Field names are case-insensitive and automatically normalized (e.g., `story_points` matches `Story Points`).

#### Creating Project Views

Project views can be created automatically by declaring them in the `views` array. Views are created when the workflow runs, after processing update_project items from the agent.

**View configuration:**
```yaml
safe-outputs:
update-project:
github-token: ${{ secrets.GH_AW_PROJECT_GITHUB_TOKEN }}
views:
- name: "Sprint Board" # required: view name
layout: board # required: table, board, or roadmap
filter: "is:issue is:open" # optional: filter query
- name: "Task Tracker"
layout: table
filter: "is:issue,is:pull_request"
- name: "Campaign Timeline"
layout: roadmap
```

**View properties:**

| Property | Type | Required | Description |
|----------|------|----------|-------------|
| `name` | string | Yes | View name (e.g., "Sprint Board", "Task Tracker") |
| `layout` | string | Yes | View layout: `table`, `board`, or `roadmap` |
| `filter` | string | No | Filter query (e.g., `is:issue is:open`, `label:bug`) |
| `visible-fields` | array | No | Field IDs to display (table/board only, not roadmap) |

**Layout types:**
- **`table`** — List view with customizable columns for detailed tracking
- **`board`** — Kanban-style cards grouped by status or custom field
- **`roadmap`** — Timeline visualization with date-based swimlanes

**Filter syntax examples:**
- `is:issue is:open` — Open issues only
- `is:pull_request` — Pull requests only
- `is:issue,is:pull_request` — Both issues and PRs
- `label:bug` — Items with bug label
- `assignee:@me` — Items assigned to viewer

Views are created automatically during workflow execution. The workflow must include at least one `update_project` operation to provide the target project URL. See [Project Management Guide](/gh-aw/guides/campaigns/project-management/) for view customization strategies.



### Project Board Copy (`copy-project:`)
Expand Down
38 changes: 37 additions & 1 deletion pkg/parser/schemas/main_workflow_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -3694,7 +3694,7 @@
"oneOf": [
{
"type": "object",
"description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, campaign_id, and create_if_missing.",
"description": "Configuration for managing GitHub Projects v2 boards. Smart tool that can add issue/PR items and update custom fields on existing items. By default it is update-only: if the project does not exist, the job fails with instructions to create it manually. To allow workflows to create missing projects, explicitly opt in via the agent output field create_if_missing=true (and/or provide a github-token override). NOTE: Projects v2 requires a Personal Access Token (PAT) or GitHub App token with appropriate permissions; the GITHUB_TOKEN cannot be used for Projects v2. Safe output items produced by the agent use type=update_project Configuration also supports an optional views array for declaring project views to create. Safe output items produced by the agent use type=update_project and may include: project (board name), content_type (issue|pull_request), content_number, fields, campaign_id, and create_if_missing.",
"properties": {
"max": {
"type": "integer",
Expand All @@ -3705,6 +3705,42 @@
"github-token": {
"$ref": "#/$defs/github_token",
"description": "GitHub token to use for this specific output type. Overrides global github-token if specified."
},
"views": {
"type": "array",
"description": "Optional array of project views to create. Each view must have a name and layout. Views are created during project setup.",
"items": {
"type": "object",
"description": "View configuration for creating project views",
"required": ["name", "layout"],
"properties": {
"name": {
"type": "string",
"description": "The name of the view (e.g., 'Sprint Board', 'Campaign Roadmap')"
},
"layout": {
"type": "string",
"enum": ["table", "board", "roadmap"],
"description": "The layout type of the view"
},
"filter": {
"type": "string",
"description": "Optional filter query for the view (e.g., 'is:issue is:open', 'label:bug')"
},
"visible-fields": {
"type": "array",
"items": {
"type": "integer"
},
"description": "Optional array of field IDs that should be visible in the view (table/board only, not applicable to roadmap)"
},
"description": {
"type": "string",
"description": "Optional human description for the view. Not supported by the GitHub Views API and may be ignored."
}
},
"additionalProperties": false
}
}
},
"additionalProperties": false,
Expand Down
Loading
Loading