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
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,16 @@ jobs:
content_id: ${{ github.event.client_payload.command.resource.id }}
field: Status
value: ${{ github.event.client_payload.data.status }}
- name: Clear due date
id: clear_due_date
uses: github/update-project-action@v3
with:
github_token: ${{ secrets.STATUS_UPDATE_TOKEN }}
organization: github
project_number: 1234
content_id: ${{ github.event.client_payload.command.resource.id }}
field: "Due Date"
operation: clear
```

*Note: The above step can be repeated multiple times in a given job to update multiple fields on the same or different projects.*
Expand All @@ -62,10 +72,10 @@ The Action is largely feature complete with regards to its initial goals. Find a
* `content_id` - The global ID of the issue or pull request within the project
* `field` - The field on the project to set the value of
* `github_token` - A GitHub Token with access to both the source issue and the destination project (`repo` and `write:org` scopes)
* `operation` - Operation type (update or read)
* `operation` - Operation type (update, read, or clear)
* `organization` - The organization that contains the project, defaults to the current repository owner
* `project_number` - The project number from the project's URL
* `value` - The value to set the project field to. Only required for operation type read
* `value` - The value to set the project field to. Only required for operation type `update`; not required for `read` or `clear`.

### Outputs

Expand Down
64 changes: 64 additions & 0 deletions __test__/main.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ describe("with environmental variables", () => {
expect(result.operation).toEqual("read");
});

test("getInputs accepts clear", () => {
process.env = { ...process.env, ...{ INPUT_OPERATION: "clear" } };
const result = updateProject.getInputs();
expect(result.operation).toEqual("clear");
});

test("getInputs doesn't accept other operations", () => {
process.env = { ...process.env, ...{ INPUT_OPERATION: "foo" } };
const result = updateProject.getInputs();
Expand Down Expand Up @@ -514,6 +520,41 @@ describe("with Octokit setup", () => {
expect(mock.done()).toBe(true);
});

test("clearField clears a field", async () => {
const item = { project: { number: 1, owner: { login: "github" } } };
mockContentMetadata("test", item);

const field = {
id: 1,
name: "testField",
dataType: "date",
};
mockProjectMetadata(1, field);

const data = { data: { projectV2Item: { id: 1 } } };
mockGraphQL(data, "clearField", "clearProjectV2ItemFieldValue");

const projectMetadata = await updateProject.fetchProjectMetadata(
"github",
1,
"testField",
"",
"clear"
);
const contentMetadata = await updateProject.fetchContentMetadata(
"1",
"test",
1,
"github"
);
const result = await updateProject.clearField(
projectMetadata,
contentMetadata
);
expect(result).toEqual(data.data);
expect(mock.done()).toBe(true);
});

test("run updates a field that was not empty", async () => {
const item = {
field: { value: "testValue" },
Expand Down Expand Up @@ -617,4 +658,27 @@ describe("with Octokit setup", () => {
await updateProject.run();
expect(mock.done()).toBe(true);
});

test("run clears a field", async () => {
process.env = { ...OLD_ENV, ...INPUTS, ...{ INPUT_OPERATION: "clear" } };

const item = {
field: { value: "2023-01-01" },
project: { number: 1, owner: { login: "github" } },
};
mockContentMetadata("testField", item);

const field = {
id: 1,
name: "testField",
dataType: "date",
};
mockProjectMetadata(1, field);

const data = { data: { projectV2Item: { id: 1 } } };
mockGraphQL(data, "clearField", "clearProjectV2ItemFieldValue");

await updateProject.run();
expect(mock.done()).toBe(true);
});
});
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ inputs:
description: The project number from the project's URL
required: true
operation:
description: Operation type (update or read)
description: Operation type (update, read, or clear)
default: update
required: false
content_id:
Expand Down
42 changes: 39 additions & 3 deletions dist/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -10408,7 +10408,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
});
};
Object.defineProperty(exports, "__esModule", ({ value: true }));
exports.run = exports.setupOctokit = exports.getInputs = exports.updateField = exports.convertValueToFieldType = exports.valueGraphqlType = exports.ensureExists = exports.fetchProjectMetadata = exports.fetchContentMetadata = void 0;
exports.run = exports.setupOctokit = exports.getInputs = exports.clearField = exports.updateField = exports.convertValueToFieldType = exports.valueGraphqlType = exports.ensureExists = exports.fetchProjectMetadata = exports.fetchContentMetadata = void 0;
const core_1 = __nccwpck_require__(2186);
const github_1 = __nccwpck_require__(5438);
let octokit;
Expand Down Expand Up @@ -10648,6 +10648,37 @@ function updateField(projectMetadata, contentMetadata, value) {
});
}
exports.updateField = updateField;
/**
* Clears the field value for the content item
* @param {GraphQlQueryResponseData} projectMetadata - The project metadata returned from fetchProjectMetadata()
* @param {GraphQlQueryResponseData} contentMetadata - The content metadata returned from fetchContentMetadata()
* @return {Promise<GraphQlQueryResponseData>} - The updated content metadata
*/
function clearField(projectMetadata, contentMetadata) {
return __awaiter(this, void 0, void 0, function* () {
const result = yield octokit.graphql(`
mutation($project: ID!, $item: ID!, $field: ID!) {
clearProjectV2ItemFieldValue(
input: {
projectId: $project
itemId: $item
fieldId: $field
}
) {
projectV2Item {
id
}
}
}
`, {
project: projectMetadata.projectId,
item: contentMetadata.id,
field: projectMetadata.field.fieldId,
});
return result;
});
}
exports.clearField = clearField;
/**
* Returns the validated and normalized inputs for the action
*
Expand All @@ -10657,8 +10688,8 @@ function getInputs() {
let operation = (0, core_1.getInput)("operation");
if (operation === "")
operation = "update";
if (!["read", "update"].includes(operation)) {
(0, core_1.setFailed)(`Invalid value passed for the 'operation' parameter (passed: ${operation}, allowed: read, update)`);
if (!["read", "update", "clear"].includes(operation)) {
(0, core_1.setFailed)(`Invalid value passed for the 'operation' parameter (passed: ${operation}, allowed: read, update, clear)`);
return {};
}
const inputs = {
Expand Down Expand Up @@ -10704,6 +10735,11 @@ function run() {
(0, core_1.setOutput)("field_updated_value", inputs.value);
(0, core_1.info)(`Updated field ${inputs.fieldName} on ${contentMetadata.title} to ${inputs.value}`);
}
else if (inputs.operation === "clear") {
yield clearField(projectMetadata, contentMetadata);
(0, core_1.setOutput)("field_updated_value", null);
(0, core_1.info)(`Cleared field ${inputs.fieldName} on ${contentMetadata.title}`);
}
else {
(0, core_1.setOutput)("field_updated_value", (_b = contentMetadata.field) === null || _b === void 0 ? void 0 : _b.value);
}
Expand Down
44 changes: 42 additions & 2 deletions src/update-project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -289,6 +289,42 @@ export async function updateField(
return result;
}

/**
* Clears the field value for the content item
* @param {GraphQlQueryResponseData} projectMetadata - The project metadata returned from fetchProjectMetadata()
* @param {GraphQlQueryResponseData} contentMetadata - The content metadata returned from fetchContentMetadata()
* @return {Promise<GraphQlQueryResponseData>} - The updated content metadata
*/
export async function clearField(
projectMetadata: GraphQlQueryResponseData,
contentMetadata: GraphQlQueryResponseData
): Promise<GraphQlQueryResponseData> {
const result: GraphQlQueryResponseData = await octokit.graphql(
`
mutation($project: ID!, $item: ID!, $field: ID!) {
clearProjectV2ItemFieldValue(
input: {
projectId: $project
itemId: $item
fieldId: $field
}
) {
projectV2Item {
id
}
}
}
`,
{
project: projectMetadata.projectId,
item: contentMetadata.id,
field: projectMetadata.field.fieldId,
}
);

return result;
}

/**
* Returns the validated and normalized inputs for the action
*
Expand All @@ -298,9 +334,9 @@ export function getInputs(): { [key: string]: any } {
let operation = getInput("operation");
if (operation === "") operation = "update";

if (!["read", "update"].includes(operation)) {
if (!["read", "update", "clear"].includes(operation)) {
setFailed(
`Invalid value passed for the 'operation' parameter (passed: ${operation}, allowed: read, update)`
`Invalid value passed for the 'operation' parameter (passed: ${operation}, allowed: read, update, clear)`
);

return {};
Expand Down Expand Up @@ -361,6 +397,10 @@ export async function run(): Promise<void> {
info(
`Updated field ${inputs.fieldName} on ${contentMetadata.title} to ${inputs.value}`
);
} else if (inputs.operation === "clear") {
await clearField(projectMetadata, contentMetadata);
setOutput("field_updated_value", null);
info(`Cleared field ${inputs.fieldName} on ${contentMetadata.title}`);
} else {
setOutput("field_updated_value", contentMetadata.field?.value);
}
Expand Down
Loading