1*61c4878aSAndroid Build Coastguard Worker// Copyright 2023 The Pigweed Authors 2*61c4878aSAndroid Build Coastguard Worker// 3*61c4878aSAndroid Build Coastguard Worker// Licensed under the Apache License, Version 2.0 (the "License"); you may not 4*61c4878aSAndroid Build Coastguard Worker// use this file except in compliance with the License. You may obtain a copy of 5*61c4878aSAndroid Build Coastguard Worker// the License at 6*61c4878aSAndroid Build Coastguard Worker// 7*61c4878aSAndroid Build Coastguard Worker// https://www.apache.org/licenses/LICENSE-2.0 8*61c4878aSAndroid Build Coastguard Worker// 9*61c4878aSAndroid Build Coastguard Worker// Unless required by applicable law or agreed to in writing, software 10*61c4878aSAndroid Build Coastguard Worker// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11*61c4878aSAndroid Build Coastguard Worker// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12*61c4878aSAndroid Build Coastguard Worker// License for the specific language governing permissions and limitations under 13*61c4878aSAndroid Build Coastguard Worker// the License. 14*61c4878aSAndroid Build Coastguard Worker 15*61c4878aSAndroid Build Coastguard Worker// This file powers the changelog tool in //docs/contributing/changelog.rst. 16*61c4878aSAndroid Build Coastguard Worker// We use this tool to speed up the generation of bi-weekly changelog 17*61c4878aSAndroid Build Coastguard Worker// updates. It fetches the commits over a user-specified timeframe, derives 18*61c4878aSAndroid Build Coastguard Worker// a little metadata about each commit, organizes the commits, and renders 19*61c4878aSAndroid Build Coastguard Worker// the data as reStructuredText. It doesn't completely automate the changelog 20*61c4878aSAndroid Build Coastguard Worker// update process (a contributor still needs to manually write the summaries) 21*61c4878aSAndroid Build Coastguard Worker// but it does reduce a lot of the toil. 22*61c4878aSAndroid Build Coastguard Worker 23*61c4878aSAndroid Build Coastguard Worker// Get the commits from the user-specified timeframe. 24*61c4878aSAndroid Build Coastguard Workerasync function get() { 25*61c4878aSAndroid Build Coastguard Worker const start = `${document.querySelector('#start').value}T00:00:00Z`; 26*61c4878aSAndroid Build Coastguard Worker const end = `${document.querySelector('#end').value}T23:59:59Z`; 27*61c4878aSAndroid Build Coastguard Worker document.querySelector('#status').textContent = `Getting commit data...`; 28*61c4878aSAndroid Build Coastguard Worker let page = 1; 29*61c4878aSAndroid Build Coastguard Worker let done = false; 30*61c4878aSAndroid Build Coastguard Worker let commits = []; 31*61c4878aSAndroid Build Coastguard Worker while (!done) { 32*61c4878aSAndroid Build Coastguard Worker // The commits are pulled from the Pigweed mirror on GitHub because 33*61c4878aSAndroid Build Coastguard Worker // GitHub provides a better API than Gerrit for this task. 34*61c4878aSAndroid Build Coastguard Worker let url = new URL(`https://api.github.com/repos/google/pigweed/commits`); 35*61c4878aSAndroid Build Coastguard Worker const params = { since: start, until: end, per_page: 100, page }; 36*61c4878aSAndroid Build Coastguard Worker Object.keys(params).forEach((key) => 37*61c4878aSAndroid Build Coastguard Worker url.searchParams.append(key, params[key]), 38*61c4878aSAndroid Build Coastguard Worker ); 39*61c4878aSAndroid Build Coastguard Worker const headers = { 40*61c4878aSAndroid Build Coastguard Worker Accept: 'application/vnd.github+json', 41*61c4878aSAndroid Build Coastguard Worker 'X-GitHub-Api-Version': '2022-11-28', 42*61c4878aSAndroid Build Coastguard Worker }; 43*61c4878aSAndroid Build Coastguard Worker const response = await fetch(url.href, { method: 'GET', headers }); 44*61c4878aSAndroid Build Coastguard Worker if (!response.ok) { 45*61c4878aSAndroid Build Coastguard Worker document.querySelector('#status').textContent = 46*61c4878aSAndroid Build Coastguard Worker 'An error occurred while fetching the commit data.'; 47*61c4878aSAndroid Build Coastguard Worker console.error(response); 48*61c4878aSAndroid Build Coastguard Worker return; 49*61c4878aSAndroid Build Coastguard Worker } 50*61c4878aSAndroid Build Coastguard Worker const data = await response.json(); 51*61c4878aSAndroid Build Coastguard Worker if (data.length === 0) { 52*61c4878aSAndroid Build Coastguard Worker done = true; 53*61c4878aSAndroid Build Coastguard Worker continue; 54*61c4878aSAndroid Build Coastguard Worker } 55*61c4878aSAndroid Build Coastguard Worker commits = commits.concat(data); 56*61c4878aSAndroid Build Coastguard Worker page += 1; 57*61c4878aSAndroid Build Coastguard Worker } 58*61c4878aSAndroid Build Coastguard Worker return commits; 59*61c4878aSAndroid Build Coastguard Worker} 60*61c4878aSAndroid Build Coastguard Worker 61*61c4878aSAndroid Build Coastguard Worker// Weed out all the data that GitHub provides that we don't need. 62*61c4878aSAndroid Build Coastguard Worker// Also, parse the "subject" of the commit, which is the first line 63*61c4878aSAndroid Build Coastguard Worker// of the commit message. 64*61c4878aSAndroid Build Coastguard Workerasync function normalize(commits) { 65*61c4878aSAndroid Build Coastguard Worker function parseSubject(message) { 66*61c4878aSAndroid Build Coastguard Worker const end = message.indexOf('\n\n'); 67*61c4878aSAndroid Build Coastguard Worker return message.substring(0, end); 68*61c4878aSAndroid Build Coastguard Worker } 69*61c4878aSAndroid Build Coastguard Worker 70*61c4878aSAndroid Build Coastguard Worker document.querySelector('#status').textContent = 'Normalizing data...'; 71*61c4878aSAndroid Build Coastguard Worker let normalizedCommits = []; 72*61c4878aSAndroid Build Coastguard Worker commits.forEach((commit) => { 73*61c4878aSAndroid Build Coastguard Worker normalizedCommits.push({ 74*61c4878aSAndroid Build Coastguard Worker sha: commit.sha, 75*61c4878aSAndroid Build Coastguard Worker message: commit.commit.message, 76*61c4878aSAndroid Build Coastguard Worker date: commit.commit.committer.date, 77*61c4878aSAndroid Build Coastguard Worker subject: parseSubject(commit.commit.message), 78*61c4878aSAndroid Build Coastguard Worker }); 79*61c4878aSAndroid Build Coastguard Worker }); 80*61c4878aSAndroid Build Coastguard Worker return normalizedCommits; 81*61c4878aSAndroid Build Coastguard Worker} 82*61c4878aSAndroid Build Coastguard Worker 83*61c4878aSAndroid Build Coastguard Worker// Derive Pigweed-specific metadata from each commit. 84*61c4878aSAndroid Build Coastguard Workerasync function annotate(commits) { 85*61c4878aSAndroid Build Coastguard Worker function categorize(preamble) { 86*61c4878aSAndroid Build Coastguard Worker if (preamble.startsWith('third_party')) { 87*61c4878aSAndroid Build Coastguard Worker return 'Third party'; 88*61c4878aSAndroid Build Coastguard Worker } else if (preamble.startsWith('pw_')) { 89*61c4878aSAndroid Build Coastguard Worker return 'Modules'; 90*61c4878aSAndroid Build Coastguard Worker } else if (preamble.startsWith('targets')) { 91*61c4878aSAndroid Build Coastguard Worker return 'Targets'; 92*61c4878aSAndroid Build Coastguard Worker } else if (['build', 'bazel', 'cmake'].includes(preamble)) { 93*61c4878aSAndroid Build Coastguard Worker return 'Build'; 94*61c4878aSAndroid Build Coastguard Worker } else if (['rust', 'python'].includes(preamble)) { 95*61c4878aSAndroid Build Coastguard Worker return 'Language support'; 96*61c4878aSAndroid Build Coastguard Worker } else if (['zephyr', 'freertos'].includes(preamble)) { 97*61c4878aSAndroid Build Coastguard Worker return 'OS support'; 98*61c4878aSAndroid Build Coastguard Worker } else if (preamble.startsWith('SEED')) { 99*61c4878aSAndroid Build Coastguard Worker return 'SEEDs'; 100*61c4878aSAndroid Build Coastguard Worker } else if (preamble === 'docs') { 101*61c4878aSAndroid Build Coastguard Worker return 'Docs'; 102*61c4878aSAndroid Build Coastguard Worker } else { 103*61c4878aSAndroid Build Coastguard Worker return 'Miscellaneous'; 104*61c4878aSAndroid Build Coastguard Worker } 105*61c4878aSAndroid Build Coastguard Worker } 106*61c4878aSAndroid Build Coastguard Worker 107*61c4878aSAndroid Build Coastguard Worker function parseTitle(message) { 108*61c4878aSAndroid Build Coastguard Worker const start = message.indexOf(':') + 1; 109*61c4878aSAndroid Build Coastguard Worker const tmp = message.substring(start); 110*61c4878aSAndroid Build Coastguard Worker const end = tmp.indexOf('\n'); 111*61c4878aSAndroid Build Coastguard Worker return tmp.substring(0, end).trim(); 112*61c4878aSAndroid Build Coastguard Worker } 113*61c4878aSAndroid Build Coastguard Worker 114*61c4878aSAndroid Build Coastguard Worker function parseBugUrl(message, bugLabel) { 115*61c4878aSAndroid Build Coastguard Worker const start = message.indexOf(bugLabel); 116*61c4878aSAndroid Build Coastguard Worker const tmp = message.substring(start); 117*61c4878aSAndroid Build Coastguard Worker const end = tmp.indexOf('\n'); 118*61c4878aSAndroid Build Coastguard Worker let bug = tmp.substring(bugLabel.length, end).trim(); 119*61c4878aSAndroid Build Coastguard Worker if (bug.startsWith('b/')) bug = bug.replace('b/', ''); 120*61c4878aSAndroid Build Coastguard Worker return `https://issues.pigweed.dev/issues/${bug}`; 121*61c4878aSAndroid Build Coastguard Worker } 122*61c4878aSAndroid Build Coastguard Worker 123*61c4878aSAndroid Build Coastguard Worker function parseChangeUrl(message) { 124*61c4878aSAndroid Build Coastguard Worker const label = 'Reviewed-on:'; 125*61c4878aSAndroid Build Coastguard Worker const start = message.indexOf(label); 126*61c4878aSAndroid Build Coastguard Worker const tmp = message.substring(start); 127*61c4878aSAndroid Build Coastguard Worker const end = tmp.indexOf('\n'); 128*61c4878aSAndroid Build Coastguard Worker const change = tmp.substring(label.length, end).trim(); 129*61c4878aSAndroid Build Coastguard Worker return change; 130*61c4878aSAndroid Build Coastguard Worker } 131*61c4878aSAndroid Build Coastguard Worker 132*61c4878aSAndroid Build Coastguard Worker for (let i = 0; i < commits.length; i++) { 133*61c4878aSAndroid Build Coastguard Worker let commit = commits[i]; 134*61c4878aSAndroid Build Coastguard Worker const { message, sha } = commit; 135*61c4878aSAndroid Build Coastguard Worker commit.url = `https://cs.opensource.google/pigweed/pigweed/+/${sha}`; 136*61c4878aSAndroid Build Coastguard Worker commit.change = parseChangeUrl(message); 137*61c4878aSAndroid Build Coastguard Worker commit.summary = message.substring(0, message.indexOf('\n')); 138*61c4878aSAndroid Build Coastguard Worker commit.preamble = message.substring(0, message.indexOf(':')); 139*61c4878aSAndroid Build Coastguard Worker commit.category = categorize(commit.preamble); 140*61c4878aSAndroid Build Coastguard Worker commit.title = parseTitle(message); 141*61c4878aSAndroid Build Coastguard Worker // We use syntax like "pw_{tokenizer,string}" to indicate that a commit 142*61c4878aSAndroid Build Coastguard Worker // affects both pw_tokenizer and pw_string. The next logic detects this 143*61c4878aSAndroid Build Coastguard Worker // situation. The same commit gets duplicated to each module's section. 144*61c4878aSAndroid Build Coastguard Worker // The rationale for the duplication is that someone might only care about 145*61c4878aSAndroid Build Coastguard Worker // pw_tokenizer and they should be able to see all commits that affected 146*61c4878aSAndroid Build Coastguard Worker // in a single place. 147*61c4878aSAndroid Build Coastguard Worker if (commit.preamble.indexOf('{') > -1) { 148*61c4878aSAndroid Build Coastguard Worker commit.topics = []; 149*61c4878aSAndroid Build Coastguard Worker const topics = commit.preamble 150*61c4878aSAndroid Build Coastguard Worker .substring( 151*61c4878aSAndroid Build Coastguard Worker commit.preamble.indexOf('{') + 1, 152*61c4878aSAndroid Build Coastguard Worker commit.preamble.indexOf('}'), 153*61c4878aSAndroid Build Coastguard Worker ) 154*61c4878aSAndroid Build Coastguard Worker .split(','); 155*61c4878aSAndroid Build Coastguard Worker topics.forEach((topic) => commit.topics.push(`pw_${topic}`)); 156*61c4878aSAndroid Build Coastguard Worker } else { 157*61c4878aSAndroid Build Coastguard Worker commit.topics = [commit.preamble]; 158*61c4878aSAndroid Build Coastguard Worker } 159*61c4878aSAndroid Build Coastguard Worker const bugLabels = ['Bug:', 'Fixes:', 'Fixed:']; 160*61c4878aSAndroid Build Coastguard Worker for (let i = 0; i < bugLabels.length; i++) { 161*61c4878aSAndroid Build Coastguard Worker const bugLabel = bugLabels[i]; 162*61c4878aSAndroid Build Coastguard Worker if (message.indexOf(bugLabel) > -1) { 163*61c4878aSAndroid Build Coastguard Worker const bugUrl = parseBugUrl(message, bugLabel); 164*61c4878aSAndroid Build Coastguard Worker const bugId = bugUrl.substring(bugUrl.lastIndexOf('/') + 1); 165*61c4878aSAndroid Build Coastguard Worker commit.issue = { id: bugId, url: bugUrl }; 166*61c4878aSAndroid Build Coastguard Worker break; 167*61c4878aSAndroid Build Coastguard Worker } 168*61c4878aSAndroid Build Coastguard Worker } 169*61c4878aSAndroid Build Coastguard Worker } 170*61c4878aSAndroid Build Coastguard Worker return commits; 171*61c4878aSAndroid Build Coastguard Worker} 172*61c4878aSAndroid Build Coastguard Worker 173*61c4878aSAndroid Build Coastguard Worker// If there are any categories of commits that we don't want to surface 174*61c4878aSAndroid Build Coastguard Worker// in the changelog, this function is where we drop them. 175*61c4878aSAndroid Build Coastguard Workerasync function filter(commits) { 176*61c4878aSAndroid Build Coastguard Worker const filteredCommits = commits.filter((commit) => { 177*61c4878aSAndroid Build Coastguard Worker if (commit.preamble === 'roll') return false; 178*61c4878aSAndroid Build Coastguard Worker return true; 179*61c4878aSAndroid Build Coastguard Worker }); 180*61c4878aSAndroid Build Coastguard Worker return filteredCommits; 181*61c4878aSAndroid Build Coastguard Worker} 182*61c4878aSAndroid Build Coastguard Worker 183*61c4878aSAndroid Build Coastguard Worker// Render the commit data as reStructuredText. 184*61c4878aSAndroid Build Coastguard Workerasync function render(commits) { 185*61c4878aSAndroid Build Coastguard Worker function organizeByCategoryAndTopic(commits) { 186*61c4878aSAndroid Build Coastguard Worker let categories = {}; 187*61c4878aSAndroid Build Coastguard Worker commits.forEach((commit) => { 188*61c4878aSAndroid Build Coastguard Worker const { category } = commit; 189*61c4878aSAndroid Build Coastguard Worker if (!(category in categories)) categories[category] = {}; 190*61c4878aSAndroid Build Coastguard Worker commit.topics.forEach((topic) => { 191*61c4878aSAndroid Build Coastguard Worker topic in categories[category] 192*61c4878aSAndroid Build Coastguard Worker ? categories[category][topic].push(commit) 193*61c4878aSAndroid Build Coastguard Worker : (categories[category][topic] = [commit]); 194*61c4878aSAndroid Build Coastguard Worker }); 195*61c4878aSAndroid Build Coastguard Worker }); 196*61c4878aSAndroid Build Coastguard Worker return categories; 197*61c4878aSAndroid Build Coastguard Worker } 198*61c4878aSAndroid Build Coastguard Worker 199*61c4878aSAndroid Build Coastguard Worker async function createRestSection(commits) { 200*61c4878aSAndroid Build Coastguard Worker const locale = 'en-US'; 201*61c4878aSAndroid Build Coastguard Worker const format = { day: '2-digit', month: 'short', year: 'numeric' }; 202*61c4878aSAndroid Build Coastguard Worker const start = new Date( 203*61c4878aSAndroid Build Coastguard Worker document.querySelector('#start').value, 204*61c4878aSAndroid Build Coastguard Worker ).toLocaleDateString(locale, format); 205*61c4878aSAndroid Build Coastguard Worker const end = new Date( 206*61c4878aSAndroid Build Coastguard Worker document.querySelector('#end').value, 207*61c4878aSAndroid Build Coastguard Worker ).toLocaleDateString(locale, format); 208*61c4878aSAndroid Build Coastguard Worker let rest = ''; 209*61c4878aSAndroid Build Coastguard Worker rest += '.. _docs-changelog-latest:\n\n'; 210*61c4878aSAndroid Build Coastguard Worker const title = `${end}`; 211*61c4878aSAndroid Build Coastguard Worker rest += `${'-'.repeat(title.length)}\n`; 212*61c4878aSAndroid Build Coastguard Worker rest += `${title}\n`; 213*61c4878aSAndroid Build Coastguard Worker rest += `${'-'.repeat(title.length)}\n\n`; 214*61c4878aSAndroid Build Coastguard Worker rest += '.. changelog_highlights_start\n\n'; 215*61c4878aSAndroid Build Coastguard Worker rest += `Highlights (${start} to ${end}):\n\n`; 216*61c4878aSAndroid Build Coastguard Worker rest += '* **<Highlight 1>**: Description\n'; 217*61c4878aSAndroid Build Coastguard Worker rest += '* **<Highlight 2>**: Description\n'; 218*61c4878aSAndroid Build Coastguard Worker rest += '* **<Highlight 3>**: Description\n\n'; 219*61c4878aSAndroid Build Coastguard Worker rest += '.. changelog_highlights_end\n\n'; 220*61c4878aSAndroid Build Coastguard Worker rest += 'Active SEEDs\n'; 221*61c4878aSAndroid Build Coastguard Worker rest += '============\n'; 222*61c4878aSAndroid Build Coastguard Worker rest += 'Help shape the future of Pigweed! Please visit :ref:`seed-0000`\n'; 223*61c4878aSAndroid Build Coastguard Worker rest += 'and leave feedback on the RFCs (i.e. SEEDs) marked\n'; 224*61c4878aSAndroid Build Coastguard Worker rest += '``Open for Comments``.\n\n'; 225*61c4878aSAndroid Build Coastguard Worker rest += '.. Note: There is space between the following section headings\n'; 226*61c4878aSAndroid Build Coastguard Worker rest += '.. and commit lists to remind you to write a summary for each\n'; 227*61c4878aSAndroid Build Coastguard Worker rest += '.. section. If a summary is not needed, delete the extra\n'; 228*61c4878aSAndroid Build Coastguard Worker rest += '.. space.\n\n'; 229*61c4878aSAndroid Build Coastguard Worker const categories = [ 230*61c4878aSAndroid Build Coastguard Worker 'Modules', 231*61c4878aSAndroid Build Coastguard Worker 'Build systems', 232*61c4878aSAndroid Build Coastguard Worker 'Hardware targets', 233*61c4878aSAndroid Build Coastguard Worker 'Language support', 234*61c4878aSAndroid Build Coastguard Worker 'OS support', 235*61c4878aSAndroid Build Coastguard Worker 'Docs', 236*61c4878aSAndroid Build Coastguard Worker 'SEEDs', 237*61c4878aSAndroid Build Coastguard Worker 'Third-party software', 238*61c4878aSAndroid Build Coastguard Worker 'Miscellaneous', 239*61c4878aSAndroid Build Coastguard Worker ]; 240*61c4878aSAndroid Build Coastguard Worker for (let i = 0; i < categories.length; i++) { 241*61c4878aSAndroid Build Coastguard Worker const category = categories[i]; 242*61c4878aSAndroid Build Coastguard Worker if (!(category in commits)) continue; 243*61c4878aSAndroid Build Coastguard Worker rest += `${category}\n`; 244*61c4878aSAndroid Build Coastguard Worker rest += `${'='.repeat(category.length)}\n\n`; 245*61c4878aSAndroid Build Coastguard Worker let topics = Object.keys(commits[category]); 246*61c4878aSAndroid Build Coastguard Worker topics.sort(); 247*61c4878aSAndroid Build Coastguard Worker topics.forEach((topic) => { 248*61c4878aSAndroid Build Coastguard Worker // Some topics should not be rendered because they're redundant. 249*61c4878aSAndroid Build Coastguard Worker // E.g. we already have a "Docs" H3 heading so we don't need another 250*61c4878aSAndroid Build Coastguard Worker // "docs" H4 heading right after it. 251*61c4878aSAndroid Build Coastguard Worker const topicsToSkip = ['docs']; 252*61c4878aSAndroid Build Coastguard Worker if (!topicsToSkip.includes(topic)) { 253*61c4878aSAndroid Build Coastguard Worker rest += `${topic}\n`; 254*61c4878aSAndroid Build Coastguard Worker rest += `${'-'.repeat(topic.length)}\n\n\n`; 255*61c4878aSAndroid Build Coastguard Worker } 256*61c4878aSAndroid Build Coastguard Worker commits[category][topic].forEach((commit) => { 257*61c4878aSAndroid Build Coastguard Worker // Escape any backticks that are used in the commit message. 258*61c4878aSAndroid Build Coastguard Worker const change = commit.change.replaceAll('`', '\\`'); 259*61c4878aSAndroid Build Coastguard Worker // Use double underscores to make the links anonymous so that Sphinx 260*61c4878aSAndroid Build Coastguard Worker // doesn't error when the same link is used multiple times. 261*61c4878aSAndroid Build Coastguard Worker // https://github.com/sphinx-doc/sphinx/issues/3921 262*61c4878aSAndroid Build Coastguard Worker rest += `* \`${commit.title}\n <${change}>\`__\n`; 263*61c4878aSAndroid Build Coastguard Worker if (commit.issue) 264*61c4878aSAndroid Build Coastguard Worker rest += ` (issue \`#${commit.issue.id} <${commit.issue.url}>\`__)\n`; 265*61c4878aSAndroid Build Coastguard Worker }); 266*61c4878aSAndroid Build Coastguard Worker rest += '\n'; 267*61c4878aSAndroid Build Coastguard Worker }); 268*61c4878aSAndroid Build Coastguard Worker } 269*61c4878aSAndroid Build Coastguard Worker const section = document.createElement('section'); 270*61c4878aSAndroid Build Coastguard Worker const heading = document.createElement('h2'); 271*61c4878aSAndroid Build Coastguard Worker section.appendChild(heading); 272*61c4878aSAndroid Build Coastguard Worker const pre = document.createElement('pre'); 273*61c4878aSAndroid Build Coastguard Worker section.appendChild(pre); 274*61c4878aSAndroid Build Coastguard Worker const code = document.createElement('code'); 275*61c4878aSAndroid Build Coastguard Worker pre.appendChild(code); 276*61c4878aSAndroid Build Coastguard Worker code.textContent = rest; 277*61c4878aSAndroid Build Coastguard Worker try { 278*61c4878aSAndroid Build Coastguard Worker await navigator.clipboard.writeText(rest); 279*61c4878aSAndroid Build Coastguard Worker document.querySelector('#status').textContent = 280*61c4878aSAndroid Build Coastguard Worker 'Done! The output was copied to your clipboard.'; 281*61c4878aSAndroid Build Coastguard Worker } catch (error) { 282*61c4878aSAndroid Build Coastguard Worker document.querySelector('#status').textContent = 'Done!'; 283*61c4878aSAndroid Build Coastguard Worker } 284*61c4878aSAndroid Build Coastguard Worker return section; 285*61c4878aSAndroid Build Coastguard Worker } 286*61c4878aSAndroid Build Coastguard Worker 287*61c4878aSAndroid Build Coastguard Worker const organizedCommits = organizeByCategoryAndTopic(commits); 288*61c4878aSAndroid Build Coastguard Worker document.querySelector('#status').textContent = 'Rendering data...'; 289*61c4878aSAndroid Build Coastguard Worker const container = document.createElement('div'); 290*61c4878aSAndroid Build Coastguard Worker const restSection = await createRestSection(organizedCommits); 291*61c4878aSAndroid Build Coastguard Worker container.appendChild(restSection); 292*61c4878aSAndroid Build Coastguard Worker return container; 293*61c4878aSAndroid Build Coastguard Worker} 294*61c4878aSAndroid Build Coastguard Worker 295*61c4878aSAndroid Build Coastguard Worker// Use the placeholder in the start and end date text inputs to guide users 296*61c4878aSAndroid Build Coastguard Worker// towards the correct date format. 297*61c4878aSAndroid Build Coastguard Workerfunction populateDates() { 298*61c4878aSAndroid Build Coastguard Worker // Suggest the start date. 299*61c4878aSAndroid Build Coastguard Worker let twoWeeksAgo = new Date(); 300*61c4878aSAndroid Build Coastguard Worker twoWeeksAgo.setDate(twoWeeksAgo.getDate() - 14); 301*61c4878aSAndroid Build Coastguard Worker const twoWeeksAgoFormatted = twoWeeksAgo.toISOString().slice(0, 10); 302*61c4878aSAndroid Build Coastguard Worker document.querySelector('#start').placeholder = twoWeeksAgoFormatted; 303*61c4878aSAndroid Build Coastguard Worker // Suggest the end date. 304*61c4878aSAndroid Build Coastguard Worker const today = new Date(); 305*61c4878aSAndroid Build Coastguard Worker const todayFormatted = today.toISOString().slice(0, 10); 306*61c4878aSAndroid Build Coastguard Worker document.querySelector('#end').placeholder = todayFormatted; 307*61c4878aSAndroid Build Coastguard Worker} 308*61c4878aSAndroid Build Coastguard Worker 309*61c4878aSAndroid Build Coastguard Worker// Enable the "generate" button only when the start and end dates are valid. 310*61c4878aSAndroid Build Coastguard Workerfunction validateDates() { 311*61c4878aSAndroid Build Coastguard Worker const dateFormat = /^\d{4}-\d{2}-\d{2}$/; 312*61c4878aSAndroid Build Coastguard Worker const start = document.querySelector('#start').value; 313*61c4878aSAndroid Build Coastguard Worker const end = document.querySelector('#end').value; 314*61c4878aSAndroid Build Coastguard Worker const status = document.querySelector('#status'); 315*61c4878aSAndroid Build Coastguard Worker let generate = document.querySelector('#generate'); 316*61c4878aSAndroid Build Coastguard Worker if (!start.match(dateFormat) || !end.match(dateFormat)) { 317*61c4878aSAndroid Build Coastguard Worker generate.disabled = true; 318*61c4878aSAndroid Build Coastguard Worker status.textContent = 'Invalid start or end date (should be YYYY-MM-DD)'; 319*61c4878aSAndroid Build Coastguard Worker } else { 320*61c4878aSAndroid Build Coastguard Worker generate.disabled = false; 321*61c4878aSAndroid Build Coastguard Worker status.textContent = 'Ready to generate!'; 322*61c4878aSAndroid Build Coastguard Worker } 323*61c4878aSAndroid Build Coastguard Worker} 324*61c4878aSAndroid Build Coastguard Worker 325*61c4878aSAndroid Build Coastguard Worker// Set up the date placeholder and validation stuff when the page loads. 326*61c4878aSAndroid Build Coastguard Workerwindow.addEventListener('load', () => { 327*61c4878aSAndroid Build Coastguard Worker populateDates(); 328*61c4878aSAndroid Build Coastguard Worker document.querySelector('#start').addEventListener('keyup', validateDates); 329*61c4878aSAndroid Build Coastguard Worker document.querySelector('#end').addEventListener('keyup', validateDates); 330*61c4878aSAndroid Build Coastguard Worker}); 331*61c4878aSAndroid Build Coastguard Worker 332*61c4878aSAndroid Build Coastguard Worker// Run through the whole get/normalize/annotate/filter/render pipeline when 333*61c4878aSAndroid Build Coastguard Worker// the user clicks the "generate" button. 334*61c4878aSAndroid Build Coastguard Workerdocument.querySelector('#generate').addEventListener('click', async (e) => { 335*61c4878aSAndroid Build Coastguard Worker e.target.disabled = true; 336*61c4878aSAndroid Build Coastguard Worker const rawCommits = await get(); 337*61c4878aSAndroid Build Coastguard Worker const normalizedCommits = await normalize(rawCommits); 338*61c4878aSAndroid Build Coastguard Worker const annotatedCommits = await annotate(normalizedCommits); 339*61c4878aSAndroid Build Coastguard Worker const filteredCommits = await filter(annotatedCommits); 340*61c4878aSAndroid Build Coastguard Worker const output = await render(filteredCommits); 341*61c4878aSAndroid Build Coastguard Worker document.querySelector('#output').innerHTML = ''; 342*61c4878aSAndroid Build Coastguard Worker document.querySelector('#output').appendChild(output); 343*61c4878aSAndroid Build Coastguard Worker e.target.disabled = false; 344*61c4878aSAndroid Build Coastguard Worker}); 345