Skip to content
Draft
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
6 changes: 6 additions & 0 deletions actions/setup/js/create_project.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -459,6 +459,11 @@ async function main(config = {}, githubClient = null) {

core.info(`✓ Successfully created project: ${projectInfo.projectUrl}`);

// Store temporary ID mapping so subsequent operations can reference this project
const normalizedTempId = normalizeTemporaryId(temporaryId ?? "");
temporaryIdMap.set(normalizedTempId, { projectUrl: projectInfo.projectUrl });
core.info(`Stored temporary ID mapping: ${temporaryId} -> ${projectInfo.projectUrl}`);

// Create configured views if any
if (configuredViews.length > 0) {
core.info(`Creating ${configuredViews.length} configured view(s) on project: ${projectInfo.projectUrl}`);
Expand Down Expand Up @@ -488,6 +493,7 @@ async function main(config = {}, githubClient = null) {
projectTitle: projectInfo.projectTitle,
projectUrl: projectInfo.projectUrl,
itemId: projectInfo.itemId,
temporaryId,
};
} catch (err) {
// prettier-ignore
Expand Down
22 changes: 21 additions & 1 deletion actions/setup/js/create_project_status_update.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
const { getErrorMessage } = require("./error_helpers.cjs");
const { sanitizeContent } = require("./sanitize_content.cjs");
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
const { isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs");
const { ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs");

/**
Expand Down Expand Up @@ -328,7 +329,8 @@ async function main(config = {}, githubClient = null) {

// Validate that project field is explicitly provided in the message
// The project field is required in agent output messages and must be a full GitHub project URL
const effectiveProjectUrl = output.project;
// or a temporary project ID (e.g., aw_abc123 or #aw_abc123) from create_project
let effectiveProjectUrl = output.project;

if (!effectiveProjectUrl || typeof effectiveProjectUrl !== "string" || effectiveProjectUrl.trim() === "") {
core.error('Missing required "project" field. The agent must explicitly include the project URL in the output message: {"type": "create_project_status_update", "project": "https://github.com/orgs/myorg/projects/42", "body": "..."}');
Expand All @@ -338,6 +340,24 @@ async function main(config = {}, githubClient = null) {
};
}

// Resolve temporary project ID if present
const projectStr = effectiveProjectUrl.trim();
const projectWithoutHash = projectStr.startsWith("#") ? projectStr.substring(1) : projectStr;
if (isTemporaryId(projectWithoutHash)) {
const normalizedId = normalizeTemporaryId(projectWithoutHash);
const resolved = temporaryIdMap.get(normalizedId);
if (resolved && typeof resolved === "object" && "projectUrl" in resolved && resolved.projectUrl) {
core.info(`Resolved temporary project ID ${projectStr} to ${resolved.projectUrl}`);
effectiveProjectUrl = resolved.projectUrl;
} else {
core.error(`Temporary project ID '${projectStr}' not found. Ensure create_project was called before create_project_status_update.`);
return {
success: false,
error: `${ERR_NOT_FOUND}: Temporary project ID '${projectStr}' not found. Ensure create_project was called before create_project_status_update.`,
};
}
}

if (!output.body) {
core.error("Missing required field: body (status update content)");
return {
Expand Down
110 changes: 110 additions & 0 deletions actions/setup/js/create_project_status_update.test.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -600,4 +600,114 @@ describe("create_project_status_update", () => {
// Cleanup
delete process.env.GH_AW_PROJECT_URL;
});

it("should resolve a temporary project ID from temporaryIdMap", async () => {
mockGithub.graphql
.mockResolvedValueOnce({
organization: {
projectV2: {
id: "PVT_test123",
number: 42,
title: "Test Project",
url: "https://github.com/orgs/test-org/projects/42",
},
},
})
.mockResolvedValueOnce({
createProjectV2StatusUpdate: {
statusUpdate: {
id: "PVTSU_test123",
body: "Test status update",
bodyHTML: "<p>Test status update</p>",
startDate: "2025-01-01",
targetDate: "2025-12-31",
status: "ON_TRACK",
createdAt: "2025-01-06T12:00:00Z",
},
},
});

const handler = await main({ max: 10 });

const temporaryIdMap = new Map();
temporaryIdMap.set("aw_abc12345", { projectUrl: "https://github.com/orgs/test-org/projects/42" });

const result = await handler(
{
project: "aw_abc12345",
body: "Test status update",
status: "ON_TRACK",
start_date: "2025-01-01",
target_date: "2025-12-31",
},
temporaryIdMap
);

expect(result.success).toBe(true);
expect(result.status_update_id).toBe("PVTSU_test123");
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID aw_abc12345"));
});

it("should resolve a temporary project ID with hash prefix from temporaryIdMap", async () => {
mockGithub.graphql
.mockResolvedValueOnce({
organization: {
projectV2: {
id: "PVT_test123",
number: 42,
title: "Test Project",
url: "https://github.com/orgs/test-org/projects/42",
},
},
})
.mockResolvedValueOnce({
createProjectV2StatusUpdate: {
statusUpdate: {
id: "PVTSU_test123",
body: "Test status update",
bodyHTML: "<p>Test status update</p>",
startDate: "2025-01-01",
targetDate: "2025-12-31",
status: "ON_TRACK",
createdAt: "2025-01-06T12:00:00Z",
},
},
});

const handler = await main({ max: 10 });

const temporaryIdMap = new Map();
temporaryIdMap.set("aw_abc12345", { projectUrl: "https://github.com/orgs/test-org/projects/42" });

const result = await handler(
{
project: "#aw_abc12345",
body: "Test status update",
},
temporaryIdMap
);

expect(result.success).toBe(true);
expect(result.status_update_id).toBe("PVTSU_test123");
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID #aw_abc12345"));
});

it("should return error when temporary project ID is not found in temporaryIdMap", async () => {
const handler = await main({ max: 10 });

const temporaryIdMap = new Map();

const result = await handler(
{
project: "aw_notfound",
body: "Test status update",
},
temporaryIdMap
);

expect(result.success).toBe(false);
expect(result.error).toContain("aw_notfound");
expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("aw_notfound"));
expect(mockGithub.graphql).not.toHaveBeenCalled();
});
});