Skip to content

Commit f403bfb

Browse files
Debanitrklclaude
andauthored
Add Parseable Cloud CTA, adopters section, auto-sync, and frictionless adopter registration (#1569)
- 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 - frictionless adopter registration via GitHub Issue Form 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 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 08073d6 commit f403bfb

6 files changed

Lines changed: 322 additions & 3 deletions

File tree

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: "Add Adopter"
2+
description: "Add your organization to the Parseable adopters list"
3+
title: "Add adopter: "
4+
labels: ["new-adopter"]
5+
body:
6+
- type: markdown
7+
attributes:
8+
value: |
9+
Thanks for using Parseable! Fill out the form below to add your organization to our adopters list.
10+
A PR will be created automatically — no fork needed.
11+
12+
- type: input
13+
id: org_name
14+
attributes:
15+
label: Organization Name
16+
description: "The name of your organization"
17+
placeholder: "Acme Corp"
18+
validations:
19+
required: true
20+
21+
- type: input
22+
id: org_url
23+
attributes:
24+
label: Organization URL
25+
description: "Your organization's website"
26+
placeholder: "https://example.com"
27+
validations:
28+
required: true
29+
30+
- type: input
31+
id: contact
32+
attributes:
33+
label: Contact
34+
description: "GitHub @handle or name of the contact person"
35+
placeholder: "@username"
36+
validations:
37+
required: true
38+
39+
- type: textarea
40+
id: description
41+
attributes:
42+
label: Description of Use
43+
description: "How does your organization use Parseable?"
44+
placeholder: "We use Parseable for centralized logging of our microservices..."
45+
validations:
46+
required: true
47+
48+
- type: checkboxes
49+
id: confirmation
50+
attributes:
51+
label: Confirmation
52+
options:
53+
- label: "I am authorized to represent this organization"
54+
required: true

.github/ISSUE_TEMPLATE/config.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
blank_issues_enabled: true
2+
contact_links:
3+
- name: Slack Community
4+
url: https://logg.ing/community
5+
about: Join the Parseable Slack community for questions and discussions
6+
- name: Documentation
7+
url: https://www.parseable.com/docs
8+
about: Check our documentation for guides and references

.github/workflows/add-adopter.yaml

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
name: Add Adopter
2+
3+
on:
4+
issues:
5+
types: [opened]
6+
7+
permissions:
8+
contents: write
9+
issues: write
10+
pull-requests: write
11+
12+
jobs:
13+
add-adopter:
14+
if: contains(github.event.issue.labels.*.name, 'new-adopter')
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout repository
18+
uses: actions/checkout@v4
19+
20+
- name: Parse issue and create PR
21+
uses: actions/github-script@v7
22+
with:
23+
github-token: ${{ secrets.GITHUB_TOKEN }}
24+
script: |
25+
const issue = context.payload.issue;
26+
const body = issue.body || '';
27+
28+
// Parse structured fields from issue body
29+
function parseField(body, heading) {
30+
const regex = new RegExp(`### ${heading}\\s*\\n\\s*([\\s\\S]*?)(?=\\n### |$)`);
31+
const match = body.match(regex);
32+
return match ? match[1].trim() : '';
33+
}
34+
35+
const orgName = parseField(body, 'Organization Name');
36+
const orgUrl = parseField(body, 'Organization URL');
37+
const contact = parseField(body, 'Contact');
38+
const description = parseField(body, 'Description of Use');
39+
40+
// Validate fields
41+
const errors = [];
42+
if (!orgName) errors.push('- **Organization Name** is missing');
43+
if (!orgUrl) errors.push('- **Organization URL** is missing');
44+
if (!orgUrl.startsWith('https://')) errors.push('- **Organization URL** must start with `https://`');
45+
if (!contact) errors.push('- **Contact** is missing');
46+
if (!description) errors.push('- **Description of Use** is missing');
47+
48+
if (errors.length > 0) {
49+
await github.rest.issues.createComment({
50+
owner: context.repo.owner,
51+
repo: context.repo.repo,
52+
issue_number: issue.number,
53+
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.`
54+
});
55+
await github.rest.issues.addLabels({
56+
owner: context.repo.owner,
57+
repo: context.repo.repo,
58+
issue_number: issue.number,
59+
labels: ['invalid']
60+
});
61+
return;
62+
}
63+
64+
// Read USERS.md and check for duplicates
65+
const { data: fileData } = await github.rest.repos.getContent({
66+
owner: context.repo.owner,
67+
repo: context.repo.repo,
68+
path: 'USERS.md',
69+
ref: 'main'
70+
});
71+
const usersContent = Buffer.from(fileData.content, 'base64').toString('utf-8');
72+
const orgNameLower = orgName.toLowerCase().trim();
73+
74+
// Parse existing org names from USERS.md for exact match
75+
const existingOrgs = usersContent.split('\n')
76+
.filter(line => line.startsWith('|'))
77+
.map(line => {
78+
const match = line.match(/\|\s*\[([^\]]+)\]/);
79+
return match ? match[1].toLowerCase().trim() : null;
80+
})
81+
.filter(Boolean);
82+
83+
if (existingOrgs.includes(orgNameLower)) {
84+
await github.rest.issues.createComment({
85+
owner: context.repo.owner,
86+
repo: context.repo.repo,
87+
issue_number: issue.number,
88+
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.`
89+
});
90+
await github.rest.issues.addLabels({
91+
owner: context.repo.owner,
92+
repo: context.repo.repo,
93+
issue_number: issue.number,
94+
labels: ['duplicate']
95+
});
96+
await github.rest.issues.update({
97+
owner: context.repo.owner,
98+
repo: context.repo.repo,
99+
issue_number: issue.number,
100+
state: 'closed'
101+
});
102+
return;
103+
}
104+
105+
// Sanitize user input for markdown table
106+
const sanitize = (str) => str.replace(/\|/g, '\\|').replace(/\n/g, ' ').trim();
107+
108+
// Build contact link
109+
let contactCell = sanitize(contact);
110+
if (contact.startsWith('@')) {
111+
const handle = contact.substring(1).trim();
112+
contactCell = `[@${handle}](https://github.com/${handle})`;
113+
}
114+
115+
// Append new row to USERS.md
116+
const safeDesc = sanitize(description);
117+
const safeOrgName = sanitize(orgName);
118+
const newRow = `| [${safeOrgName}](${orgUrl}) | ${contactCell} | ${safeDesc} |`;
119+
const updatedContent = usersContent.trimEnd() + '\n' + newRow + '\n';
120+
121+
// Create branch
122+
const slug = orgName.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/(^-|-$)/g, '');
123+
const branchName = `adopter/add-${slug}-${issue.number}`;
124+
125+
const { data: ref } = await github.rest.git.getRef({
126+
owner: context.repo.owner,
127+
repo: context.repo.repo,
128+
ref: 'heads/main'
129+
});
130+
131+
await github.rest.git.createRef({
132+
owner: context.repo.owner,
133+
repo: context.repo.repo,
134+
ref: `refs/heads/${branchName}`,
135+
sha: ref.object.sha
136+
});
137+
138+
// Commit updated USERS.md
139+
await github.rest.repos.createOrUpdateFileContents({
140+
owner: context.repo.owner,
141+
repo: context.repo.repo,
142+
path: 'USERS.md',
143+
message: `Add ${orgName} to adopters list`,
144+
content: Buffer.from(updatedContent).toString('base64'),
145+
sha: fileData.sha,
146+
branch: branchName
147+
});
148+
149+
// Create PR
150+
const { data: pr } = await github.rest.pulls.create({
151+
owner: context.repo.owner,
152+
repo: context.repo.repo,
153+
title: `Add ${orgName} to adopters list`,
154+
head: branchName,
155+
base: 'main',
156+
body: `## New Adopter\n\n| Field | Value |\n|-------|-------|\n| **Organization** | [${orgName}](${orgUrl}) |\n| **Contact** | ${contactCell} |\n| **Description** | ${description} |\n\nCloses #${issue.number}`
157+
});
158+
159+
// Comment on issue
160+
await github.rest.issues.createComment({
161+
owner: context.repo.owner,
162+
repo: context.repo.repo,
163+
issue_number: issue.number,
164+
body: `PR created: ${pr.html_url}\n\nA maintainer will review and merge it shortly. Thanks for using Parseable!`
165+
});

