diff --git a/404.html b/404.html new file mode 100644 index 0000000..de1a8b8 --- /dev/null +++ b/404.html @@ -0,0 +1,25 @@ + + + + + + + + + +

Page not found.

+ + + \ No newline at end of file diff --git a/js/formDataToJson.js b/js/formDataToJson.js index 9b8f0a0..c4d939a 100644 --- a/js/formDataToJson.js +++ b/js/formDataToJson.js @@ -16,23 +16,23 @@ async function retrieveFile(filePath) { function isMultiSelect(obj) { for (const key in obj) { - if (typeof obj[key] !== 'boolean') { - return false; - } + if (typeof obj[key] !== 'boolean') { + return false; + } } return true; // Returns true if all values are booleans } // Convert from dictionary to array function getSelectedOptions(options) { - let selectedOptions = []; - - for (let key in options) { - if(options[key]) { - selectedOptions.push(key); - } - } - return selectedOptions; + let selectedOptions = []; + + for (let key in options) { + if (options[key]) { + selectedOptions.push(key); + } + } + return selectedOptions; } // Populates fields with form data @@ -46,7 +46,7 @@ function populateObject(data, schema) { let value = data[key]; // Adjusts value accordingly if multi-select field - if((typeof value === "object" && isMultiSelect(value))) { + if ((typeof value === "object" && isMultiSelect(value))) { value = getSelectedOptions(value); } @@ -57,7 +57,10 @@ function populateObject(data, schema) { } async function populateCodeJson(data) { - const filePath = "schemas/schema.json"; + // Fetching schema based on search params + const params = new URLSearchParams(window.location.search); + const page = params.get("page") || "gov"; + const filePath = `schemas/${page}/schema.json`; // Retrieves schema with fields in correct order const schema = await retrieveFile(filePath); @@ -87,37 +90,33 @@ async function createCodeJson(data) { } // Copies code.json to clipboard -async function copyToClipboard(event){ +async function copyToClipboard(event) { event.preventDefault(); var textArea = document.getElementById("json-result"); - textArea.select(); + textArea.select(); document.execCommand("copy") } const NEW_BRANCH = 'code-json-branch' + Math.random().toString(36).substring(2, 10); -function getOrgAndRepoArgsGitHub(url) -{ +function getOrgAndRepoArgsGitHub(url) { const pattern = /https:\/\/github\.com\/([^\/]+)\/([^\/]+)/; - const match = url.match(pattern); + const match = url.match(pattern); - if(match) - { + if (match) { const owner = match[1]; const repo = match[2]; - return {owner,repo}; + return { owner, repo }; } - else - { + else { throw new Error('Invalid URL!'); } } -async function createBranchOnProject(projectURL, token) -{ - const {owner, repo} = getOrgAndRepoArgsGitHub(projectURL); +async function createBranchOnProject(projectURL, token) { + const { owner, repo } = getOrgAndRepoArgsGitHub(projectURL); const response = await fetch(`https://api.github.com/repos/${owner}/${repo}/git/refs/heads/main`, { @@ -130,41 +129,37 @@ async function createBranchOnProject(projectURL, token) const data = await response.json(); - if (response.ok) - { + if (response.ok) { const sha = data.object.sha; - + const createBranchApiUrl = `https://api.github.com/repos/${owner}/${repo}/git/refs`; // Create the new branch from the base branch const newBranchResponse = await fetch(createBranchApiUrl, { method: 'POST', headers: { - 'Content-Type': 'application/json', - 'Authorization': `token ${token}`, + 'Content-Type': 'application/json', + 'Authorization': `token ${token}`, }, body: JSON.stringify({ - ref: `refs/heads/${NEW_BRANCH}`, // Name of the new branch - sha: sha, // SHA of the base branch (main) + ref: `refs/heads/${NEW_BRANCH}`, // Name of the new branch + sha: sha, // SHA of the base branch (main) }), }); const newBranchData = await newBranchResponse.json(); - if ( newBranchResponse.ok ) - { + if (newBranchResponse.ok) { console.log('New branch created successfully: ', newBranchData); return true; } - else - { + else { console.error('Error creating new branch: ', newBranchData); alert("Failed to create branch on project! Error code: " + newBranchResponse.status + ". Please check API Key permissions and try again.") return false; } } - else - { + else { console.error('Error fetching base branch info:', data); alert('Error fetching base branch info:', data); return false; @@ -172,16 +167,15 @@ async function createBranchOnProject(projectURL, token) } -async function addFileToBranch(projectURL, token, codeJSONObj) -{ - const {owner, repo} = getOrgAndRepoArgsGitHub(projectURL); +async function addFileToBranch(projectURL, token, codeJSONObj) { + const { owner, repo } = getOrgAndRepoArgsGitHub(projectURL); const FILE_PATH = 'code.json' const createFileApiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/${FILE_PATH}`; const encodedContent = btoa(codeJSONObj); console.log("Content: ", encodedContent); console.log("Branch: ", NEW_BRANCH); - const response = await fetch(createFileApiUrl, + const response = await fetch(createFileApiUrl, { method: 'PUT', headers: { @@ -203,24 +197,21 @@ async function addFileToBranch(projectURL, token, codeJSONObj) const data = await response.json() - if ( response.ok ) - { + if (response.ok) { console.log('File added successfully: ', data); return true; } - else - { + else { console.error('Error adding file: ', data); alert("Failed to add file on project! Error code: " + response.status + ". Please check API Key permissions and try again.") return false; } } -async function createPR(projectURL, token) -{ - const {owner, repo} = getOrgAndRepoArgsGitHub(projectURL); +async function createPR(projectURL, token) { + const { owner, repo } = getOrgAndRepoArgsGitHub(projectURL); const createPrApiUrl = `https://api.github.com/repos/${owner}/${repo}/pulls`; - const response = await fetch(createPrApiUrl, + const response = await fetch(createPrApiUrl, { method: 'POST', headers: { @@ -240,13 +231,11 @@ async function createPR(projectURL, token) const data = await response.json(); - if (response.ok) - { + if (response.ok) { console.log('Pull request created successfully: ', data); return true; } - else - { + else { console.error("Error creating PR!: ", data); alert("Failed to create PR on project! Error code: " + response.status + ". Please check API Key permissions and try again.") return false; @@ -254,50 +243,42 @@ async function createPR(projectURL, token) } // Creates PR on requested project -async function createProjectPR(event){ +async function createProjectPR(event) { event.preventDefault(); var textArea = document.getElementById("json-result");//Step 1 var codeJSONObj = JSON.parse(textArea.value) - - if('gh_api_key' in window) - { + + if ('gh_api_key' in window) { var apiKey = window.gh_api_key; - - if ('repositoryURL' in codeJSONObj) - { + + if ('repositoryURL' in codeJSONObj) { var prCreated = false; //Step 1 - const branchCreated = await createBranchOnProject(codeJSONObj.repositoryURL,apiKey); - if (branchCreated) - { + const branchCreated = await createBranchOnProject(codeJSONObj.repositoryURL, apiKey); + if (branchCreated) { const fileAdded = await addFileToBranch(codeJSONObj.repositoryURL, apiKey, textArea.value); - if (fileAdded) - { + if (fileAdded) { prCreated = await createPR(codeJSONObj.repositoryURL, apiKey); - if(prCreated) - { + if (prCreated) { console.log("PR successfully created!"); alert("PR has been created!"); } } } - else - { + else { console.error("Could not create branch on requested repository with the requested API key!"); alert("Could not create branch on requested repository with the requested API key!"); } } - else - { + else { console.error("No URL found!"); alert("No URL given for project! Please provide project URL in repositoryURL text box"); } - + } - else - { + else { console.error("No API key found!"); alert("No API Key in submitted data! Please provide an API key"); } @@ -328,27 +309,27 @@ async function emailFile(event) { const codeJson = document.getElementById("json-result").value const jsonObject = JSON.parse(codeJson); - - try { - const cleanData = {...jsonObject}; - delete cleanData.submit; - const jsonString = JSON.stringify(cleanData, null, 2); + try { + const cleanData = { ...jsonObject }; + delete cleanData.submit; + + const jsonString = JSON.stringify(cleanData, null, 2); - const subject = "Code.json generator Results"; - const body = `Hello,\n\nHere are the code.json results:\n\n${jsonString}\n\nThank you!`; + const subject = "Code.json generator Results"; + const body = `Hello,\n\nHere are the code.json results:\n\n${jsonString}\n\nThank you!`; - const recipients = ["opensource@cms.hhs.gov"]; + const recipients = ["opensource@cms.hhs.gov"]; - const mailtoLink = `mailto:${recipients}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; + const mailtoLink = `mailto:${recipients}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`; - window.location.href = mailtoLink; + window.location.href = mailtoLink; - console.log("Email client opened"); - } catch { - console.error("Error preparing email:", error); - showNotificationModal("Error preparing email. Please try again or copy the data manually.", 'error'); - } + console.log("Email client opened"); + } catch { + console.error("Error preparing email:", error); + showNotificationModal("Error preparing email. Please try again or copy the data manually.", 'error'); + } } window.createCodeJson = createCodeJson; diff --git a/js/generateFormComponents.js b/js/generateFormComponents.js index f708615..1f8628d 100644 --- a/js/generateFormComponents.js +++ b/js/generateFormComponents.js @@ -23,7 +23,7 @@ function transformArrayToOptions(arr) { } // Function that handles validation object needed for each form component -function determineValidation(fieldName, fieldObject, requiredArray){ +function determineValidation(fieldName, fieldObject, requiredArray) { return { "required": requiredArray.includes(fieldName) } @@ -207,7 +207,7 @@ function createComponent(fieldName, fieldObject, requiredArray) { description: fieldObject["description"], validate }; - case "container": + case "container": return { label: fieldName, hideLabel: false, @@ -238,7 +238,7 @@ function createComponent(fieldName, fieldObject, requiredArray) { input: true, components: [], validate - }; + }; default: break; } @@ -256,20 +256,20 @@ function createAutoGenerationBox() { } // Iterates through each json field and creates component array for Form.io -function createAllComponents(schema, prefix = ""){ +function createAllComponents(schema, prefix = "") { let components = []; if (schema.type === "object" && schema.properties) { const items = schema.properties.hasOwnProperty("items") ? schema.properties.items : schema.properties; - + let requiredArray = []; if (schema.hasOwnProperty("required")) { requiredArray = schema.required; } - for (const [key, value] of Object.entries(items)) { - + for (const [key, value] of Object.entries(items)) { + console.log("key at play:", key); const fullKey = prefix ? `${prefix}.${key}` : key; @@ -277,23 +277,26 @@ function createAllComponents(schema, prefix = ""){ if (fieldComponent.type === "container") { fieldComponent.components = createAllComponents(value, fullKey); - } + } else if (fieldComponent.type === "datagrid") { fieldComponent.components = createAllComponents(value.items, fullKey); } components.push(fieldComponent); - } - } + } + } - return components; + return components; } // Creates complete form based on input json schema async function createFormComponents() { let components = []; - const filePath = "schemas/schema.json"; + // Fetching schema based on search params + const params = new URLSearchParams(window.location.search); + const page = params.get("page") || "gov"; + const filePath = `schemas/${page}/schema.json`; const jsonData = await retrieveFile(filePath); console.log("JSON Data:", jsonData); @@ -326,7 +329,7 @@ async function createFormComponents() { tableView: false, }); - + console.log(components); diff --git a/schemas/schema.json b/schemas/cms/schema.json similarity index 95% rename from schemas/schema.json rename to schemas/cms/schema.json index 989175a..239abe2 100644 --- a/schemas/schema.json +++ b/schemas/cms/schema.json @@ -23,13 +23,13 @@ "status": { "type": "string", "enum": [ - "Ideation", - "Development", - "Alpha", - "Beta", - "Release Candidate", - "Production", - "Archival" + "ideation", + "development", + "alpha", + "beta", + "releaseCandidate", + "production", + "archival" ], "description": "Development status of the project" }, @@ -114,7 +114,7 @@ "repositoryURL": { "type": "string", "format": "uri", - "description": "The URL of the public release repository for open source repositories. This field is not required for repositories that are only available as government-wide reuse or are closed (pursuant to one of the exemptions)." + "description": "The URL of the public release repository for open source repositories. This field is not required for repositories that are only available as government-wide reuse or are closed (pursuant to one of the exemptions). It can be listed as 'private' for repositories that are closed." }, "projectURL": { "type": "string", @@ -304,9 +304,9 @@ "type": "string", "description": "Level of security categorization assigned to an information system under the Federal Information Security Modernization Act (FISMA): https://security.cms.gov/learn/federal-information-security-modernization-act-fisma", "enum": [ - "Low", - "Moderate", - "High" + "low", + "moderate", + "high" ] }, "group": { @@ -336,10 +336,10 @@ "items": { "type": "string", "enum": [ - "Policy", - "Operational", - "Medicare", - "Medicaid" + "policy", + "operational", + "medicare", + "medicaid" ] }, "description": "Healthcare-related subset" @@ -349,9 +349,9 @@ "items": { "type": "string", "enum": [ - "Providers", - "Patients", - "Government" + "providers", + "patients", + "government" ] }, "description": "Types of users who interact with the software" @@ -403,4 +403,4 @@ "maturityModelTier" ], "additionalProperties": false -} +} \ No newline at end of file diff --git a/schemas/gov/schema.json b/schemas/gov/schema.json new file mode 100644 index 0000000..8a4b5c4 --- /dev/null +++ b/schemas/gov/schema.json @@ -0,0 +1,295 @@ +{ + "$schema": "http://json-schema.org/draft-04/schema#", + "title": "code.json metadata", + "description": "A metadata standard for software repositories", + "type": "object", + "properties": { + "items": { + "name": { + "type": "string", + "description": "Name of the project or software" + }, + "description": { + "type": "string", + "description": "A short description of the project. It should be a single line containing a single sentence. Maximum 150 characters are allowed.", + "maxLength": 150 + }, + "longDescription": { + "type": "string", + "description": "Provide longer description of the software, between 150 and 10000 chars. It is meant to provide an overview of the capabilities of the software for a potential user.", + "minLength": 150, + "maxLength": 10000 + }, + "status": { + "type": "string", + "enum": [ + "ideation", + "development", + "alpha", + "beta", + "releaseCandidate", + "production", + "archival" + ], + "description": "Development status of the project" + }, + "permissions": { + "type": "object", + "description": "An object containing description of the usage/restrictions regarding the release", + "properties": { + "licenses": { + "type": "array", + "description": "License(s) for the release", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "enum": [ + "CC0-1.0", + "Apache-2.0", + "MIT", + "MPL-2.0", + "GPL-2.0-only", + "GPL-3.0-only", + "GPL-3.0-or-later", + "LGPL-2.1-only", + "LGPL-3.0-only", + "BSD-2-Clause", + "BSD-3-Clause", + "EPL-2.0", + "Other", + "None" + ], + "description": "An abbreviation for the name of the license" + }, + "URL": { + "type": "string", + "format": "uri", + "description": "The URL of the release license in the repository" + } + }, + "required": [ + "name", + "URL" + ] + } + }, + "usageType": { + "type": "string", + "description": "A list of enumerated values which describes the usage permissions for the release: (1) openSource: Open source; (2) governmentWideReuse: Government-wide reuse; (3) exemptByLaw: The sharing of the source code is restricted by law or regulation, including—but not limited to—patent or intellectual property law, the Export Asset Regulations, the International Traffic in Arms Regulation, and the Federal laws and regulations governing classified information; (4) exemptByNationalSecurity: The sharing of the source code would create an identifiable risk to the detriment of national security, confidentiality of Government information, or individual privacy; (5) exemptByAgencySystem: The sharing of the source code would create an identifiable risk to the stability, security, or integrity of the agency’s systems or personnel, (6) exemptByAgencyMission: The sharing of the source code would create an identifiable risk to agency mission, programs, or operations; (7) exemptByCIO: The CIO believes it is in the national interest to exempt sharing the source code; (8) exemptByPolicyDate: The release was created prior to the M-16-21 policy (August 8, 2016)", + "enum": [ + "openSource", + "governmentWideReuse", + "exemptByLaw", + "exemptByNationalSecurity", + "exemptByAgencySystem", + "exemptByAgencyMission", + "exemptByCIO", + "exemptByPolicyDate" + ], + "additionalProperties": false + }, + "exemptionText": { + "type": [ + "string", + "null" + ], + "description": "If an exemption is listed in the 'usageType' field, this field should include a one- or two- sentence justification for the exemption used." + } + }, + "additionalProperties": false, + "required": [ + "licenses", + "usageType" + ] + }, + "organization": { + "type": "string", + "description": "Organization responsible for the project", + "enum": [ + "Centers for Medicare & Medicaid Services" + ] + }, + "repositoryURL": { + "type": "string", + "format": "uri", + "description": "The URL of the public release repository for open source repositories. This field is not required for repositories that are only available as government-wide reuse or are closed (pursuant to one of the exemptions). It can be listed as 'private' for repositories that are closed." + }, + "repositoryVisibility": { + "type": "string", + "enum": [ + "public", + "private" + ], + "description": "Visibility of repository" + }, + "vcs": { + "type": "string", + "description": "Version control system used", + "enum": [ + "git", + "hg", + "svn", + "rcs", + "bzr" + ] + }, + "laborHours": { + "type": "number", + "description": "Labor hours invested in the project. Calculated using COCOMO measured by the SCC tool: https://github.com/boyter/scc?tab=readme-ov-file#cocomo" + }, + "reuseFrequency": { + "type": "object", + "description": "Measures frequency of code reuse in various forms. (e.g. forks, downloads, clones)", + "properties": { + "forks": { + "type": "integer" + }, + "clones": { + "type": "integer" + } + }, + "additionalProperties": true + }, + "platforms": { + "type": "array", + "description": "Platforms supported by the project", + "items": { + "type": "string", + "enum": [ + "web", + "windows", + "mac", + "linux", + "ios", + "android", + "other" + ] + } + }, + "categories": { + "type": "array", + "description": "Categories the project belongs to. Select from: https://yml.publiccode.tools/categories-list.html", + "items": { + "type": "string" + } + }, + "softwareType": { + "type": "string", + "description": "Type of software", + "enum": [ + "standalone/mobile", + "standalone/iot", + "standalone/desktop", + "standalone/web", + "standalone/backend", + "standalone/other", + "addon", + "library", + "configurationFiles" + ] + }, + "languages": { + "type": "array", + "description": "Programming languages that make up the codebase", + "items": { + "type": "string" + } + }, + "maintenance": { + "type": "string", + "description": "The dedicated staff that keeps the software up-to-date, if any", + "enum": [ + "internal", + "contract", + "community", + "none" + ] + }, + "contractNumber": { + "type": "string", + "description": "Contract number" + }, + "date": { + "type": "object", + "description": "A date object describing the release", + "properties": { + "created": { + "type": "string", + "format": "date-time", + "description": "Creation date of project." + }, + "lastModified": { + "type": "string", + "format": "date-time", + "description": "Date when the project was last modified" + }, + "metaDataLastUpdated": { + "type": "string", + "format": "date-time", + "description": "Date when metadata was last updated" + } + } + }, + "tags": { + "type": "array", + "description": "Topics and keywords associated with the project to improve search and discoverability", + "items": { + "type": "string" + } + }, + "contact": { + "type": "object", + "description": "Point of contact for the release", + "properties": { + "email": { + "type": "string", + "format": "email", + "description": "Email address of the point of contact" + }, + "name": { + "type": "string", + "description": "Name of the point of contact" + } + } + }, + "feedbackMechanisms": { + "type": "array", + "description": "Methods a repository receives feedback from the community. Default value is the URL to GitHub repository issues page.", + "items": { + "type": "string" + } + }, + "localisation": { + "type": "boolean", + "description": "Indicates if the project supports multiple languages" + } + } + }, + "required": [ + "name", + "description", + "longDescription", + "status", + "permissions", + "organization", + "repositoryURL", + "repositoryVisibility", + "vcs", + "laborHours", + "reuseFrequency", + "platforms", + "categories", + "softwareType", + "languages", + "maintenance", + "contractNumber", + "date", + "tags", + "contact", + "feedbackMechanisms", + "localisation" + ], + "additionalProperties": false +} \ No newline at end of file