From 8818c5832d4fbd206f75e85b5058e17780b04eaa Mon Sep 17 00:00:00 2001 From: Debanitrkl Date: Thu, 5 Mar 2026 00:38:00 +0530 Subject: [PATCH 1/4] docs: add Parseable Cloud CTA, adopters section, and auto-sync workflow - Update README description to link Parseable Cloud (app.parseable.com) - Add centered Cloud CTA block before YouTube embed - Add Adopters section (between Features and Verify Images) with ADOPTERS:START/END markers, sourced from USERS.md - Add GitHub Action to auto-sync adopters from USERS.md to README on push to main Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-adopters.yaml | 68 ++++++++++++++++++++++++++++ README.md | 20 +++++++- 2 files changed, 87 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/sync-adopters.yaml diff --git a/.github/workflows/sync-adopters.yaml b/.github/workflows/sync-adopters.yaml new file mode 100644 index 000000000..7a70a4b69 --- /dev/null +++ b/.github/workflows/sync-adopters.yaml @@ -0,0 +1,68 @@ +name: Sync Adopters to README + +on: + push: + branches: [main] + paths: [USERS.md] + +permissions: + contents: write + +jobs: + sync: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Extract adopters and update README + run: | + python3 - <<'SCRIPT' + import re + + # Read USERS.md and extract Organization + Description columns + with open("USERS.md") as f: + lines = f.read().strip().split("\n") + + rows = [] + for line in lines: + if not line.startswith("|"): + continue + parts = [p.strip() for p in line.split("|")] + # parts: ['', org, contact, desc, ''] + if len(parts) < 4: + continue + org = parts[1] + if org == "Organization" or org.startswith("---") or org.startswith("--"): + continue + desc = parts[3] + rows.append(f"| {org} | {desc} |") + + table = "| Organization | Description of Use |\n| --- | --- |\n" + "\n".join(rows) + + # Replace content between markers in README.md + with open("README.md") as f: + readme = f.read() + + readme = re.sub( + r"(\n).*?(\n)", + rf"\g<1>{table}\g<2>", + readme, + flags=re.DOTALL, + ) + + with open("README.md", "w") as f: + f.write(readme) + SCRIPT + + - name: Check for changes + id: diff + run: git diff --quiet README.md || echo "changed=true" >> "$GITHUB_OUTPUT" + + - name: Commit and push + if: steps.diff.outputs.changed == 'true' + run: | + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add README.md + git commit -m "docs: sync adopters from USERS.md to README" + git push diff --git a/README.md b/README.md index 499cdcc88..8d8592b23 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,14 @@ -Parseable is a full stack observability platform built to ingest, analyze and extract insights from all types of telemetry (MELT) data. You can run Parseable on your local machine, in the cloud, or as a managed service. To experience Parseable UI, checkout [demo.parseable.com ↗︎](https://demo.parseable.com/login). +Parseable is a full stack observability platform built to ingest, analyze and extract insights from all types of telemetry (MELT) data. You can run Parseable on your local machine, in the cloud, or use [Parseable Cloud](https://app.parseable.com) — the fully managed service. To experience Parseable UI, checkout [demo.parseable.com ↗︎](https://demo.parseable.com/login). + +
+

+ Try Parseable Cloud — Start Free ↗︎ +

+

The fastest way to get started. No infrastructure to manage.