.github/workflows/cla.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ jobs:
3131
path-to-document: 'https://github.com/parseablehq/.github/blob/main/CLA.md' # e.g. a CLA or a DCO document
3232
# branch should not be protected
3333
branch: 'main'
34-
allowlist: dependabot[bot],deepsource-autofix[bot],deepsourcebot
34+
allowlist: dependabot[bot],deepsource-autofix[bot],deepsourcebot,github-actions[bot]
3535

3636
# the followings are the optional inputs - If the optional inputs are not given, then default values will be taken
3737
#remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
name: Sync Adopters to README
2+
3+
on:
4+
push:
5+
branches: [main]
6+
paths: [USERS.md]
7+
8+
permissions:
9+
contents: write
10+
11+
jobs:
12+
sync:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
17+
- name: Extract adopters and update README
18+
run: |
19+
python3 - <<'SCRIPT'
20+
import re
21+
22+
# Read USERS.md and extract Organization + Description columns
23+
with open("USERS.md") as f:
24+
lines = f.read().strip().split("\n")
25+
26+
def esc_cell(value: str) -> str:
27+
return value.replace("\n", " ").replace("|", r"\|").strip()
28+
29+
rows = []
30+
for line in lines:
31+
if not line.startswith("|"):
32+
continue
33+
parts = [p.strip() for p in line.split("|")]
34+
# parts: ['', org, contact, desc, '']
35+
if len(parts) < 4:
36+
continue
37+
org = parts[1]
38+
if org == "Organization" or org.startswith("---") or org.startswith("--"):
39+
continue
40+
desc = parts[3]
41+
rows.append(f"| {esc_cell(org)} | {esc_cell(desc)} |")
42+
43+
table = "| Organization | Description of Use |\n| --- | --- |\n" + "\n".join(rows)
44+
45+
# Replace content between markers in README.md
46+
with open("README.md") as f:
47+
readme = f.read()
48+
49+
pattern = re.compile(
50+
r"(<!-- ADOPTERS:START -->\n).*?(\n<!-- ADOPTERS:END -->)",
51+
flags=re.DOTALL,
52+
)
53+
readme, replaced = pattern.subn(
54+
rf"\g<1>{table}\g<2>", readme, count=1
55+
)
56+
if replaced != 1:
57+
raise SystemExit("Expected exactly one ADOPTERS block in README.md")
58+
59+
with open("README.md", "w") as f:
60+
f.write(readme)
61+
SCRIPT
62+
63+
- name: Check for changes
64+
id: diff
65+
run: git diff --quiet README.md || echo "changed=true" >> "$GITHUB_OUTPUT"
66+
67+
- name: Commit and push
68+
if: steps.diff.outputs.changed == 'true'
69+
run: |
70+
git config user.name "github-actions[bot]"
71+
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
72+
git add README.md
73+
git commit -m "docs: sync adopters from USERS.md to README"
74+
git push

