diff --git a/README.md b/README.md index 4093322..8cbb0ce 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # GitHub Issue Parser -Use this action to convert issues into a unified JSON structure. +Use this action to convert issues into a unified JSON structure. Read the [Codeless Contributions with GitHub Issue Forms](https://stefanbuck.com/blog/codeless-contributions-with-github-issue-forms) post on my blog. ## Setup @@ -55,6 +55,7 @@ body: required: true - type: checkboxes + id: what_else attributes: label: What else? options: @@ -95,8 +96,7 @@ The actions output will be "what_happened": "A bug happened!", "version": "1.0.0", "browsers": "Chrome, Safari", - "never_give_up": true, - "hot_dog_is_a_sandwich": false + "what_else": ["Never give up"] } ``` diff --git a/fixtures/readme-example/expected.json b/fixtures/readme-example/expected.json index ef80cc0..dec22c1 100644 --- a/fixtures/readme-example/expected.json +++ b/fixtures/readme-example/expected.json @@ -3,6 +3,7 @@ "what_happened": "A bug happened!", "version": "1.0.0", "browsers": "Chrome, Safari", - "never_give_up": true, - "hot_dog_is_a_sandwich": false + "anything_else": ["Never give up"], + "second_anything_else": ["Hot Dog is a Sandwich", "Another item"], + "checkbox_without_an_id": [] } diff --git a/fixtures/readme-example/form.yml b/fixtures/readme-example/form.yml index 5aebdd4..6ec1db5 100644 --- a/fixtures/readme-example/form.yml +++ b/fixtures/readme-example/form.yml @@ -33,8 +33,25 @@ body: required: true - type: checkboxes + id: anything_else attributes: label: What else? options: - label: Never give up - label: Hot Dog is a Sandwich + + - type: checkboxes + id: second_anything_else + attributes: + label: And with that? + options: + - label: Never give up + - label: Hot Dog is a Sandwich + - label: Another item + + - type: checkboxes + attributes: + label: Checkbox without an id? + options: + - label: IDs are great + - label: IDs are bad diff --git a/fixtures/readme-example/issue-body.md b/fixtures/readme-example/issue-body.md index e0cc3db..030ece6 100644 --- a/fixtures/readme-example/issue-body.md +++ b/fixtures/readme-example/issue-body.md @@ -14,7 +14,18 @@ A bug happened! Chrome, Safari -### Code of Conduct +### What else? - [x] Never give up - [ ] Hot Dog is a Sandwich + +### And with that? + +- [ ] Never give up +- [x] Hot Dog is a Sandwich +- [x] Another item + +### Checkbox without an id? + +- [ ] IDs are great +- [ ] IDs are bad diff --git a/index.js b/index.js index d679020..f26f7ea 100644 --- a/index.js +++ b/index.js @@ -32,7 +32,24 @@ async function run(env, eventPayload, fs, core) { if (item.type === "markdown") { return null; } - return [item.attributes.label, item.id]; + return [item.attributes.label, item.id, item.type]; + }) + .filter(Boolean) + ); + } + + function getIDTypesFromIssueTemplate(formTemplate) { + if (!formTemplate.body) { + return {}; + } + + return Object.fromEntries( + formTemplate.body + .map((item) => { + if (item.type === "markdown") { + return null; + } + return [toKey(item.attributes.label), item.type]; }) .filter(Boolean) ); @@ -41,6 +58,7 @@ async function run(env, eventPayload, fs, core) { let result; const body = eventPayload.issue.body || ''; const idMapping = getIDsFromIssueTemplate(form); + const idTypes = getIDTypesFromIssueTemplate(form); function toKey(str) { if (idMapping[str]) { @@ -66,6 +84,33 @@ async function run(env, eventPayload, fs, core) { return value; } + //toObject merges checkbox results in an array and spits out correctly formatted object + function toObject(array) { + let result = {}; + + array.forEach((item) => { + const key = item && item[0]; + const value = item && item[1]; + + if (key in result) { + const content = result[key]; + + if (value !== undefined) { + result[key] = content.concat(value); + } + return; + } + + if (idTypes[key] == "checkboxes") { + result[key] = value === undefined ? [] : [value]; + } else { + result[key] = value; + } + }); + + return result + } + result = body .trim() .split("###") @@ -74,12 +119,18 @@ async function run(env, eventPayload, fs, core) { return line .split(/\r?\n\r?\n/) .filter(Boolean) - .map((item) => { + .map((item, index, arr) => { const line = item.trim(); + if (line.startsWith("- [")) { return line.split(/\r?\n/).map((check) => { const field = check.replace(/- \[[X\s]\]\s+/i, ""); - return [`${field}`, check.toUpperCase().startsWith("- [X] ")]; + const previousIndex = index === 0 ? index : index - 1; + const key = arr[previousIndex].trim(); + if (check.toUpperCase().startsWith("- [X] ")) { + return [key, field]; + } + return [key]; }); } @@ -92,32 +143,30 @@ async function run(env, eventPayload, fs, core) { } return [...prev, curr]; - }, []) - .map(([key, ...lines]) => { - const checkListValue = lines.find((line) => Array.isArray(line)); - const value = checkListValue - ? toValue(checkListValue) - : toValue(...lines); - - return [toKey(key), value]; - }); + }, []); - result.forEach(([key, value]) => { - core.setOutput(`issueparser_${key}`, value); + result = result.map(([key, ...lines]) => { + const checkListValue = lines.find((line) => Array.isArray(line)); + const value = checkListValue ? toValue(checkListValue) : toValue(...lines); + + return [toKey(key), value]; }); + result = toObject(result); + Object.entries(result).forEach(([key, value]) => { + core.setOutput(`issueparser_${key}`, Array.isArray(value) ? value.join(',') : value); + }) + function jsonStringify(json) { return JSON.stringify(json, null, 2); } - const json = Object.fromEntries(result); - fs.writeFileSync( `${env.USERPROFILE || env.HOME}/issue-parser-result.json`, - jsonStringify(json), + jsonStringify(result), "utf-8" ); - core.setOutput("jsonString", jsonStringify(json)); + core.setOutput("jsonString", jsonStringify(result)); } // We wrap the code in a `run` function to enable testing. diff --git a/test.spec.js b/test.spec.js index 13585be..8870baf 100644 --- a/test.spec.js +++ b/test.spec.js @@ -34,27 +34,21 @@ it("readme example", () => { // mock core const core = { - getInput(inputName) { - expect(inputName).toBe("template-path"); - return ""; - }, - setOutput(outputName, outputValue) { - if (outputName === "jsonString") { - expect(outputValue).toBe(expectedOutputJson); - return; - } - - if (outputName.startsWith("issueparser_")) { - const key = outputName.substr("issueparser_".length); - expect(Object.keys(expectedOutput)).toContain(key); - - expect(outputValue).toBe(expectedOutput[key]); - return; - } - }, + getInput: jest.fn(() => ''), + setOutput: jest.fn(), }; run(env, eventPayload, fs, core); + expect(core.getInput).toHaveBeenCalledWith('template-path') + expect(core.setOutput).toHaveBeenCalledWith('jsonString', JSON.stringify(expectedOutput, null, 2)) + expect(core.setOutput).toHaveBeenCalledWith('issueparser_contact', 'me@me.com') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_what_happened', 'A bug happened!') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_version', '1.0.0') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_browsers', 'Chrome, Safari') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_anything_else', 'Never give up') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_second_anything_else', 'Hot Dog is a Sandwich,Another item') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_checkbox_without_an_id', '') + expect(core.setOutput.mock.calls.length).toBe(8) }); it("multiple paragraphs", () => { @@ -84,27 +78,17 @@ it("multiple paragraphs", () => { // mock core const core = { - getInput(inputName) { - expect(inputName).toBe("template-path"); - return ""; - }, - setOutput(outputName, outputValue) { - if (outputName === "jsonString") { - expect(outputValue).toBe(expectedOutputJson); - return; - } - - if (outputName.startsWith("issueparser_")) { - const key = outputName.substr("issueparser_".length); - expect(Object.keys(expectedOutput)).toContain(key); - - expect(outputValue).toBe(expectedOutput[key]); - return; - } - }, + getInput: jest.fn(() => ''), + setOutput: jest.fn(), }; run(env, eventPayload, fs, core); + + expect(core.getInput).toHaveBeenCalledWith('template-path') + expect(core.setOutput).toHaveBeenCalledWith('jsonString', JSON.stringify(expectedOutput, null, 2)) + expect(core.setOutput).toHaveBeenCalledWith('issueparser_textarea-one', '1st paragraph\n\n2nd paragraph') + expect(core.setOutput).toHaveBeenCalledWith('issueparser_textarea-two', '1st paragraph\n2nd paragraph') + expect(core.setOutput.mock.calls.length).toBe(3) }); it("blank", () => {