Skip to content
Open
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
154 changes: 153 additions & 1 deletion functions/packages/feed-form/src/impl/feed-form-impl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,7 +327,17 @@ async function createGithubIssue(
if (formData.country && formData.country in countries) {
const country = countries[formData.country as TCountryCode];
const continent = continents[country.continent].toLowerCase();
if (continent != null) labels.push(continent);
if (continent != null) labels.push(`region/${continent}`);
}

if (formData.authType !== "None - 0") {
labels.push("auth required");
}

if (!isValidZipUrl(formData.feedLink)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For GTFS feeds, the expectation is that the resource's content is a ZIP file. However, many URLs don't have a zip suffix. I would remove this condition in favor of data_type == GTFS and not authentication required (since the key will not be available when we check the resource).

if (!await isValidZipDownload(formData.feedLink)) {
labels.push("invalid");
}
}

try {
Expand All @@ -345,6 +355,19 @@ async function createGithubIssue(
},
}
);

const issueNodeId = response.data.node_id;
const projectId = "PVT_kwDOAnHxDs4Ayxl6";
const statusFieldId = "PVTSSF_lADOAnHxDs4Ayxl6zgorIUI";
const backlogOptionId = "8e14ac56";
Comment on lines +360 to +362
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These hardcoded variables should be hidden as ENV variables ex

const sheetId = process.env.FEED_SUBMIT_GOOGLE_SHEET_ID;

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, we could also use 1Password to store them, there's an SDK for Javascript.

await addIssueToProjectV2(
issueNodeId,
githubToken,
projectId,
statusFieldId,
backlogOptionId
);

return response.data.html_url;
} catch (error) {
logger.error("Error creating GitHub issue:", error);
Expand Down Expand Up @@ -441,3 +464,132 @@ export function buildGithubIssueBody(
return content;
}
/* eslint-enable */

/**
* Parses the provided URL to check if it is a valid ZIP file URL
* @param {string | undefined | null } url The direct download URL
* @return {boolean} Whether the URL is a valid ZIP file URL
*/
function isValidZipUrl(url: string | undefined | null): boolean {
if (!url) return false;
try {
const parsed = new URL(url);
return parsed.pathname.toLowerCase().endsWith(".zip");
} catch {
return false;
}
}

/**
* Checks if URL points to a valid ZIP file by making HEAD request
* @param {string | undefined | null } url The download URL
* @return {boolean} Whether the URL downloads a valid ZIP file
*/
async function isValidZipDownload(
url: string | undefined | null
): Promise<boolean> {
try {
if (!url) return false;
const response = await axios.head(url, {maxRedirects: 2});
const contentType = response.headers["content-type"];
const contentDisposition = response.headers["content-disposition"];

if (contentType && contentType.includes("zip")) return true;
if (contentDisposition && contentDisposition.includes("zip")) return true;
return false;
} catch {
return false;
}
}
Comment on lines +468 to +503
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this file is getting large, bring these functions out into a helper file ex functions/packages/feed-form/src/impl/utils.ts and writing unit tests for them would be good


/**
* Adds a GitHub issue to a project with a specific status
* @param {string} issueNodeId The ID of the created issue
* @param {string} githubToken GitHub token
* @param {string} projectId The ID of the project
* @param {string} statusFieldId The ID of the Status field
* @param {string} statusOptionId The ID of the status option
*/
async function addIssueToProjectV2(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same idea with this function, bring it outside

issueNodeId: string,
githubToken: string,
projectId: string,
statusFieldId: string,
statusOptionId: string
) {
try {
const addToProjectMutation = `
mutation($projectId: ID!, $contentId: ID!) {
addProjectV2ItemById(
input: {projectId: $projectId, contentId: $contentId}
) {
item { id }
}
}
`;

const addToProjectResponse = await axios.post(
"https://api.github.com/graphql",
{
query: addToProjectMutation,
variables: {
projectId,
contentId: issueNodeId,
},
},
{
headers: {
Authorization: `bearer ${githubToken}`,
Accept: "application/vnd.github.v3+json",
},
}
);

const itemId = addToProjectResponse.data.data.addProjectV2ItemById.item.id;

const updateStatusMutation = `
mutation(
$projectId: ID!
$itemId: ID!
$fieldId: ID!
$value: ProjectV2FieldValue!
) {
updateProjectV2ItemFieldValue(
input: {
projectId: $projectId
itemId: $itemId
fieldId: $fieldId
value: $value
}
) {
projectV2Item { id }
}
}
`;

await axios.post(
"https://api.github.com/graphql",
{
query: updateStatusMutation,
variables: {
projectId,
itemId,
fieldId: statusFieldId,
value: {
singleSelectOptionId: statusOptionId,
},
},
},
{
headers: {
Authorization: `bearer ${githubToken}`,
Accept: "application/vnd.github.v3+json",
},
}
);

logger.info("Successfully added issue to Feed Submissions Backlog");
} catch (error) {
logger.error("Error adding issue to Feed Submissions Backlog:", error);
}
}
Loading