Skip to content

feat(release): Add emoji reactions support for Releases#38415

Open
SudhanshuMatrix wants to merge 5 commits into
go-gitea:mainfrom
SudhanshuMatrix:feat/release-reactions
Open

feat(release): Add emoji reactions support for Releases#38415
SudhanshuMatrix wants to merge 5 commits into
go-gitea:mainfrom
SudhanshuMatrix:feat/release-reactions

Conversation

@SudhanshuMatrix

Copy link
Copy Markdown
Contributor

Description

This pull request implements the emoji reactions feature for Gitea Releases, bringing feature parity with GitHub. Community members can now react to software releases using standard emojis (👍, 👎, 😄, 🎉, etc.) to show appreciation and give feedback.

Fixes #34268

Screenshots

Before

image

After

image

Key Changes

  • Database Schema: Added ReleaseReaction model, supporting database queries, creation, deletion, and user association for release-specific reactions. Added a migration (v343) to create the database table.
  • Backend Service Layer: Implemented validation, validation tests, and reaction storage/removal logic under services/release.
  • API and Routing: Registered /releases/:id/reactions/:action endpoints in routers/web and added ChangeReleaseReaction handler to handle interactive reactions.
  • UI & Layout:
    • Placed the reaction button (smiley face icon) in the top-right header of each release block, next to the edit pencil.
    • Displayed the list of existing reactions at the bottom of the release block.
    • Hid the bottom reactions container entirely when there are no reactions.
    • Adjusted margins (margin: 1.25rem 0 0.5rem;) to keep reactions clean, separated, and aligned flush to the left.
  • Frontend Initialization: Configured Gitea legacy feature loaders to load initCompReactionSelector on the releases page.

Testing & Verification

  • Unit and integration tests added in models/repo/release_reaction_test.go and services/release/release_test.go.
  • Verified UI interactivity and alignment (including top-right reaction button rendering and bottom margins).

@GiteaBot GiteaBot added the lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. label Jul 12, 2026
@github-actions github-actions Bot added the type/feature Completely new functionality. Can only be merged if feature freeze is not active. label Jul 12, 2026

@bircni bircni left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Findings

1. services/repository/delete.go & services/release/release.go:364Reactions are orphaned when a release or repo is deleted

DeleteReleaseByID deletes the release row and its attachments but never deletes release_reaction rows. Likewise, the repo-deletion bean list in services/repository/delete.go:150-175 cleans up &repo_model.Release{RepoID: repoID} but has no entry for release reactions — and can't trivially, since ReleaseReaction has no repo_id column (only release_id).

Result: every deleted release/repo leaks orphaned reaction rows forever.

Fix: add a cleanup step. In DeleteReleaseByID, delete reactions by release_id; for repo deletion, delete via a subquery/join on the repo's releases before the releases themselves are removed, e.g.:

sess.In("release_id", builder.Select("id").From("release").Where(builder.Eq{"repo_id": repoID})).Delete(&repo_model.ReleaseReaction{})

A regression test asserting reactions are gone after DeleteReleaseByID would lock this in.

2. routers/web/repo/release.go:719-728Benign "already reacted" / forbidden errors return HTTP 500

reaction, err := release_service.CreateReleaseReaction(ctx, ctx.Doer, rel, form.Content)
if err != nil {
    if errors.Is(err, user_model.ErrBlockedUser) {
        ctx.ServerError("ChangeReleaseReaction", err)
        return
    }
    ctx.ServerError("CreateReleaseReaction", err)   // <-- everything else is a 500
    return
}

Compare ChangeIssueReaction (routers/web/repo/issue.go), which treats ErrForbiddenIssueReaction/ErrBlockedUser as errors but logs-and-continues on the "already exists" case rather than 500ing. Here, ErrReleaseReactionAlreadyExist (easily triggered by a stale tab / double-click / concurrent request) and ErrForbiddenReleaseReaction (crafted content) both surface as a 500. Mirror the issue handler: return early only for forbidden/blocked, log.Info + break for the duplicate case.

3. routers/web/repo/release.go:99-105N+1 query loading reaction users

