From 3432ced1697c9dbdc775d60823b064797afe078f Mon Sep 17 00:00:00 2001 From: avivkeller Date: Thu, 18 Sep 2025 09:44:55 -0400 Subject: [PATCH 1/2] fix(search): use advanced search --- create-node-meeting-artifacts.mjs | 5 +---- package-lock.json | 2 +- src/constants.mjs | 3 +++ src/github.mjs | 36 +++++++++++-------------------- src/meeting.mjs | 13 ++++------- 5 files changed, 22 insertions(+), 37 deletions(-) diff --git a/create-node-meeting-artifacts.mjs b/create-node-meeting-artifacts.mjs index c88d956..acdc427 100644 --- a/create-node-meeting-artifacts.mjs +++ b/create-node-meeting-artifacts.mjs @@ -51,10 +51,7 @@ const gitHubAgendaIssues = await github.getAgendaIssues( ); // Step 10: Parse meeting agenda from GitHub issues -const meetingAgenda = meetings.generateMeetingAgenda( - gitHubAgendaIssues, - meetingConfig -); +const meetingAgenda = meetings.generateMeetingAgenda(gitHubAgendaIssues); // Step 11: Create HackMD document with meeting notes const hackmdNote = await hackmd.createMeetingNotesDocument( diff --git a/package-lock.json b/package-lock.json index 3fc3ab9..e9ef128 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,7 +15,7 @@ "dedent": "^1.6.0" }, "bin": { - "create-meeting": "bin/create-meeting" + "create-meeting": "create-node-meeting-artifacts.mjs" }, "devDependencies": { "@eslint/js": "^9.33.0", diff --git a/src/constants.mjs b/src/constants.mjs index 897a892..2f9cd4a 100644 --- a/src/constants.mjs +++ b/src/constants.mjs @@ -34,3 +34,6 @@ export const HACKMD_DEFAULT_PERMISSIONS = { writePermission: 'signed_in', commentPermission: 'signed_in_users', }; + +export const REPOSITORY_URL_PREFIX_LENGTH = 'https://api.github.com/repos/' + .length; diff --git a/src/github.mjs b/src/github.mjs index b075c5d..e2c4507 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; -import { DEFAULT_CONFIG } from './constants.mjs'; +import { DEFAULT_CONFIG, REPOSITORY_URL_PREFIX_LENGTH } from './constants.mjs'; /** * Creates a GitHub API client @@ -47,36 +47,26 @@ export const createGitHubIssue = async ( * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client * @param {import('./types.d.ts').AppConfig} config - Application configuration * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration - * @returns {Promise<{ repoName: string, issues: Array }> } Formatted markdown string of issues + * @returns {Promise<{ [key: string]: Array }>} Formatted markdown string of issues */ export const getAgendaIssues = async ( - { paginate, rest }, + githubClient, { meetingGroup }, { properties } ) => { const githubOrg = properties.USER ?? DEFAULT_CONFIG.githubOrg; const agendaTag = properties.AGENDA_TAG ?? `${meetingGroup}-agenda`; - // Get all public repositories in the organization - const repos = await paginate(rest.repos.listForOrg, { - org: githubOrg, - type: 'public', - per_page: 100, + // Get all issues/PRs in the organization + const issues = await githubClient.paginate('GET /search/issues', { + q: `label:${agendaTag} org:${githubOrg}`, + advanced_search: true, }); - // Fetch issues and PRs from all repositories concurrently - const issuePromises = repos.map(async repo => { - const items = await paginate(rest.issues.listForRepo, { - owner: githubOrg, - repo: repo.name, - labels: agendaTag, - state: 'open', - per_page: 100, - }); - - // Include both issues and PRs for agenda items - return { repoName: repo.name, issues: items }; - }); - - return Promise.all(issuePromises); + return issues.reduce((obj, issue) => { + (obj[issue.repository_url.slice(REPOSITORY_URL_PREFIX_LENGTH)] ||= []).push( + issue + ); + return obj; + }, {}); }; diff --git a/src/meeting.mjs b/src/meeting.mjs index 60a401f..c56f042 100644 --- a/src/meeting.mjs +++ b/src/meeting.mjs @@ -56,21 +56,16 @@ export const generateMeetingTitle = (config, meetingConfig, meetingDate) => { /** * Generates the meeting agenda from the list of agenda issues - * @param {Array<{ repoName: string, issues: Array }>} agendaIssues - List of agenda issues - * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration + * @param {Array<{ [key: string]: Array }>} agendaIssues - List of agenda issues * @returns {Promise} Formatted meeting agenda */ -export const generateMeetingAgenda = (agendaIssues, meetingConfig) => { - const props = meetingConfig.properties; - - const githubOrg = props.USER ?? DEFAULT_CONFIG.githubOrg; - +export const generateMeetingAgenda = agendaIssues => { // Format issues as markdown let agendaMarkdown = ''; - agendaIssues.forEach(({ repoName, issues }) => { + Object.entries(agendaIssues).forEach(([repoName, issues]) => { if (issues.length > 0) { - agendaMarkdown += `### ${githubOrg}/${repoName}\n\n`; + agendaMarkdown += `### ${repoName}\n\n`; issues.forEach(issue => { // Escape markdown characters in title From 50b9e0076d7c8a8efcb82d2d8ba9347ae88b2aef Mon Sep 17 00:00:00 2001 From: avivkeller Date: Sat, 27 Sep 2025 14:46:40 -0400 Subject: [PATCH 2/2] use helper --- src/constants.mjs | 3 --- src/github.mjs | 26 +++++++++++++++++--------- 2 files changed, 17 insertions(+), 12 deletions(-) diff --git a/src/constants.mjs b/src/constants.mjs index 2f9cd4a..897a892 100644 --- a/src/constants.mjs +++ b/src/constants.mjs @@ -34,6 +34,3 @@ export const HACKMD_DEFAULT_PERMISSIONS = { writePermission: 'signed_in', commentPermission: 'signed_in_users', }; - -export const REPOSITORY_URL_PREFIX_LENGTH = 'https://api.github.com/repos/' - .length; diff --git a/src/github.mjs b/src/github.mjs index e2c4507..ec828b9 100644 --- a/src/github.mjs +++ b/src/github.mjs @@ -1,6 +1,6 @@ import { Octokit } from '@octokit/rest'; -import { DEFAULT_CONFIG, REPOSITORY_URL_PREFIX_LENGTH } from './constants.mjs'; +import { DEFAULT_CONFIG } from './constants.mjs'; /** * Creates a GitHub API client @@ -42,12 +42,25 @@ export const createGitHubIssue = async ( return response.data; }; +/** + * Sorts issues by repository + * @param {Array} issues The issues to sort + * @returns {Promise<{ [key: string]: Array }>} Sorted issues + */ +export const sortIssuesByRepo = issues => + issues.reduce((obj, issue) => { + (obj[issue.repository_url.split('/').slice(-2).join('/')] ||= []).push( + issue + ); + return obj; + }, {}); + /** * Fetches GitHub issues from all repositories in an organization with a specific label * @param {import('@octokit/rest').Octokit} githubClient - Authenticated GitHub API client * @param {import('./types.d.ts').AppConfig} config - Application configuration * @param {import('./types.d.ts').MeetingConfig} meetingConfig - Meeting configuration - * @returns {Promise<{ [key: string]: Array }>} Formatted markdown string of issues + * @returns {Promise<{ [key: string]: Array }>} Meeting agenda */ export const getAgendaIssues = async ( githubClient, @@ -59,14 +72,9 @@ export const getAgendaIssues = async ( // Get all issues/PRs in the organization const issues = await githubClient.paginate('GET /search/issues', { - q: `label:${agendaTag} org:${githubOrg}`, + q: `is:open label:${agendaTag} org:${githubOrg}`, advanced_search: true, }); - return issues.reduce((obj, issue) => { - (obj[issue.repository_url.slice(REPOSITORY_URL_PREFIX_LENGTH)] ||= []).push( - issue - ); - return obj; - }, {}); + return sortIssuesByRepo(issues); };