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..a2e21a5f2 --- /dev/null +++ b/.github/workflows/add-adopter.yaml @@ -0,0 +1,165 @@ +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().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, + 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; + } + + // Sanitize user input for markdown table + const sanitize = (str) => str.replace(/\|/g, '\\|').replace(/\n/g, ' ').trim(); + + // Build contact link + let contactCell = sanitize(contact); + if (contact.startsWith('@')) { + const handle = contact.substring(1).trim(); + contactCell = `[@${handle}](https://github.com/${handle})`; + } + + // Append new row to USERS.md + const safeDesc = sanitize(description); + const safeOrgName = sanitize(orgName); + const newRow = `| [${safeOrgName}](${orgUrl}) | ${contactCell} | ${safeDesc} |`; + 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/.github/workflows/sync-adopters.yaml b/.github/workflows/sync-adopters.yaml new file mode 100644 index 000000000..c476fa8d7 --- /dev/null +++ b/.github/workflows/sync-adopters.yaml @@ -0,0 +1,74 @@ +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") + + def esc_cell(value: str) -> str: + return value.replace("\n", " ").replace("|", r"\|").strip() + + 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"| {esc_cell(org)} | {esc_cell(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() + + pattern = re.compile( + r"(\n).*?(\n)", + 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) + 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..4dab307d9 100644 --- a/README.md +++ b/README.md @@ -13,11 +13,18 @@ [](https://logg.ing/docs) [](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 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 [app.parseable.com ↗︎](https://app.parseable.com). + +
The fastest way to get started. No infrastructure to manage.
+