getReleaseInfos batch-loads all reactions in one query (good), but then loops per release calling relReactions.LoadUsers(...), issuing one user query per release. For a releases page with N releases this is N user queries. Minor, but the batching intent is undercut — consider collecting all user IDs across releases and loading once, or noting this as acceptable given typical page sizes.

4. models/repo/release_reaction.go (whole file) — ~330 lines largely duplicated from models/issues/reaction.go

LoadUser, RemapExternalUser, getUserIDs, valuesUser, newMigrationOriginalUser, GroupByType, HasUser, GetFirstUsers, GetMoreUserCount, and the find/create/delete logic are near-verbatim copies of the issue reaction code. newMigrationOriginalUser and valuesUser in particular already exist elsewhere. This is a maintainability concern — the two implementations will drift. Worth a maintainer discussion on whether reactions can share a generic implementation (parameterized by the owning entity) rather than copy-paste per entity type.

5. templates/repo/release/list.tmpl:39-44 & reactions.tmplTwo reaction "add" buttons when reactions exist

The list template places add_reaction (smiley) in the top-right header, and the bottom reactions.tmpl also renders add_reaction at the end of the reaction list ({{if AllowedReactions}}). So once a release has reactions, users see two smiley pickers. For issue comments the top picker lives in a hidden dropdown, so it doesn't visibly duplicate; here both are inline. Confirm this is the intended UX, or omit the header button (or the bottom one) to avoid redundancy.


Minor / nits

  • models/repo/release_reaction.go:229 LoadUsers does In("id", userIDs) with a possibly-empty userIDs slice (all external authors) — same copied pattern as issues, generally safe, just flagging.
  • The react/unreact handler correctly re-fetches all reactions unfiltered for the fragment render — good, consistent with the issue flow.
  • Migration copyright year 2026 and Assisted-by conventions look consistent with repo standards; NOT NULL DEFAULT(0) unique index matches the issue-reaction schema.
  • Tests are solid for the happy paths (create/duplicate/forbidden/find/delete/group). Consider adding a test for the blocked-user path in services/release and the deletion-cleanup once #1 is fixed.

just a quick review with claude

@GiteaBot GiteaBot added lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged and removed lgtm/need 2 This PR needs two approvals by maintainers to be considered for merging. labels Jul 12, 2026
@delvh

delvh commented Jul 12, 2026

Copy link
Copy Markdown
Member

Also, I think it is a really bad idea to duplicate the entire reactions logic.
If anything, we should add an enum scope to the existing reactions table which can have the fields issue, comment, release or something like that.

@delvh delvh left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's not duplicate the reactions logic

@SudhanshuMatrix

Copy link
Copy Markdown
Contributor Author

I implemented release reactions as a separate release_reaction table to avoid circular imports (since models/issues imports models/repo) and to avoid risky migrations on the existing reaction table.

The reaction table is a hot table on large Gitea instances with millions of rows. Unifying reactions would require modifying its composite unique indexes to include release_id, which could lead to locking, timeouts, or replication issues depending on the database engine. A separate table avoids those risks.

That said, I'd appreciate your feedback. Would you prefer a single polymorphic reaction table with a scope column (similar to the attachment table), even if it requires those migration changes?

@lunny

lunny commented Jul 13, 2026

Copy link
Copy Markdown
Member

A new table is better and some logics could be shared?

…ic reaction table

- Modified the existing `reaction` table to add a `release_id` column, updating the composite unique index `s` accordingly.
- Moved and unified all query, creation, deletion, formatting, and user loading logic for reactions into `models/repo/reaction.go`.
- Refactored `models/issues/reaction.go` to type-alias `issues.Reaction` and `issues.ReactionList` to `repo.Reaction` and `repo.ReactionList`, maintaining complete backwards compatibility.
- Cleaned up routers and services to use the unified model and queries.
- Removed the separate `release_reaction` table, saving duplicate logic and code size.
@SudhanshuMatrix SudhanshuMatrix requested review from bircni and delvh July 14, 2026 07:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

lgtm/blocked A maintainer has reservations with the PR and thus it cannot be merged type/feature Completely new functionality. Can only be merged if feature freeze is not active.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Emoji Reactions for Releases

5 participants