README.md

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,18 @@
1313
[![Docs](https://img.shields.io/badge/stable%20docs-parseable.com%2Fdocs-brightgreen?style=flat&color=%2373DC8C&label=Docs)](https://logg.ing/docs)
1414
[![Build](https://img.shields.io/github/checks-status/parseablehq/parseable/main?style=flat&color=%2373DC8C&label=Checks)](https://github.com/parseablehq/parseable/actions)
1515

16-
[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)
16+
[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)
1717

1818
</div>
1919

20-
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).
20+
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).
21+
22+
<div align="center">
23+
<h3>
24+
<a href="https://app.parseable.com">Try Parseable Cloud — Start Free ↗︎</a>
25+
</h3>
26+
<p><i>The fastest way to get started. No infrastructure to manage.</i></p>
27+
</div>
2128

2229
<div align="center">
2330
<a href="http://www.youtube.com/watch?feature=player_embedded&v=gYn3pFAfrVA" target="_blank">
@@ -103,6 +110,17 @@ This section elaborates available options to run Parseable in production or deve
103110
- [OAuth2 support ↗︎](https://www.parseable.com/docs/features/oepnid)
104111
- [OpenTelemetry support ↗︎](https://www.parseable.com/docs/OpenTelemetry/logs)
105112

113+
## Adopters :handshake:
114+
115+
Organizations using Parseable in production. [Add yours here](https://github.com/parseablehq/parseable/issues/new?template=add-adopter.yml) — no fork needed!
116+
117+
<!-- ADOPTERS:START -->
118+
| Organization | Description of Use |
119+
| --- | --- |
120+
| [HireXL](https://www.hirexl.in/) | Frontend application logging |
121+
| [Elfsquad](https://elfsquad.io) | Centralized application/infrastructure logging |
122+
<!-- ADOPTERS:END -->
123+
106124
## Verify images :writing_hand:
107125

108126
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:

0 commit comments

Comments
 (0)