From b3b60c39a55f3ff82f64430df30a2d923e1c45dd Mon Sep 17 00:00:00 2001 From: Angel Date: Mon, 9 Sep 2024 10:34:42 -0400 Subject: [PATCH] Feat: Automated changelogs from beamer (#7593) * beamer first pass * add beamer fetcher * add github action * revert old gha * revert konnet changelog * beamer updates towards wireframe * place holder badge styling * caps * first pass at trying out styling * try some badges * Remove date writing, add compromise library * change the way badges are written so we can render multiple badges on the same line * final styling * remove compromise library add new tagging system in beamer * reset changelog * Update fetch-konnect-changelog.yml * reset badges --------- Co-authored-by: Lena --- .github/workflows/fetch-konnect-changelog.yml | 43 ++++++ .github/workflows/generate-changelog.yml | 2 +- app/_assets/stylesheets/pages/changelog.less | 50 +++---- tools/beamer/fetch-beamer-posts.js | 141 ++++++++++++++++++ 4 files changed, 209 insertions(+), 27 deletions(-) create mode 100644 .github/workflows/fetch-konnect-changelog.yml create mode 100644 tools/beamer/fetch-beamer-posts.js diff --git a/.github/workflows/fetch-konnect-changelog.yml b/.github/workflows/fetch-konnect-changelog.yml new file mode 100644 index 000000000000..28ce9d5b2fac --- /dev/null +++ b/.github/workflows/fetch-konnect-changelog.yml @@ -0,0 +1,43 @@ +name: Update Konnect Changelog + +on: workflow_dispatch + +concurrency: + cancel-in-progress: true + group: ${{ github.workflow }} + +permissions: + contents: write + pull-requests: write + +jobs: + update-changelog: + runs-on: ubuntu-latest + timeout-minutes: 10 + + steps: + - uses: actions/checkout@v4 + with: + token: ${{ secrets.PAT }} + - name: Run changelog script + working-directory: tools/beamer + env: + BEAMER_API_KEY: ${{ secrets.BEAMER_API_KEY }} + run: | + npm ci + node fetch-beamer-posts.js + + - name: Create Pull Request + uses: peter-evans/create-pull-request@v6 + with: + title: Automated Konnect changelog update [skip-ci] + body: | + Latest Konnect changelog entries from beamer + labels: skip-changelog,review:general + base: main + branch: automated-konnect-changelog-update + token: ${{ secrets.PAT }} + committer: kong-docs[bot] + author: kong-docs[bot] + delete-branch: true + commit-message: "Automated Konnect changelog update" diff --git a/.github/workflows/generate-changelog.yml b/.github/workflows/generate-changelog.yml index 3b81d598d696..312b489c978d 100644 --- a/.github/workflows/generate-changelog.yml +++ b/.github/workflows/generate-changelog.yml @@ -41,4 +41,4 @@ jobs: committer: kong-docs[bot] author: kong-docs[bot] delete-branch: true - commit-message: "Automated changelog update" + commit-message: "Automated changelog update" \ No newline at end of file diff --git a/app/_assets/stylesheets/pages/changelog.less b/app/_assets/stylesheets/pages/changelog.less index 256baadf3dd9..8ae8f7e3cc63 100644 --- a/app/_assets/stylesheets/pages/changelog.less +++ b/app/_assets/stylesheets/pages/changelog.less @@ -1,32 +1,30 @@ -.page-changelog { - .version-select { - ul { - li { - a { - display: flex; - align-items: center; - justify-content: left; - } - &.active { - a { - color: @blue; - } - } - } - } +.changelog-entries { + display: flex; + flex-direction: row; + margin-bottom: 25px; + + .changelog-date { + display: block; + font-weight: 200; + font-size: 18px; } - .content { - h2 { - margin-top: none; + .changelog-entry { + flex-direction: column; + margin-left: 35px; + + .changelog-title { + display: inline-block; + font-weight: 600; + font-size: 18px; } - ul { - margin-left: 1.5rem; - font-size: 100%; - li { - margin-bottom: 2rem; - } + .changelog-description { + margin-top: 8px; + + } + .changelog-badges { + margin-top: 8px; } } -} +} \ No newline at end of file diff --git a/tools/beamer/fetch-beamer-posts.js b/tools/beamer/fetch-beamer-posts.js new file mode 100644 index 000000000000..ae27965a16eb --- /dev/null +++ b/tools/beamer/fetch-beamer-posts.js @@ -0,0 +1,141 @@ +const https = require('https'); +const fs = require('fs'); +const path = require('path'); + +const apiKey = process.env.BEAMER_API_KEY; +// const filePath = process.env.FILE_PATH; +const filePath = __dirname + "/../../app/konnect/updates.md"; +const options = { + hostname: 'api.getbeamer.com', + port: 443, + path: '/v0/posts', + method: 'GET', + headers: { + 'Beamer-Api-Key': apiKey, + } +}; +const monthNames = [ + "January", "February", "March", "April", "May", "June", + "July", "August", "September", "October", "November", "December" +]; + +function cleanHTML(contentHtml) { + if (!contentHtml) return ''; + return contentHtml + .replace(/<[^>]*>/g, '') // Remove HTML tags + .replace(/\s+/g, ' ') // Replace multiple spaces/newlines with a single space + .trim(); // Trim leading and trailing spaces +} + +// Function to summarize content by extracting the first two sentences +function summarizeContent(content, maxSentences = 2) { + const sentenceRegex = /[^.!?]*[.!?]/g; // Regex to capture sentences + const sentences = content.match(sentenceRegex) || []; // Match all sentences in the content + + // Extract and join the first 'maxSentences' sentences + const summary = sentences.slice(0, maxSentences).join(' ').trim(); + return summary; +} + +// Function to clean and summarize content +function cleanContent(content) { + const cleanedContent = content.replace(/\s+/g, ' ').trim(); + return summarizeContent(cleanedContent); // Summarize the cleaned content +} + +const req = https.request(options, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + try { + const posts = JSON.parse(data); + const groupedPosts = {}; + + posts.forEach(post => { + const date = new Date(post.date); + const month = monthNames[date.getMonth()]; + const year = date.getFullYear(); + const day = String(date.getDate()).padStart(2, '0'); + const monthYear = `${month} ${year}`; + const formattedDate = `${month} ${day}`; + + if (!groupedPosts[monthYear]) { + groupedPosts[monthYear] = []; + } + + post.translations.forEach(translation => { + // Check if 'changelog' is part of the category + if (translation.category.split(';').some(cat => cat.trim().toLowerCase() === 'changelog')) { + let contentPreview = translation.content ? cleanContent(translation.content) : cleanHTML(translation.contentHtml); + groupedPosts[monthYear].push({ + date: formattedDate, + title: translation.title, + postUrl: translation.postUrl, + content: contentPreview, + }); + } + }); + }); + + let updatesContent = ''; + + for (const [monthYear, posts] of Object.entries(groupedPosts)) { + // Add the month and year heading before the posts for that month + updatesContent += `## ${monthYear}\n\n`; + + posts.forEach(post => { + updatesContent += `
\n`; + updatesContent += `
${post.date}
\n`; + updatesContent += `
\n`; + updatesContent += `
\n`; + updatesContent += `${post.title}\n`; + updatesContent += `
\n`; + + if (post.content) { + updatesContent += `
${post.content}
\n`; + } + + updatesContent += `
\n`; + updatesContent += `
\n`; + }); + } + + fs.readFile(filePath, 'utf8', (err, data) => { + if (err) { + console.error('Error reading file:', err); + return; + } + + const match = data.match(/## \w+ \d{4}/); + if (!match) { + console.error('First month heading not found in file.'); + return; + } + const insertIndex = match.index; + + const newContent = `${data.slice(0, insertIndex)}${updatesContent}\n${data.slice(insertIndex)}`; + + fs.writeFile(filePath, newContent, 'utf8', (err) => { + if (err) { + console.error('Error writing to file:', err); + return; + } + + console.log('Updates added successfully!'); + }); + }); + } catch (error) { + console.error('Error parsing response:', error); + } + }); +}); + +req.on('error', (e) => { + console.error('Error making request:', e); +}); + +req.end();