Skip to content

Commit bac1f8a

Browse files
authored
Allow temporary IDs for all project operations (#19573)
1 parent 0b72c1f commit bac1f8a

3 files changed

Lines changed: 149 additions & 7 deletions

File tree

actions/setup/js/create_project.cjs

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -326,11 +326,11 @@ async function main(config = {}, githubClient = null) {
326326
/**
327327
* Message handler function that processes a single create_project message
328328
* @param {Object} message - The create_project message to process
329-
* @param {Map<string, {repo?: string, number?: number, projectUrl?: string}>} temporaryIdMap - Unified map of temporary IDs
330329
* @param {Object} resolvedTemporaryIds - Plain object version of temporaryIdMap for backward compatibility
330+
* @param {Map<string, {repo?: string, number?: number, projectUrl?: string}>|null} temporaryIdMap - Unified map of temporary IDs
331331
* @returns {Promise<Object>} Result with success/error status
332332
*/
333-
return async function handleCreateProject(message, temporaryIdMap, resolvedTemporaryIds = {}) {
333+
return async function handleCreateProject(message, resolvedTemporaryIds = {}, temporaryIdMap = null) {
334334
// Check max limit
335335
if (processedCount >= maxCount) {
336336
core.warning(`Skipping create_project: max count of ${maxCount} reached`);
@@ -370,8 +370,9 @@ async function main(config = {}, githubClient = null) {
370370

371371
// Check if it's a valid temporary ID
372372
if (isTemporaryId(tempIdWithoutHash)) {
373-
// Look up in the unified temporaryIdMap
374-
const resolved = temporaryIdMap.get(normalizeTemporaryId(tempIdWithoutHash));
373+
// Look up in the unified temporaryIdMap (Map) or resolvedTemporaryIds (plain object)
374+
const normalizedKey = normalizeTemporaryId(tempIdWithoutHash);
375+
const resolved = temporaryIdMap instanceof Map ? temporaryIdMap.get(normalizedKey) : resolvedTemporaryIds[normalizedKey];
375376

376377
if (resolved && resolved.repo && resolved.number) {
377378
// Build the proper GitHub issue URL
@@ -459,6 +460,13 @@ async function main(config = {}, githubClient = null) {
459460

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

463+
// Store temporary ID mapping so subsequent operations can reference this project
464+
const normalizedTempId = normalizeTemporaryId(temporaryId ?? "");
465+
if (temporaryIdMap instanceof Map) {
466+
temporaryIdMap.set(normalizedTempId, { projectUrl: projectInfo.projectUrl });
467+
}
468+
core.info(`Stored temporary ID mapping: ${temporaryId} -> ${projectInfo.projectUrl}`);
469+
462470
// Create configured views if any
463471
if (configuredViews.length > 0) {
464472
core.info(`Creating ${configuredViews.length} configured view(s) on project: ${projectInfo.projectUrl}`);
@@ -488,6 +496,7 @@ async function main(config = {}, githubClient = null) {
488496
projectTitle: projectInfo.projectTitle,
489497
projectUrl: projectInfo.projectUrl,
490498
itemId: projectInfo.itemId,
499+
temporaryId,
491500
};
492501
} catch (err) {
493502
// prettier-ignore

actions/setup/js/create_project_status_update.cjs

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { loadAgentOutput } = require("./load_agent_output.cjs");
55
const { getErrorMessage } = require("./error_helpers.cjs");
66
const { sanitizeContent } = require("./sanitize_content.cjs");
77
const { logStagedPreviewInfo } = require("./staged_preview.cjs");
8+
const { isTemporaryId, normalizeTemporaryId } = require("./temporary_id.cjs");
89
const { ERR_CONFIG, ERR_NOT_FOUND, ERR_PARSE, ERR_VALIDATION } = require("./error_codes.cjs");
910

1011
/**
@@ -308,11 +309,11 @@ async function main(config = {}, githubClient = null) {
308309
/**
309310
* Message handler function that processes a single create_project_status_update message
310311
* @param {Object} message - The create_project_status_update message to process
311-
* @param {Map<string, {repo?: string, number?: number, projectUrl?: string}>} temporaryIdMap - Unified map of temporary IDs
312312
* @param {Object} resolvedTemporaryIds - Plain object version of temporaryIdMap for backward compatibility
313+
* @param {Map<string, {repo?: string, number?: number, projectUrl?: string}>|null} temporaryIdMap - Unified map of temporary IDs
313314
* @returns {Promise<Object>} Result with success/error status and status update details
314315
*/
315-
return async function handleCreateProjectStatusUpdate(message, temporaryIdMap, resolvedTemporaryIds = {}) {
316+
return async function handleCreateProjectStatusUpdate(message, resolvedTemporaryIds = {}, temporaryIdMap = null) {
316317
// Check if we've hit the max limit
317318
if (processedCount >= maxCount) {
318319
core.warning(`Skipping create-project-status-update: max count of ${maxCount} reached`);
@@ -328,7 +329,8 @@ async function main(config = {}, githubClient = null) {
328329

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

333335
if (!effectiveProjectUrl || typeof effectiveProjectUrl !== "string" || effectiveProjectUrl.trim() === "") {
334336
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": "..."}');
@@ -338,6 +340,24 @@ async function main(config = {}, githubClient = null) {
338340
};
339341
}
340342

343+
// Resolve temporary project ID if present
344+
const projectStr = effectiveProjectUrl.trim();
345+
const projectWithoutHash = projectStr.startsWith("#") ? projectStr.substring(1) : projectStr;
346+
if (isTemporaryId(projectWithoutHash)) {
347+
const normalizedId = normalizeTemporaryId(projectWithoutHash);
348+
const resolved = temporaryIdMap instanceof Map ? temporaryIdMap.get(normalizedId) : resolvedTemporaryIds[normalizedId];
349+
if (resolved && typeof resolved === "object" && "projectUrl" in resolved && resolved.projectUrl) {
350+
core.info(`Resolved temporary project ID ${projectStr} to ${resolved.projectUrl}`);
351+
effectiveProjectUrl = resolved.projectUrl;
352+
} else {
353+
core.error(`Temporary project ID '${projectStr}' not found. Ensure create_project was called before create_project_status_update.`);
354+
return {
355+
success: false,
356+
error: `${ERR_NOT_FOUND}: Temporary project ID '${projectStr}' not found. Ensure create_project was called before create_project_status_update.`,
357+
};
358+
}
359+
}
360+
341361
if (!output.body) {
342362
core.error("Missing required field: body (status update content)");
343363
return {

actions/setup/js/create_project_status_update.test.cjs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -600,4 +600,117 @@ describe("create_project_status_update", () => {
600600
// Cleanup
601601
delete process.env.GH_AW_PROJECT_URL;
602602
});
603+
604+
it("should resolve a temporary project ID from temporaryIdMap", async () => {
605+
mockGithub.graphql
606+
.mockResolvedValueOnce({
607+
organization: {
608+
projectV2: {
609+
id: "PVT_test123",
610+
number: 42,
611+
title: "Test Project",
612+
url: "https://github.com/orgs/test-org/projects/42",
613+
},
614+
},
615+
})
616+
.mockResolvedValueOnce({
617+
createProjectV2StatusUpdate: {
618+
statusUpdate: {
619+
id: "PVTSU_test123",
620+
body: "Test status update",
621+
bodyHTML: "<p>Test status update</p>",
622+
startDate: "2025-01-01",
623+
targetDate: "2025-12-31",
624+
status: "ON_TRACK",
625+
createdAt: "2025-01-06T12:00:00Z",
626+
},
627+
},
628+
});
629+
630+
const handler = await main({ max: 10 });
631+
632+
const temporaryIdMap = new Map();
633+
temporaryIdMap.set("aw_abc12345", { projectUrl: "https://github.com/orgs/test-org/projects/42" });
634+
635+
const result = await handler(
636+
{
637+
project: "aw_abc12345",
638+
body: "Test status update",
639+
status: "ON_TRACK",
640+
start_date: "2025-01-01",
641+
target_date: "2025-12-31",
642+
},
643+
Object.fromEntries(temporaryIdMap),
644+
temporaryIdMap
645+
);
646+
647+
expect(result.success).toBe(true);
648+
expect(result.status_update_id).toBe("PVTSU_test123");
649+
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID aw_abc12345"));
650+
});
651+
652+
it("should resolve a temporary project ID with hash prefix from temporaryIdMap", async () => {
653+
mockGithub.graphql
654+
.mockResolvedValueOnce({
655+
organization: {
656+
projectV2: {
657+
id: "PVT_test123",
658+
number: 42,
659+
title: "Test Project",
660+
url: "https://github.com/orgs/test-org/projects/42",
661+
},
662+
},
663+
})
664+
.mockResolvedValueOnce({
665+
createProjectV2StatusUpdate: {
666+
statusUpdate: {
667+
id: "PVTSU_test123",
668+
body: "Test status update",
669+
bodyHTML: "<p>Test status update</p>",
670+
startDate: "2025-01-01",
671+
targetDate: "2025-12-31",
672+
status: "ON_TRACK",
673+
createdAt: "2025-01-06T12:00:00Z",
674+
},
675+
},
676+
});
677+
678+
const handler = await main({ max: 10 });
679+
680+
const temporaryIdMap = new Map();
681+
temporaryIdMap.set("aw_abc12345", { projectUrl: "https://github.com/orgs/test-org/projects/42" });
682+
683+
const result = await handler(
684+
{
685+
project: "#aw_abc12345",
686+
body: "Test status update",
687+
},
688+
Object.fromEntries(temporaryIdMap),
689+
temporaryIdMap
690+
);
691+
692+
expect(result.success).toBe(true);
693+
expect(result.status_update_id).toBe("PVTSU_test123");
694+
expect(mockCore.info).toHaveBeenCalledWith(expect.stringContaining("Resolved temporary project ID #aw_abc12345"));
695+
});
696+
697+
it("should return error when temporary project ID is not found in temporaryIdMap", async () => {
698+
const handler = await main({ max: 10 });
699+
700+
const temporaryIdMap = new Map();
701+
702+
const result = await handler(
703+
{
704+
project: "aw_notfound",
705+
body: "Test status update",
706+
},
707+
Object.fromEntries(temporaryIdMap),
708+
temporaryIdMap
709+
);
710+
711+
expect(result.success).toBe(false);
712+
expect(result.error).toContain("aw_notfound");
713+
expect(mockCore.error).toHaveBeenCalledWith(expect.stringContaining("aw_notfound"));
714+
expect(mockGithub.graphql).not.toHaveBeenCalled();
715+
});
603716
});

0 commit comments

Comments
 (0)