+
@@ -103,6 +110,17 @@ This section elaborates available options to run Parseable in production or deve - [OAuth2 support ↗︎](https://www.parseable.com/docs/features/oepnid) - [OpenTelemetry support ↗︎](https://www.parseable.com/docs/OpenTelemetry/logs) +## Adopters :handshake: + +Organizations using Parseable in production. Add yours by submitting a PR to [USERS.md](USERS.md). + + +| Organization | Description of Use | +| --- | --- | +| [HireXL](https://www.hirexl.in/) | Frontend application logging | +| [Elfsquad](https://elfsquad.io) | Centralized application/infrastructure logging | + + ## Verify images :writing_hand: Parseable builds are attested for build provenance and integrity using the [attest-build-provenance](https://github.com/actions/attest-build-provenance) action. The attestations can be verified by having the latest version of [GitHub CLI](https://github.com/cli/cli/releases/latest) installed in your system. Then, execute the following command: From b45c37d2d2e2f9dc6dd02c11d764858dbf1ab164 Mon Sep 17 00:00:00 2001 From: Debanitrkl Date: Thu, 5 Mar 2026 00:49:15 +0530 Subject: [PATCH 2/4] feat: frictionless adopter registration via GitHub Issue Form MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Users can now add their organization to the adopters list by simply filling out a GitHub Issue Form — no fork or PR needed. How it works: 1. User opens an issue using the "Add Adopter" template 2. Fills in: Organization Name, URL, Contact, and Description of Use 3. A GitHub Action automatically validates the submission, checks for duplicates, and creates a PR updating USERS.md 4. Maintainers review and merge the PR — issue auto-closes Changes: - Add issue form template (.github/ISSUE_TEMPLATE/add-adopter.yml) - Add template chooser config with Slack and docs links - Add add-adopter workflow to automate PR creation - Add github-actions[bot] to CLA allowlist for bot-created PRs - Update README adopters section to link to the issue form Co-Authored-By: Claude Opus 4.6 --- .github/ISSUE_TEMPLATE/add-adopter.yml | 54 +++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++ .github/workflows/add-adopter.yaml | 151 +++++++++++++++++++++++++ .github/workflows/cla.yaml | 2 +- README.md | 2 +- 5 files changed, 215 insertions(+), 2 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/add-adopter.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/workflows/add-adopter.yaml diff --git a/.github/ISSUE_TEMPLATE/add-adopter.yml b/.github/ISSUE_TEMPLATE/add-adopter.yml new file mode 100644 index 000000000..e707b8863 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/add-adopter.yml @@ -0,0 +1,54 @@ +name: "Add Adopter" +description: "Add your organization to the Parseable adopters list" +title: "Add adopter: " +labels: ["new-adopter"] +body: + - type: markdown + attributes: + value: | + Thanks for using Parseable! Fill out the form below to add your organization to our adopters list. + A PR will be created automatically — no fork needed. + + - type: input + id: org_name + attributes: + label: Organization Name + description: "The name of your organization" + placeholder: "Acme Corp" + validations: + required: true + + - type: input + id: org_url + attributes: + label: Organization URL + description: "Your organization's website" + placeholder: "https://example.com" + validations: + required: true + + - type: input + id: contact + attributes: + label: Contact + description: "GitHub @handle or name of the contact person" + placeholder: "@username" + validations: + required: true + + - type: textarea + id: description + attributes: + label: Description of Use + description: "How does your organization use Parseable?" + placeholder: "We use Parseable for centralized logging of our microservices..." + validations: + required: true + + - type: checkboxes + id: confirmation + attributes: + label: Confirmation + options: + - label: "I am authorized to represent this organization" + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..8836ed291 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: true +contact_links: + - name: Slack Community + url: https://logg.ing/community + about: Join the Parseable Slack community for questions and discussions + - name: Documentation + url: https://www.parseable.com/docs + about: Check our documentation for guides and references diff --git a/.github/workflows/add-adopter.yaml b/.github/workflows/add-adopter.yaml new file mode 100644 index 000000000..239ee1ebc --- /dev/null +++ b/.github/workflows/add-adopter.yaml @@ -0,0 +1,151 @@ +name: Add Adopter + +on: + issues: + types: [opened] + +permissions: + contents: write + issues: write + pull-requests: write + +jobs: + add-adopter: + if: contains(github.event.issue.labels.*.name, 'new-adopter') + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Parse issue and create PR + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + script: | + const issue = context.payload.issue; + const body = issue.body || ''; + + // Parse structured fields from issue body + function parseField(body, heading) { + const regex = new RegExp(`### ${heading}\\s*\\n\\s*([\\s\\S]*?)(?=\\n### |$)`); + const match = body.match(regex); + return match ? match[1].trim() : ''; + } + + const orgName = parseField(body, 'Organization Name'); + const orgUrl = parseField(body, 'Organization URL'); + const contact = parseField(body, 'Contact'); + const description = parseField(body, 'Description of Use'); + + // Validate fields + const errors = []; + if (!orgName) errors.push('- **Organization Name** is missing'); + if (!orgUrl) errors.push('- **Organization URL** is missing'); + if (!orgUrl.startsWith('https://')) errors.push('- **Organization URL** must start with `https://`'); + if (!contact) errors.push('- **Contact** is missing'); + if (!description) errors.push('- **Description of Use** is missing'); + + if (errors.length > 0) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `👋 Thanks for your interest in being listed as a Parseable adopter!\n\nUnfortunately, there were some issues with your submission:\n\n${errors.join('\n')}\n\nPlease close this issue and [open a new one](https://github.com/${context.repo.owner}/${context.repo.repo}/issues/new?template=add-adopter.yml) with the corrected information.` + }); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['invalid'] + }); + return; + } + + // Read USERS.md and check for duplicates + const { data: fileData } = await github.rest.repos.getContent({ + owner: context.repo.owner, + repo: context.repo.repo, + path: 'USERS.md', + ref: 'main' + }); + const usersContent = Buffer.from(fileData.content, 'base64').toString('utf-8'); + const orgNameLower = orgName.toLowerCase(); + + if (usersContent.toLowerCase().includes(orgNameLower)) { + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `👋 Thanks for your interest!\n\nIt looks like **${orgName}** is already listed in our adopters list. If you need to update the existing entry, please open a PR directly.\n\nClosing this issue as duplicate.` + }); + await github.rest.issues.addLabels({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + labels: ['duplicate'] + }); + await github.rest.issues.update({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + state: 'closed' + }); + return; + } + + // Build contact link + let contactCell = contact; + if (contact.startsWith('@')) { + const handle = contact.substring(1); + contactCell = `[@${handle}](https://github.com/${handle})`; + } + + // Append new row to USERS.md + const newRow = `| [${orgName}](${orgUrl}) | ${contactCell} | ${description} |`; + const updatedContent = usersContent.trimEnd() + '\n' + newRow + '\n'; + + // Create branch + const slug = orgName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, ''); + const branchName = `adopter/add-${slug}-${issue.number}`; + + const { data: ref } = await github.rest.git.getRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: 'heads/main' + }); + + await github.rest.git.createRef({ + owner: context.repo.owner, + repo: context.repo.repo, + ref: `refs/heads/${branchName}`, + sha: ref.object.sha + }); + + // Commit updated USERS.md + await github.rest.repos.createOrUpdateFileContents({ + owner: context.repo.owner, + repo: context.repo.repo, + path: 'USERS.md', + message: `Add ${orgName} to adopters list`, + content: Buffer.from(updatedContent).toString('base64'), + sha: fileData.sha, + branch: branchName + }); + + // Create PR + const { data: pr } = await github.rest.pulls.create({ + owner: context.repo.owner, + repo: context.repo.repo, + title: `Add ${orgName} to adopters list`, + head: branchName, + base: 'main', + body: `## New Adopter\n\n| Field | Value |\n|-------|-------|\n| **Organization** | [${orgName}](${orgUrl}) |\n| **Contact** | ${contactCell} |\n| **Description** | ${description} |\n\nCloses #${issue.number}` + }); + + // Comment on issue + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: issue.number, + body: `PR created: ${pr.html_url}\n\nA maintainer will review and merge it shortly. Thanks for using Parseable!` + }); diff --git a/.github/workflows/cla.yaml b/.github/workflows/cla.yaml index a4599cf8a..3f9224985 100644 --- a/.github/workflows/cla.yaml +++ b/.github/workflows/cla.yaml @@ -31,7 +31,7 @@ jobs: path-to-document: 'https://github.com/parseablehq/.github/blob/main/CLA.md' # e.g. a CLA or a DCO document # branch should not be protected branch: 'main' - allowlist: dependabot[bot],deepsource-autofix[bot],deepsourcebot + allowlist: dependabot[bot],deepsource-autofix[bot],deepsourcebot,github-actions[bot] # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken #remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) diff --git a/README.md b/README.md index 8d8592b23..e22051d6e 100644 --- a/README.md +++ b/README.md @@ -112,7 +112,7 @@ This section elaborates available options to run Parseable in production or deve ## Adopters :handshake: -Organizations using Parseable in production. Add yours by submitting a PR to [USERS.md](USERS.md). +Organizations using Parseable in production. [Add yours here](https://github.com/parseablehq/parseable/issues/new?template=add-adopter.yml) — no fork needed! | Organization | Description of Use | From 5a155fa6760ea87ee6f0bcfd8d3f7c100503a01e Mon Sep 17 00:00:00 2001 From: Debanitrkl Date: Thu, 5 Mar 2026 00:51:16 +0530 Subject: [PATCH 3/4] fix: address CodeRabbit review comments - Escape pipe and newline characters in adopter table cells - Fail fast if ADOPTERS markers are missing or ambiguous in README - Hyphenate "full-stack" compound adjective in README Co-Authored-By: Claude Opus 4.6 --- .github/workflows/sync-adopters.yaml | 14 ++++++++++---- README.md | 2 +- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/workflows/sync-adopters.yaml b/.github/workflows/sync-adopters.yaml index 7a70a4b69..c476fa8d7 100644 --- a/.github/workflows/sync-adopters.yaml +++ b/.github/workflows/sync-adopters.yaml @@ -23,6 +23,9 @@ jobs: with open("USERS.md") as f: lines = f.read().strip().split("\n") + def esc_cell(value: str) -> str: + return value.replace("\n", " ").replace("|", r"\|").strip() + rows = [] for line in lines: if not line.startswith("|"): @@ -35,7 +38,7 @@ jobs: if org == "Organization" or org.startswith("---") or org.startswith("--"): continue desc = parts[3] - rows.append(f"| {org} | {desc} |") + rows.append(f"| {esc_cell(org)} | {esc_cell(desc)} |") table = "| Organization | Description of Use |\n| --- | --- |\n" + "\n".join(rows) @@ -43,12 +46,15 @@ jobs: with open("README.md") as f: readme = f.read() - readme = re.sub( + pattern = re.compile( r"(\n).*?(\n)", - rf"\g<1>{table}\g<2>", - readme, flags=re.DOTALL, ) + readme, replaced = pattern.subn( + rf"\g<1>{table}\g<2>", readme, count=1 + ) + if replaced != 1: + raise SystemExit("Expected exactly one ADOPTERS block in README.md") with open("README.md", "w") as f: f.write(readme) diff --git a/README.md b/README.md index e22051d6e..260b9d278 100644 --- a/README.md +++ b/README.md @@ -17,7 +17,7 @@
-Parseable is a full stack observability platform built to ingest, analyze and extract insights from all types of telemetry (MELT) data. You can run Parseable on your local machine, in the cloud, or use [Parseable Cloud](https://app.parseable.com) — the fully managed service. To experience Parseable UI, checkout [demo.parseable.com ↗︎](https://demo.parseable.com/login). +Parseable is a full-stack observability platform built to ingest, analyze and extract insights from all types of telemetry (MELT) data. You can run Parseable on your local machine, in the cloud, or use [Parseable Cloud](https://app.parseable.com) — the fully managed service. To experience Parseable UI, checkout [demo.parseable.com ↗︎](https://demo.parseable.com/login).

From 222a4cc7456742cb8ff36c18de147f199c539483 Mon Sep 17 00:00:00 2001 From: Debanitrkl Date: Thu, 5 Mar 2026 16:31:18 +0530 Subject: [PATCH 4/4] =?UTF-8?q?fix:=20address=20review=20comments=20?= =?UTF-8?q?=E2=80=94=20demo=20link,=20input=20sanitization,=20duplicate=20?= =?UTF-8?q?check?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Replace demo.parseable.com with app.parseable.com (demo is no longer live) - Sanitize user-supplied fields (escape pipes, strip newlines) before inserting into USERS.md table row - Improve duplicate detection: parse existing org names from USERS.md and do exact match instead of substring includes Co-Authored-By: Claude Opus 4.6 --- .github/workflows/add-adopter.yaml | 26 ++++++++++++++++++++------ README.md | 4 ++-- 2 files changed, 22 insertions(+), 8 deletions(-) diff --git a/.github/workflows/add-adopter.yaml b/.github/workflows/add-adopter.yaml index 239ee1ebc..a2e21a5f2 100644 --- a/.github/workflows/add-adopter.yaml +++ b/.github/workflows/add-adopter.yaml @@ -69,9 +69,18 @@ jobs: ref: 'main' }); const usersContent = Buffer.from(fileData.content, 'base64').toString('utf-8'); - const orgNameLower = orgName.toLowerCase(); - - if (usersContent.toLowerCase().includes(orgNameLower)) { + const orgNameLower = orgName.toLowerCase().trim(); + + // Parse existing org names from USERS.md for exact match + const existingOrgs = usersContent.split('\n') + .filter(line => line.startsWith('|')) + .map(line => { + const match = line.match(/\|\s*\[([^\]]+)\]/); + return match ? match[1].toLowerCase().trim() : null; + }) + .filter(Boolean); + + if (existingOrgs.includes(orgNameLower)) { await github.rest.issues.createComment({ owner: context.repo.owner, repo: context.repo.repo, @@ -93,15 +102,20 @@ jobs: return; } + // Sanitize user input for markdown table + const sanitize = (str) => str.replace(/\|/g, '\\|').replace(/\n/g, ' ').trim(); + // Build contact link - let contactCell = contact; + let contactCell = sanitize(contact); if (contact.startsWith('@')) { - const handle = contact.substring(1); + const handle = contact.substring(1).trim(); contactCell = `[@${handle}](https://github.com/${handle})`; } // Append new row to USERS.md - const newRow = `| [${orgName}](${orgUrl}) | ${contactCell} | ${description} |`; + const safeDesc = sanitize(description); + const safeOrgName = sanitize(orgName); + const newRow = `| [${safeOrgName}](${orgUrl}) | ${contactCell} | ${safeDesc} |`; const updatedContent = usersContent.trimEnd() + '\n' + newRow + '\n'; // Create branch diff --git a/README.md b/README.md index 260b9d278..4dab307d9 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,11 @@ [![Docs](https://img.shields.io/badge/stable%20docs-parseable.com%2Fdocs-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://logg.ing/docs) [![Build](https://img.shields.io/github/checks-status/parseablehq/parseable/main?style=flat&color=%2373DC8C&label=Checks)](https://github.com/parseablehq/parseable/actions) -[Key Concepts](https://www.parseable.com/docs/key-concepts) | [Features](https://www.parseable.com/docs/features/alerts) | [Documentation](https://www.parseable.com/docs) | [Demo](https://demo.parseable.com/login) | [FAQ](https://www.parseable.com/docs/key-concepts/data-model#faq) +[Key Concepts](https://www.parseable.com/docs/key-concepts) | [Features](https://www.parseable.com/docs/features/alerts) | [Documentation](https://www.parseable.com/docs) | [Demo](https://app.parseable.com) | [FAQ](https://www.parseable.com/docs/key-concepts/data-model#faq)

-Parseable is a full-stack observability platform built to ingest, analyze and extract insights from all types of telemetry (MELT) data. You can run Parseable on your local machine, in the cloud, or use [Parseable Cloud](https://app.parseable.com) — the fully managed service. To experience Parseable UI, checkout [demo.parseable.com ↗︎](https://demo.parseable.com/login). +Parseable is a full-stack observability platform built to ingest, analyze and extract insights from all types of telemetry (MELT) data. You can run Parseable on your local machine, in the cloud, or use [Parseable Cloud](https://app.parseable.com) — the fully managed service. To experience Parseable UI, checkout [app.parseable.com ↗︎](https://app.parseable.com).