Skip to content

Add contrib card#113

Open
YogeshJain96 wants to merge 1 commit into
postgres:mainfrom
YogeshJain96:contrib-card
Open

Add contrib card#113
YogeshJain96 wants to merge 1 commit into
postgres:mainfrom
YogeshJain96:contrib-card

Conversation

@YogeshJain96

@YogeshJain96 YogeshJain96 commented Jul 15, 2026

Copy link
Copy Markdown

Here is a little idea to generate a shareable contributor card, inspired by https://contribcard.cncf.io/ and using commitfest.postgresql.org as the data source. Would love your thoughts and feedbacks on this.

Screenshot 2026-07-16 at 9 36 42 AM

Local Test

Once we run the dev server, we can generate shareable stats card like:

admin-card prolific-author-card

Files changed

File What
pgcommitfest/urls.py Routes for the page + two JSON APIs
pgcommitfest/commitfest/views.py contrib_card, contrib_card_lookup, contrib_card_stats
pgcommitfest/commitfest/templates/contrib_card.html The card page (vanilla JS, no jQuery)
pgcommitfest/commitfest/templates/base.html Include hook
media/commitfest/logo.svg Card logo

URLs

URL Handler Purpose
/contrib-card/ contrib_card Blank card page
/contrib-card/<handle> contrib_card Pre-filled card (auto-loads)
/contrib-card/lookup/?q=<text> contrib_card_lookup Name/handle → matching users (JSON)
/contrib-card/stats/?id=<id> contrib_card_stats User id → aggregated stats (JSON)

How it works

  1. Open the page/contrib-card/ (blank) or /contrib-card/<handle> (pre-filled). Same Django page; the handle just lands in the input box.
  2. Search — Type a name/handle (or it auto-runs from the URL). Page calls lookup/.
  3. Lookup — Server finds users who are patch authors and returns matches (name + id).
  4. Pick one — One match is used directly; many matches show buttons to choose.
  5. Get stats — Page calls stats/?id=.... Server aggregates that person's patches.
  6. Show the card — Card is drawn from the data. Name/handle come from the DB, not what you typed.
  7. Share / Download — "Copy Link" gives a clean /contrib-card/<handle> URL; "Download PNG" renders the card to an image in the browser.

One line: URL/typing → find user → fetch their stats → render card → share or download.

Data flow

                              CONTRIB CARD — DATA FLOW
                              ========================

┌──────────────────────────────────────────────────────────────────────────────┐
│  ENTRY POINTS (URLs → urls.py)                                                  │
│                                                                                 │
│   /contrib-card/                 ─┐                                             │
│   /contrib-card/<handle>         ─┼──▶  views.contrib_card(request, handle)     │
│                                   │       renders contrib_card.html             │
│                                   │       context = { q: handle or "" }         │
│                                   │                                             │
│   /contrib-card/lookup/?q=<text> ─┼──▶  views.contrib_card_lookup   (JSON API)  │
│   /contrib-card/stats/?id=<id>   ─┴──▶  views.contrib_card_stats    (JSON API)  │
└──────────────────────────────────────────────────────────────────────────────┘


  SERVER (Django)                                CLIENT (contrib_card.html, vanilla JS)
  ═══════════════                                ════════════════════════════════════

  contrib_card(handle)
        │  renders page,
        │  handle injected as
        │  <div.cc-page data-q="handle">
        ▼
   ─────────────────────────────────────────▶   initFromUrl()
                                                   reads .cc-page[data-q]
                                                   if present → $("q").value = handle
                                                                find()
                                                        ▲
                                          (also: user types name + Enter/Go button)
                                                        │
                                                        ▼
                                                   find()
                                                   • q = input value
                                                   • history.replaceState(
                                                       "/contrib-card/<q>")   ← pretty URL
                                                   • GET lookup/?q=<q> ──┐
        ┌──────────────────────────────────────────────────────────────┘
        ▼
  contrib_card_lookup(q)
   • User.objects.filter(
       active, username/first/last icontains q,
       patch_author not null)[:20]
   • returns {values:[{id, value:"Name (handle)"}]}
        │
        └──────────────────────────────────────▶  parseLookup()
                                                   • 0 results → error
                                                   • 1 result  → build(person)
                                                   • N results → render match buttons
                                                                 click → build(person)
                                                        │
                                                        ▼
                                                   build(person)
                                                   • GET stats/?id=<person.id> ──┐
        ┌──────────────────────────────────────────────────────────────────────┘
        ▼
  contrib_card_stats(id)
   • user = User(pk=id)
   • Patch.objects.filter(authors=user)
       .prefetch(tags, patchoncommitfest)
       .select_related(targetversion)
   • aggregate per patch:
       - total / committed / active
       - distinct commitfests   → cfsActive
       - tag_counts             → topTags[:6]
       - committed+version      → versions
       - rows sorted by recency → recent[:8]
   • returns JSON {name, handle, totalPatches,
       committed, active, cfsActive,
       topTags, recent, versions}
        │
        └──────────────────────────────────────▶  build() cont'd
                                                   • name/handle from stats (authoritative)
                                                   • cardHtml(person, stats) → #cardWrap
                                                   • show #actions:
                                                     ├─ Download PNG
                                                     │    html-to-image (CDN lib)
                                                     │    → renders .card node to PNG
                                                     └─ Copy Link
                                                          location.origin
                                                          + "/contrib-card/<handle>"
                                                                    │
                                                                    ▼
                                                          shareable pretty URL
                                                          (re-enters at top ▲)

Notes

  • Two-hop fetch: always lookup (text → id) then stats (id → numbers).
  • DB is authoritative: displayed name/handle come from the stats response, not user input.
  • Pretty URLs only: both search and "Copy Link" produce /contrib-card/<handle>.
  • PNG is client-side: rendered in the browser via the html-to-image CDN lib; the server never builds the image.

@YogeshJain96
YogeshJain96 marked this pull request as ready for review July 16, 2026 06:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant