Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Project: Release Notes Service #66

Open
MarshallOfSound opened this issue Aug 13, 2021 · 0 comments
Open

Project: Release Notes Service #66

MarshallOfSound opened this issue Aug 13, 2021 · 0 comments

Comments

@MarshallOfSound
Copy link
Member

MarshallOfSound commented Aug 13, 2021

Clerk currently serves a single purpose, validating that every PR has a valid Notes: .* block in the PR body and then storing those notes on merge to ensure they are frozen.

As part of electron/electron#30516 we need to extend Clerk to be the primary source of complete release notes generation. Implementation aside Clerk should implement a single API endpoint

POST /notes

Request Body

{
  "startVersion": "9.1.2",
  "endVersion": "10.0.1",
}

Response Body

Request Started: 201 Created

If the cache does not have notes between the given versions, we should queue notes generation and return a 201 Created status code immediately with an empty body.

Request In Progress: 202 Accepted

If the cache does not have notes between the given versions and a notes generation for the given range is already queued or in progress we should return a 202 Accepted status code with an empty response body.

Notes Complete: 200 OK
type Change = {
  type: "fix" | "feature" | "breaking_change" | "docs";
  // Some notes are multi-line, this should be an array of each line
  description: string[];
  // PR that caused this change
  prNumber: number;
  // If the PR that caused this change was a backport, this field should be populated
  // as the number of the original PR that landed to "main"
  originalPrNumber: number;
  // The version this change was first released in, in the context of this release notes request
  // just because this change may have been released in other versions it doesn't matter
  landedInVersion: string;
};

type Response = {
  // Changes that landed between startVersion and endVersion
  additions: Change[];
  // Changes that were available in startVersion but were backported
  // to a version in the endVersion major line AFTER the endVersion
  // i.e. in order to retain the change you have to update to a newer
  // patch version of endVersion
  removals: Change[];
}

Algorithm for determining changes

Given a startVersion and endVersion each with known properties major, minor, patch, preReleaseTag and preReleaseNumber.

Version Major Minor Patch Pre Release Tag Pre Release Number
12.0.0-alpha.1 12 0 0 alpha 1
13.1.2 13 1 2

Very pseudo code 😄

changes = []
removals = []

# returns the last commit hash that the branch for the given major release
# shares with the "main" branch.
# This can be simplified as the "newest common ancestor between main and majorRelease"
def branchPoint(majorRelease);

# returns a "Change" object for the given commit or null if no change could be
# identified.
# This should follow normal logic of:
# --> Find PR that landed commit
# --> Pull notes from PR
# --> Check if PR was backport, if so find original PR
def buildChange(commit);

# returns True if no change in the given list matches / is identical to the provided
# change.  This is determined via either the PR numbers matching (backport of th
# same PR) or the description strings being idential.
def noChangeMatches(changeList, change);

already_present_changes = []
duplicated_changes = []

for commit in range(branchPoint(startVersion.major), startVersion):
  change = buildChange(commit)
  if change is not None:
    already_present_changes.append(change)

for commit in range(branchPoint(startVersion.major), endVersion):
  change = buildChange(commit)
  if change is not None:
    # Only include the change if it wasn't already in the startVersion
    if noChangeMatches(already_present_changes, change):
      changes.append(change)
    else:
      duplicated_changes.append(change)

# Any change that was already present after the branch point but we did not find a duplicate of
# in the target version should be considered "removed"
removals = inverse_intersect(already_present_changes, duplicated_changes)

Every single part of this logic needs to be heavily cached in PSQL / Redis. We should only calculate the release notes between two versions once regardless of how many people request it.

All GitHub API requests should be heavily cached internally as well, requests for commit information can be cached indefinitely. PR information should not be cached. Notes should be cached indefinitely, if we need to cache bust we should be able to do so by re-deploying Clerk with an incremented cache key.

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

No branches or pull requests

1 participant