From 72a94c39cdfd23059fada29086035ebf5128bd23 Mon Sep 17 00:00:00 2001 From: Noah Talerman Date: Tue, 25 Aug 2026 10:17:28 -0400 Subject: [PATCH 01/23] Add changes --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- .github/ISSUE_TEMPLATE/feature-request.md | 2 +- android/RELEASE.md | 4 +- handbook/company/product-groups.md | 13 +- handbook/engineering/README.md | 4 +- handbook/engineering/releases.md | 6 +- handbook/product-design/README.md | 6 +- .../product-design/product-design.rituals.yml | 6 +- tools/github-manage/pkg/ghapi/projects.go | 28 ++- tools/github-manage/pkg/ghapi/suite_test.go | 1 - tools/github-manage/pkg/ghapi/workflows.go | 196 +++--------------- .../github-manage/pkg/ghapi/workflows_test.go | 54 ----- tools/github-manage/pkg/tui/hotkeys.go | 2 +- tools/github-manage/pkg/tui/ui.go | 146 +------------ tools/github-manage/pkg/tui/view.go | 14 +- 15 files changed, 75 insertions(+), 409 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index b0019ef8ef6..4c235a4c749 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,7 @@ name: 🦟 Bug report about: Report a bug to help us improve. title: '' -labels: 'bug,:product' +labels: 'bug,:product,~inbox' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 45ff43bf7cf..0b96c34058b 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -2,7 +2,7 @@ name: πŸ’‘Β  Feature request about: Propose a new feature or Fleet-maintained app. title: '' -labels: ':product' +labels: ':product,~inbox' assignees: '' --- diff --git a/android/RELEASE.md b/android/RELEASE.md index d9a010ada6d..b2b993253c4 100644 --- a/android/RELEASE.md +++ b/android/RELEASE.md @@ -148,9 +148,9 @@ This brings the version bump and CHANGELOG updates to main and removes only the After the release is published, close out the associated GitHub issues and milestone. For the canonical checklist, see [Conclude current milestone](https://fleetdm.com/handbook/engineering/releases#conclude-current-milestone): -1. **Move associated stories to the Drafting board**: Stories may not be on the [Drafting](https://github.com/orgs/fleetdm/projects/67) board (project 67) yet. For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, apply the `:product` label and remove the `:release` label so it moves onto the product drafting board. +1. **Flag associated stories for Product**: For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, apply the `:product` label and remove the `:release` label so the Product Designer knows they need to confirm and celebrate. -2. **Move associated stories to "Confirm and celebrate"**: On the [Drafting](https://github.com/orgs/fleetdm/projects/67) board, filter by the `fleetd-android-v1.X.X` milestone, and move all stories to the "Confirm and celebrate" column. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. +2. **Move associated stories to "Confirm and celebrate"**: On the relevant product group's board, filter by the `fleetd-android-v1.X.X` milestone, and move all stories to the "Confirm and celebrate" column. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. 3. **Close associated bugs**: Close the remaining (non-story) issues in the `fleetd-android-v1.X.X` milestone. diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index f27189facb9..c5aff407d1f 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -293,6 +293,7 @@ Each product group runs its own GitHub project board with the following columns, | πŸ₯ Ready for review | A pull request is open and awaiting code review. | | βœ”οΈ Awaiting QA | The change is merged and waiting for QA verification. | | βœ… Ready for release | QA has verified the change. Held until the next release boundary. | +| πŸŽ‰ Confirm & celebrate | The change has shipped. The Product Designer and Manager of Customer Support and Solutions Architecture [confirm and celebrate](https://fleetdm.com/handbook/product-design#confirm-and-celebrate) before the issue is closed. | | Done | Released. | There are no formal WIP limits today, but the group should watch for buildup in any one column. @@ -367,7 +368,7 @@ To deliver on this mission, we need a clear, repeatable process for turning an i To make a change to Fleet: - First, [write it down](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=~feature+fest%2C%3Aproduct&projects=&template=feature-request.md&title=) - - For every customer/prospect request, file a new GitHub issue. Whether the request is the same as an existing request will be determined by the Head of Product Design and a subject matter expert (SME) in the next step. + - For every customer/prospect request, file a new GitHub issue. The feature request template automatically applies the `~inbox` label so the request is easy to find alongside other untriaged requests and bugs. Whether the request is the same as an existing request will be determined by the Head of Product Design and a subject matter expert (SME) in the next step. - Then, it will be looked at by Fleet's [Head of Product Design](https://fleetdm.com/handbook/product-design#team) and a SME [unpack the "why"](https://fleetdm.com/handbook/product-design#inbox-review). - For customer/prospect requests to be looked at, they must have a Gong snippet. - Then, it will be [prioritized](https://fleetdm.com/handbook/company/product-groups#feature-fest) and written up as one or more user stories. @@ -478,9 +479,9 @@ To successfully deliver a user story, the people working on it need to know what Every user story has a product and engineering checklist that is completed before the user story is estimated. This populates the user story with the requirements, wireframes, and test plan necessary for the product group to effectively specify, estimate, implement, and test the change. The Product Designer is the DRI for completing the product checklist, and the Engineering Manager (EM) is the DRI for completing the engineering checklist. -When the Product Designer has completed the product checklist, it is moved to the "User story review" column of the drafting board and reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. +When the Product Designer has completed the product checklist, it is moved to the "User story review" column of the product group's board and reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. -When a user story completes the review process, it is moved to the "Ready to spec" column on the drafting board and assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer before moving to the "Ready to estimate" column. +When a user story completes the review process, it is moved to the "Ready to spec" column on the product group's board and assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer before moving to the "Ready to estimate" column. #### Providing context @@ -688,7 +689,7 @@ To make a feature request or advocate for a feature request from a customer or c New requests are [triaged daily](https://fleetdm.com/handbook/product-design#triage-new-requests) by the Head of Product Design. If the request meets the [criteria for prioritization](#criteria-for-prioritization), the request will be added to the upcoming feature fest (`~feature fest` label). If it doesn't, the request will be put to the side and the requester will be notified. -> **Fast for Fleeties:** Fleeties do not have to wait for "Unpacking the why" to add a request to feature fest. If you think Fleet is missing something and have described in detail what you already tried, it can be moved directly to the [🎁 Feature fest board](https://github.com/orgs/fleetdm/projects/72). Just add the `~feature fest` label, remove the `:product` label, and remove the issue from the 🦒 Drafting project. +> **Fast for Fleeties:** Fleeties do not have to wait for "Unpacking the why" to add a request to feature fest. If you think Fleet is missing something and have described in detail what you already tried, it can be moved directly to the [🎁 Feature fest board](https://github.com/orgs/fleetdm/projects/72). Just add the `~feature fest` label, remove the `:product` label, and remove the `~inbox` label if it's still present. ### Criteria for prioritization @@ -772,9 +773,9 @@ You can read our guide to diagnosing issues in Fleet on the [debugging page](htt #### Inbox -Quickly confirming and reproducing bug reports is a [priority for Fleet](https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks). When a new bug is created using the [bug report template](https://github.com/fleetdm/fleet/issues/new?template=bug-report.md), it is in the "inbox" state. Website bugs (label: `#g-website`) are triaged by the [website group](https://fleetdm.com/handbook/company/product-groups#website-group). +Quickly confirming and reproducing bug reports is a [priority for Fleet](https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks). The [bug report template](https://github.com/fleetdm/fleet/issues/new?template=bug-report.md) automatically applies the `~inbox` label, and the bug is in the "inbox" state. Website bugs (label: `#g-website`) are triaged by the [website group](https://fleetdm.com/handbook/company/product-groups#website-group). -At this state, the QA Manager is responsible for going through the inbox and adding the correct product group label. This moves the bug to the inbox on the product group's board. +At this state, the QA Manager is responsible for going through bugs labeled `~inbox` and adding the correct product group label. This moves the bug to the Inbox column on the product group's board. The QA Manager then removes the `~inbox` label. Then, it's the product groups Engineering Manager's (EM) responsibility to review bugs during standup. It's up to the Product Designer to decide if it's a bug and specify the expected behavior. diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index a99b97ad1aa..80113ff31ca 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -64,9 +64,9 @@ These categories compete for priority in the normal product pipeline so that pro The engineering output and architecture DRI reviews and triages engineering-initiated stories weekly on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and selects stories to prioritize for drafting by adding the `:product` label, placing it in the "Ready" column, and assigning an engineer. -1. The assigned engineer is responsible for completing the user story drafting process by completing the specs and [defining done](https://fleetdm.com/handbook/company/product-groups#defining-done). Move the issue into "In progress" on the drafting board and populate all TODOs in the issue description, define implementation details, and draft the first version of the test plan. +1. The assigned engineer is responsible for completing the user story drafting process by completing the specs and [defining done](https://fleetdm.com/handbook/company/product-groups#defining-done). Move the issue into "In progress" on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and populate all TODOs in the issue description, define implementation details, and draft the first version of the test plan. -2. When all sections have been populated, move it to the "User story review" column on the drafting board and assign to your EM. The EM will bring the story to [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews), and then to estimation before prioritizing into an upcoming release. +2. When all sections have been populated, move it to the "User story review" column on the Engineering board and assign to your EM. The EM will bring the story to [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews), and then to estimation before prioritizing into an upcoming release. > We prefer the term engineering-initiated stories over technical debt because the user story format helps keep us focused on our users and contributors. diff --git a/handbook/engineering/releases.md b/handbook/engineering/releases.md index 6300db28828..8bbd76e14ed 100644 --- a/handbook/engineering/releases.md +++ b/handbook/engineering/releases.md @@ -178,11 +178,11 @@ Immediately after publishing a new release of Fleet or fleetd, close out the ass 1. **Update product group boards**: In GitHub Projects, go to each product group board tracking the current release and filter by the current milestone. -2. **Move user stories to drafting board**: Select all items in "Ready for release" that have the `story` label. Apply the `:product` label. These items will move back to the product drafting board. +2. **Flag user stories for Product**: Select all items in "Ready for release" that have the `story` label. Apply the `:product` label so the Product Designer knows they need to confirm and celebrate. 3. **Confirm and close**: Make sure that all items with the `story` label have left the "Ready for release" column. Select all remaining items in the "Ready for release" column and move them to the "Closed" column. This will close the related GitHub issues. -4. **Confirm and celebrate**: Open the [Drafting](https://github.com/orgs/fleetdm/projects/67) board. Filter by the current milestone and move all stories to the "Confirm and celebrate" column. Product will close the issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. +4. **Confirm and celebrate**: On each product group's board, filter by the current milestone and move all `:product`-labeled stories to the "Confirm and celebrate" column. Product will close the issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. 5. **Close GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the current milestone. @@ -214,7 +214,7 @@ Some of our code does not go through a scheduled release process and is released In these cases there are two differences in our pull request process: - QA is done before merging the code change to the main branch. -- Tickets are not moved to "Ready for release". Bugs are closed, and user stories are moved to the product drafting board's "Confirm and celebrate" column. +- Tickets are not moved to "Ready for release". Bugs are closed, and user stories are moved to the "Confirm and celebrate" column on their product group's board. diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 25d158208d0..94383ca5252 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -34,7 +34,7 @@ Fleet's roadmap flows in this order (from highest to lowest fidelity): ### Triage new requests -The Head of Product Design is responsible for going through the inbox on the [drafting board](https://github.com/orgs/fleetdm/projects/67) and adding the correct [product group](https://fleetdm.com/handbook/company/product-groups#continuous-flow) label. +The Head of Product Design is responsible for going through new feature requests and bugs labeled `~inbox`. For each one, they add the correct [product group](https://fleetdm.com/handbook/company/product-groups#continuous-flow) label and remove `~inbox`. Adding the product group label moves the issue to the [Inbox column](https://fleetdm.com/handbook/company/product-groups#board-columns) of that product group's board. Once labeled, each Product Designer (PD) is responsible for reviewing the inbox on their product group's board and deciding whether each new request contributes to Fleet's [product maturity](https://fleetdm.com/handbook/company/product-maturity-assessment) goals for the current calendar year. If yes, the PD adds the `~product-maturity` label so the request is reviewed at the next [unpacking the why](#unpacking-the-why) call. If a request meets a different [criteria for prioritization](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization), the PD removes the "Unpacked" checkbox in the feature request issue and either prioritizes a [user story or quick win](https://fleetdm.com/handbook/company/product-groups#work-items) to bring through [fast draft or full draft](https://fleetdm.com/handbook/company/product-groups#drafting-tracks-full-draft-vs-fast-draft), or removes it from the product group board and adds it to the [feature fest](https://fleetdm.com/handbook/company/product-groups#feature-fest) board. @@ -50,7 +50,7 @@ If a customer or prospect request is missing a Gong snippet or requires addition At Fleet, like [GitLab](https://about.gitlab.com/handbook/product-development-flow/#but-wait-isnt-this-waterfall) and [other organizations](https://speakerdeck.com/mikermcneil/i-love-apis), every change to the product's UI gets [wireframed first](https://fleetdm.com/handbook/company/why-this-way#why-do-we-use-a-wireframe-first-approach). -1. Take the top user story that is assigned to you in the "Ready" column of the [drafting board](https://github.com/orgs/fleetdm/projects/67) and move it to "In progress." +1. Take the top user story that is assigned to you in the "Ready" column of [your product group's board](https://fleetdm.com/handbook/company/product-groups#current-product-groups) and move it to "In progress." 2. Create a new file inside the [Fleet product](https://www.figma.com/files/project/17318630/%F0%9F%94%9C%F0%9F%93%A6-Fleet-EE%C2%AE-(product)?fuid=1234929285759903870) Figma project by duplicating "\[TEMPLATE\] Starter file" (pinned to the top of the project). The starter file includes three predefined pages: "Cover," "Ready," and "Scratchpad." - **Cover**: This page has a component with the issue number and issue name. @@ -120,7 +120,7 @@ changing specifications while ensuring that Fleet meets our brand and quality gu You'll know it's time for expedited drafting when: - The team discovers that a drafted user story is missing crucial information that prevents contributors from continuing the development task. - A user story is taking more effort than was originally estimated, and Product Designer (PD) wants to find ways to cut aspects of planned functionality in order to still ship the improvement in the currently scheduled release. -- A user story on the drafting board hasn't been T-shirt sized and it cannot wait until the next weekly planning. +- A user story hasn't been T-shirt sized and it cannot wait until the next weekly planning. What happens during expedited drafting? diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index b53301923ab..35215a3f0ec 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -2,7 +2,7 @@ task: "πŸ¦’πŸ“Š Product design review" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "1. Clean up the '🦒 Drafting' board to only include stories we're working on and update milestones if necessary. 2. Prepare the '🎁 Feature fest' board." + description: "1. Review each product group's board to confirm only active stories remain in the Drafting lane. Update milestones if necessary. 2. Prepare the '🎁 Feature fest' board." moreInfoUrl: dri: "noahtalerman" - @@ -16,7 +16,7 @@ task: "Product design kickoff" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "1. Pull up all contributors' calendars to review time off and, if needed, update design review calendar events. 2. Create reference docs release branches for any milestones newly added to the drafting board 3. Each Product Designer pulls up the release planning project and walks the team through prioritized stories." + description: "1. Pull up all contributors' calendars to review time off and, if needed, update design review calendar events. 2. Create reference docs release branches for any milestones newly added to a product group's board 3. Each Product Designer pulls up the release planning project and walks the team through prioritized stories." moreInfoUrl: dri: "noahtalerman" - @@ -69,7 +69,7 @@ task: "User story review" startedOn: "2025-01-16" frequency: "Weekly" - description: "Review user stories in the 'In review' column on the drafting board with each product group's Product Designer, Engineering Mananger, and Quality Assurance Engineer." + description: "Review user stories in the 'User story review' column on each product group's board with that group's Product Designer, Engineering Manager, and Quality Assurance Engineer." dri: "noahtalerman" - task: "Revise quarterly roadmap blog post" diff --git a/tools/github-manage/pkg/ghapi/projects.go b/tools/github-manage/pkg/ghapi/projects.go index 461acb9d613..c5ad08b1b4a 100644 --- a/tools/github-manage/pkg/ghapi/projects.go +++ b/tools/github-manage/pkg/ghapi/projects.go @@ -28,13 +28,29 @@ var Aliases = map[string]int{ "releases": 87, } -// ProjectLabels maps project IDs to their corresponding label filters for the drafting project +// ProjectLabels maps product group project IDs to their corresponding GitHub label. var ProjectLabels = map[int]string{ - 58: "#g-mdm", // mdm project - 70: "#g-software", // g-software project - 71: "#g-orchestration", // g-orchestration project - 97: "#g-supply-chain", // g-supply-chain project - 112: "#g-byod", // g-byod project + 58: "#g-apple-at-work", // Apple @ Work + 70: "#g-auto-patching", // Auto Patching + 71: "#g-orchestration", // Orchestration + 92: "#g-website", // Website + 97: "#g-supply-chain", // Supply Chain + 106: "#g-power-to-pc", // Power to the PC + 112: "#g-byod", // BYOD +} + +// ProjectIDForLabels returns the product group project ID matching one of the given +// issue labels (e.g. "#g-byod"), and whether a match was found. +func ProjectIDForLabels(labels []Label) (int, bool) { + for _, l := range labels { + name := strings.ToLower(strings.TrimSpace(l.Name)) + for projectID, label := range ProjectLabels { + if strings.ToLower(label) == name { + return projectID, true + } + } + } + return 0, false } // ResolveProjectID resolves a project identifier (alias or numeric string) to a project ID. diff --git a/tools/github-manage/pkg/ghapi/suite_test.go b/tools/github-manage/pkg/ghapi/suite_test.go index dc8bef69113..626847c8720 100644 --- a/tools/github-manage/pkg/ghapi/suite_test.go +++ b/tools/github-manage/pkg/ghapi/suite_test.go @@ -44,7 +44,6 @@ func TestSuite(t *testing.T) { t.Run("Workflows Tests", func(t *testing.T) { t.Run("BulkAddLabel", TestBulkAddLabel) t.Run("BulkRemoveLabel", TestBulkRemoveLabel) - t.Run("BulkSprintKickoff", TestBulkSprintKickoff) t.Run("BulkMilestoneClose", TestBulkMilestoneClose) t.Run("WorkflowFunctionsSignatures", TestWorkflowFunctionsSignatures) t.Run("WorkflowsWithValidIssues", TestWorkflowsWithValidIssues) diff --git a/tools/github-manage/pkg/ghapi/workflows.go b/tools/github-manage/pkg/ghapi/workflows.go index 67b8cc023d2..43e785a294e 100644 --- a/tools/github-manage/pkg/ghapi/workflows.go +++ b/tools/github-manage/pkg/ghapi/workflows.go @@ -152,20 +152,6 @@ func CreateBulkMoveToCurrentSprintIfNotReadyQA(issues []Issue, projectID int) [] return CreateBulkSetSprintAction(filtered, projectID) } -func CreateBulkSprintKickoffActions(issues []Issue, sourceProjectID, projectID int) []Action { - logger.Infof("Creating sprint kickoff actions for %d issues (source project: %d, target project: %d)", len(issues), sourceProjectID, projectID) - - actions := CreateBulkAddIssueToProjectAction(issues, projectID) - actions = append(actions, CreateBulkAddLableAction(issues, ":release")...) - actions = append(actions, CreateBulkSyncEstimateAction(issues, sourceProjectID, projectID)...) - actions = append(actions, CreateBulkSetSprintAction(issues, projectID)...) - actions = append(actions, CreateBulkRemoveLabelAction(issues, ":product")...) - actions = append(actions, CreateBulkRemoveIssueFromProjectAction(issues, Aliases["draft"])...) - - logger.Infof("Created %d sprint kickoff actions", len(actions)) - return actions -} - func CreateBulkMilestoneCloseActions(issues []Issue) []Action { logger.Infof("Creating milestone close actions for %d issues", len(issues)) // Split issues by type @@ -197,9 +183,18 @@ func CreateBulkMilestoneCloseActions(issues []Issue) []Action { var actions []Action if len(storyIssues) > 0 { - actions = append(actions, CreateBulkAddIssueToProjectAction(storyIssues, Aliases["draft"])...) actions = append(actions, CreateBulkAddLableAction(storyIssues, ":product")...) - actions = append(actions, CreateBulkSetStatusAction(storyIssues, Aliases["draft"], "confirm and")...) + for _, issue := range storyIssues { + // Stories already live on their product group's board (there's no + // separate drafting board to move them onto) β€” resolve which board + // that is from the issue's own #g-* label and set status there. + projectID, ok := ProjectIDForLabels(issue.Labels) + if !ok { + logger.Errorf("Skipping 'confirm and celebrate' status for issue #%d: no product group label found", issue.Number) + continue + } + actions = append(actions, CreateBulkSetStatusAction([]Issue{issue}, projectID, "confirm and celebrate")...) + } actions = append(actions, CreateBulkRemoveLabelAction(storyIssues, ":release")...) } for _, is := range closeIssues { @@ -210,20 +205,6 @@ func CreateBulkMilestoneCloseActions(issues []Issue) []Action { return actions } -func CreateBulkKickOutOfSprintActions(issues []Issue, sourceProjectID int) []Action { - logger.Infof("Creating kick out of sprint actions for %d issues (source project: %d)", len(issues), sourceProjectID) - - actions := CreateBulkAddIssueToProjectAction(issues, Aliases["draft"]) - actions = append(actions, CreateBulkSetStatusAction(issues, Aliases["draft"], "estimated")...) - actions = append(actions, CreateBulkSyncEstimateAction(issues, sourceProjectID, Aliases["draft"])...) - actions = append(actions, CreateBulkAddLableAction(issues, ":product")...) - actions = append(actions, CreateBulkRemoveLabelAction(issues, ":release")...) - actions = append(actions, CreateBulkRemoveIssueFromProjectAction(issues, sourceProjectID)...) - - logger.Infof("Created %d kick out of sprint actions", len(actions)) - return actions -} - // AsyncManager takes a list of actions and a channel to process them assynchronously. // This will allow to send status back on the channel for live updates. the channel must return index of the action // and the status of the action. @@ -358,79 +339,12 @@ func BulkRemoveLabel(issues []Issue, label string) error { return nil } -// BulkSprintKickoff performs the sprint kickoff workflow for multiple issues. -// This includes adding issues to the target project, adding release labels, syncing estimates, and setting sprints. -func BulkSprintKickoff(issues []Issue, sourceProjectID, projectID int) error { - logger.Infof("Starting sprint kickoff workflow for %d issues (source: %d, target: %d)", len(issues), sourceProjectID, projectID) - - // Add ticket to the target product group project - logger.Info("Step 1/6: Adding issues to target project") - for _, issue := range issues { - err := AddIssueToProject(issue.Number, projectID) - if err != nil { - logger.Errorf("Failed to add issue #%d to project %d: %v", issue.Number, projectID, err) - return err - } - logger.Debugf("Added issue #%d to project %d", issue.Number, projectID) - } - - // Add the `:release` label to each issue - logger.Info("Step 2/6: Adding release labels") - err := BulkAddLabel(issues, ":release") - if err != nil { - return err - } - - // Sync the Estimate field from drafting project to the target product group project - logger.Info("Step 3/6: Syncing estimates") - for _, issue := range issues { - err := SyncEstimateField(issue.Number, sourceProjectID, projectID) - if err != nil { - logger.Errorf("Failed to sync estimate for issue #%d: %v", issue.Number, err) - return err - } - logger.Debugf("Synced estimate for issue #%d", issue.Number) - } - - // Set the sprint to the current sprint - logger.Info("Step 4/6: Setting current sprint") - for _, issue := range issues { - err := SetCurrentSprint(issue.Number, projectID) - if err != nil { - logger.Errorf("Failed to set current sprint for issue #%d: %v", issue.Number, err) - return err - } - logger.Debugf("Set current sprint for issue #%d", issue.Number) - } - - // Remove the `:product` label from each issue - logger.Info("Step 5/6: Removing product labels") - err = BulkRemoveLabel(issues, ":product") - if err != nil { - return err - } - - // Remove from the drafting project - logger.Info("Step 6/6: Removing from drafting project") - draftingProjectID := Aliases["draft"] - for _, issue := range issues { - err := RemoveIssueFromProject(issue.Number, draftingProjectID) - if err != nil { - logger.Errorf("Failed to remove issue #%d from drafting project: %v", issue.Number, err) - return err - } - logger.Debugf("Removed issue #%d from drafting project", issue.Number) - } - - logger.Info("Sprint kickoff workflow completed successfully") - return nil -} - // BulkMilestoneClose performs the milestone close workflow for multiple issues. -// This includes moving issues back to the drafting project and removing them from product group projects. +// Stories already live on their product group's board, so this sets status +// directly on that board (resolved per-issue from its #g-* label) instead of +// moving anything to a separate drafting project. func BulkMilestoneClose(issues []Issue) error { logger.Infof("Starting milestone close workflow for %d issues", len(issues)) - draftingProjectID := Aliases["draft"] var storyIssues []Issue var closeIssues []Issue for _, is := range issues { @@ -456,30 +370,28 @@ func BulkMilestoneClose(issues []Issue) error { } } - // Process story issues: move to drafting, add :product, set status, remove :release + // Process story issues: add :product, set status to confirm and celebrate on + // their own product group board, remove :release if len(storyIssues) > 0 { logger.Infof("Processing %d story issues for milestone close", len(storyIssues)) - // Step 1: Add to drafting - for _, issue := range storyIssues { - err := AddIssueToProject(issue.Number, draftingProjectID) - if err != nil { - logger.Errorf("Failed to add issue #%d to drafting project: %v", issue.Number, err) - return err - } - } - // Step 2: Add :product + // Step 1: Add :product if err := BulkAddLabel(storyIssues, ":product"); err != nil { return err } - // Step 3: Set status to confirm and celebrate + // Step 2: Set status to confirm and celebrate on each issue's product group board for _, issue := range storyIssues { - err := SetIssueStatus(issue.Number, draftingProjectID, "confirm and celebrate") + projectID, ok := ProjectIDForLabels(issue.Labels) + if !ok { + logger.Errorf("Skipping issue #%d: no product group label found, cannot resolve its board", issue.Number) + continue + } + err := SetIssueStatus(issue.Number, projectID, "confirm and celebrate") if err != nil { logger.Errorf("Failed to set status for issue #%d: %v", issue.Number, err) return err } } - // Step 4: Remove :release + // Step 3: Remove :release if err := BulkRemoveLabel(storyIssues, ":release"); err != nil { return err } @@ -499,61 +411,3 @@ func BulkMilestoneClose(issues []Issue) error { logger.Info("Milestone close workflow completed successfully") return nil } - -// BulkKickOutOfSprint performs the kick out of sprint workflow for multiple issues. -// This includes moving issues back to the drafting project, setting status to estimated, -// syncing estimates from source project, and updating labels. -func BulkKickOutOfSprint(issues []Issue, sourceProjectID int) error { - logger.Infof("Starting kick out of sprint workflow for %d issues (source: %d)", len(issues), sourceProjectID) - - // Add issues to the drafting project - logger.Info("Step 1/5: Adding issues to drafting project") - draftingProjectID := Aliases["draft"] - for _, issue := range issues { - err := AddIssueToProject(issue.Number, draftingProjectID) - if err != nil { - logger.Errorf("Failed to add issue #%d to drafting project: %v", issue.Number, err) - return err - } - logger.Debugf("Added issue #%d to drafting project", issue.Number) - } - - // Set the status to "estimated" - logger.Info("Step 2/5: Setting status to 'estimated'") - for _, issue := range issues { - err := SetIssueStatus(issue.Number, draftingProjectID, "estimated") - if err != nil { - logger.Errorf("Failed to set status for issue #%d: %v", issue.Number, err) - return err - } - logger.Debugf("Set status for issue #%d", issue.Number) - } - - // Sync the Estimate field from source project to the drafting project - logger.Info("Step 3/5: Syncing estimates from source project") - for _, issue := range issues { - err := SyncEstimateField(issue.Number, sourceProjectID, draftingProjectID) - if err != nil { - logger.Errorf("Failed to sync estimate for issue #%d: %v", issue.Number, err) - return err - } - logger.Debugf("Synced estimate for issue #%d", issue.Number) - } - - // Add the `:product` label to each issue - logger.Info("Step 4/5: Adding product labels") - err := BulkAddLabel(issues, ":product") - if err != nil { - return err - } - - // Remove the `:release` label from each issue - logger.Info("Step 5/5: Removing release labels") - err = BulkRemoveLabel(issues, ":release") - if err != nil { - return err - } - - logger.Info("Kick out of sprint workflow completed successfully") - return nil -} diff --git a/tools/github-manage/pkg/ghapi/workflows_test.go b/tools/github-manage/pkg/ghapi/workflows_test.go index a242a2a0fa9..5ebc4c8513a 100644 --- a/tools/github-manage/pkg/ghapi/workflows_test.go +++ b/tools/github-manage/pkg/ghapi/workflows_test.go @@ -103,46 +103,6 @@ func TestBulkRemoveLabel(t *testing.T) { } } -func TestBulkSprintKickoff(t *testing.T) { - tests := []struct { - name string - issues []Issue - projectID int - }{ - { - name: "empty issues slice", - issues: []Issue{}, - projectID: 58, - }, - { - name: "single issue", - issues: []Issue{ - {Number: 123, Title: "Test Issue"}, - }, - projectID: 67, - }, - { - name: "multiple issues", - issues: []Issue{ - {Number: 123, Title: "Test Issue 1"}, - {Number: 456, Title: "Test Issue 2"}, - }, - projectID: 70, - }, - } - - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - t.Skip("BulkSprintKickoff requires GitHub CLI setup and mocking for proper testing") - // Since BulkSprintKickoff depends on multiple GitHub CLI commands, - err := BulkSprintKickoff(tt.issues, tt.projectID, tt.projectID) - if err != nil { - t.Errorf("BulkSprintKickoff should return nil (stub implementation), got: %v", err) - } - }) - } -} - func TestBulkMilestoneClose(t *testing.T) { tests := []struct { name string @@ -197,12 +157,6 @@ func TestWorkflowFunctionsSignatures(t *testing.T) { // Expected for empty slice } - // Test BulkSprintKickoff signature - err = BulkSprintKickoff(issues, 123, 123) - if err != nil { - t.Errorf("BulkSprintKickoff unexpected error: %v", err) - } - // Test BulkMilestoneClose signature err = BulkMilestoneClose(issues) if err != nil { @@ -233,14 +187,6 @@ func TestWorkflowsWithValidIssues(t *testing.T) { }, } - t.Run("BulkSprintKickoff with valid issues", func(t *testing.T) { - t.Skip("BulkSprintKickoff requires GitHub CLI setup and mocking for proper testing") - err := BulkSprintKickoff(issues, 58, 58) - if err != nil { - t.Errorf("BulkSprintKickoff should return nil, got: %v", err) - } - }) - t.Run("BulkMilestoneClose with valid issues", func(t *testing.T) { t.Skip("BulkMilestoneClose requires GitHub CLI setup and mocking for proper testing") err := BulkMilestoneClose(issues) diff --git a/tools/github-manage/pkg/tui/hotkeys.go b/tools/github-manage/pkg/tui/hotkeys.go index 2a5ae6231ca..f73683e2753 100644 --- a/tools/github-manage/pkg/tui/hotkeys.go +++ b/tools/github-manage/pkg/tui/hotkeys.go @@ -246,7 +246,7 @@ func (m *model) HandleHotkeys(msg tea.Msg) (tea.Model, tea.Cmd) { m.labelInput = "" case BulkDemoSummary: return m, m.executeWorkflow() - case BulkSprintKickoff, BulkKickOutOfSprint, BulkMoveToCurrentSprint: + case BulkMoveToCurrentSprint: if m.projectID != 0 { // Use the provided project ID return m, m.executeWorkflow() diff --git a/tools/github-manage/pkg/tui/ui.go b/tools/github-manage/pkg/tui/ui.go index 3aa2ede9fd8..4b96874071d 100644 --- a/tools/github-manage/pkg/tui/ui.go +++ b/tools/github-manage/pkg/tui/ui.go @@ -47,9 +47,7 @@ type WorkflowType int const ( BulkAddLabel WorkflowType = iota BulkRemoveLabel - BulkSprintKickoff BulkMilestoneClose - BulkKickOutOfSprint BulkDemoSummary BulkMoveToCurrentSprint ) @@ -57,9 +55,7 @@ const ( var WorkflowTypeValues = []string{ "Bulk Add Label", "Bulk Remove Label", - "Bulk Sprint Kickoff", "Bulk Milestone Close", - "Bulk Kick Out Of Sprint", "Bulk Demo Summary", "Move to current sprint", } @@ -631,126 +627,12 @@ func (m *model) executeWorkflow() tea.Cmd { Progress: 0.0, }) } - case BulkSprintKickoff: - projectID := m.projectID - if projectID == 0 && m.projectInput != "" { - // Try to resolve project ID - resolvedID, err := ghapi.ResolveProjectID(m.projectInput) - if err != nil { - return func() tea.Msg { - return workflowCompleteMsg{ - success: false, - message: fmt.Sprintf("Failed to resolve project ID: %v", err), - } - } - } - projectID = resolvedID - } - - actions = ghapi.CreateBulkSprintKickoffActions(selectedIssues, ghapi.Aliases["draft"], projectID) - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: i, - Description: fmt.Sprintf("Adding #%d issue to project %d", issue.Number, projectID), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: len(selectedIssues) + i, - Description: fmt.Sprintf("Adding ':release' label to #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (2 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Syncing estimate fields for #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (3 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Setting current sprint for #%d issue in project %d", issue.Number, projectID), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (4 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Removing ':product' label from #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (5 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Removing #%d issue from drafting project", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } case BulkMilestoneClose: actions = ghapi.CreateBulkMilestoneCloseActions(selectedIssues) - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: len(selectedIssues) + i, - Description: fmt.Sprintf("Adding #%d issue to drafting project", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: len(selectedIssues) + i, - Description: fmt.Sprintf("Adding ':product' label to #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: len(selectedIssues) + i, - Description: fmt.Sprintf("Setting status to 'confirm and celebrate' for #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: len(selectedIssues) + i, - Description: fmt.Sprintf("Removing ':release' label from #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - case BulkKickOutOfSprint: - projectID := m.projectID - if projectID == 0 && m.projectInput != "" { - // Try to resolve project ID - resolvedID, err := ghapi.ResolveProjectID(m.projectInput) - if err != nil { - return func() tea.Msg { - return workflowCompleteMsg{ - success: false, - message: fmt.Sprintf("Failed to resolve project ID: %v", err), - } - } - } - projectID = resolvedID - } - - actions = ghapi.CreateBulkKickOutOfSprintActions(selectedIssues, projectID) for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ ID: i, - Description: fmt.Sprintf("Adding #%d issue to drafting project", issue.Number), + Description: fmt.Sprintf("Adding ':product' label to #%d issue", issue.Number), Status: TaskPending, Progress: 0.0, }) @@ -758,7 +640,7 @@ func (m *model) executeWorkflow() tea.Cmd { for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ ID: len(selectedIssues) + i, - Description: fmt.Sprintf("Setting status to 'estimated' for #%d issue", issue.Number), + Description: fmt.Sprintf("Setting status to 'confirm and celebrate' for #%d issue on its product group board", issue.Number), Status: TaskPending, Progress: 0.0, }) @@ -766,35 +648,11 @@ func (m *model) executeWorkflow() tea.Cmd { for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ ID: (2 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Syncing estimate from project %d for #%d issue", projectID, issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (3 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Adding ':product' label to #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (4 * len(selectedIssues)) + i, Description: fmt.Sprintf("Removing ':release' label from #%d issue", issue.Number), Status: TaskPending, Progress: 0.0, }) } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: (4 * len(selectedIssues)) + i, - Description: fmt.Sprintf("Removing #%d issue from project %d", issue.Number, projectID), - Status: TaskPending, - Progress: 0.0, - }) - } case BulkMoveToCurrentSprint: projectID := m.projectID if projectID == 0 && m.projectInput != "" { diff --git a/tools/github-manage/pkg/tui/view.go b/tools/github-manage/pkg/tui/view.go index bcb8173d578..7a84a69909a 100644 --- a/tools/github-manage/pkg/tui/view.go +++ b/tools/github-manage/pkg/tui/view.go @@ -395,12 +395,8 @@ func (m model) selectedWorkflowDescription() string { desc = "Add a label to all selected issues. You'll be prompted to type the label name." case BulkRemoveLabel: desc = "Remove a label from all selected issues. You'll be prompted to type the label name." - case BulkSprintKickoff: - desc = "Add selected issues to a project and set initial sprint kickoff fields (status, estimate sync, labels)." case BulkMilestoneClose: - desc = "For selected stories: adds to drafting, sets status to 'confirm and celebrate', removes :release, and adds :product. For selected bugs and ~sub-task: closes the issues. Does not affect the milestone object itself." - case BulkKickOutOfSprint: - desc = "Remove selected issues from a project and reset sprint-related fields (status, labels)." + desc = "For selected stories: sets status to 'confirm and celebrate' on the issue's own product group board, removes :release, and adds :product. For selected bugs and ~sub-task: closes the issues. Does not affect the milestone object itself." case BulkDemoSummary: desc = "Generate a markdown summary of selected issues grouped by feature and bug, with assignees." case BulkMoveToCurrentSprint: @@ -414,7 +410,7 @@ func (m model) selectedWorkflowDescription() string { switch w { case BulkAddLabel, BulkRemoveLabel: hints = append(hints, "Input required: label name") - case BulkSprintKickoff, BulkKickOutOfSprint, BulkMoveToCurrentSprint: + case BulkMoveToCurrentSprint: hints = append(hints, "Input required: project ID or alias") if m.projectID != 0 { hints = append(hints, fmt.Sprintf("Current project in context: %d", m.projectID)) @@ -498,12 +494,8 @@ func (m model) RenderLabelInput() string { } func (m model) RenderProjectInput() string { - workflowTitle := "Sprint Kickoff" + workflowTitle := "Move To Current Sprint" promptText := "Target Project (ID or alias):" - if m.workflowType == BulkKickOutOfSprint { - workflowTitle = "Kick Out Of Sprint" - promptText = "Source Project (ID or alias):" - } s := fmt.Sprintf("\n--- %s ---\n", workflowTitle) s += fmt.Sprintf("%s %s_\n", promptText, m.projectInput) s += "Press 'enter' to execute, 'esc' to cancel.\n" From 1c8064255bade4d17a47afe6877ca8f10f8b5954 Mon Sep 17 00:00:00 2001 From: Noah Talerman Date: Tue, 25 Aug 2026 10:43:24 -0400 Subject: [PATCH 02/23] Add changes --- android/RELEASE.md | 6 ++---- handbook/engineering/releases.md | 10 ++++------ tools/github-manage/pkg/ghapi/workflows.go | 13 ++++--------- tools/github-manage/pkg/tui/ui.go | 10 +--------- 4 files changed, 11 insertions(+), 28 deletions(-) diff --git a/android/RELEASE.md b/android/RELEASE.md index b2b993253c4..a6009e6439d 100644 --- a/android/RELEASE.md +++ b/android/RELEASE.md @@ -148,11 +148,9 @@ This brings the version bump and CHANGELOG updates to main and removes only the After the release is published, close out the associated GitHub issues and milestone. For the canonical checklist, see [Conclude current milestone](https://fleetdm.com/handbook/engineering/releases#conclude-current-milestone): -1. **Flag associated stories for Product**: For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, apply the `:product` label and remove the `:release` label so the Product Designer knows they need to confirm and celebrate. +1. **Confirm and celebrate**: For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, remove the `:release` label and move it to the "Confirm and celebrate" column on its product group's board. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. -2. **Move associated stories to "Confirm and celebrate"**: On the relevant product group's board, filter by the `fleetd-android-v1.X.X` milestone, and move all stories to the "Confirm and celebrate" column. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. - -3. **Close associated bugs**: Close the remaining (non-story) issues in the `fleetd-android-v1.X.X` milestone. +2. **Close associated bugs**: Close the remaining (non-story) issues in the `fleetd-android-v1.X.X` milestone. 4. **Close the GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the `fleetd-android-v1.X.X` milestone. diff --git a/handbook/engineering/releases.md b/handbook/engineering/releases.md index 8bbd76e14ed..2b99bdadc07 100644 --- a/handbook/engineering/releases.md +++ b/handbook/engineering/releases.md @@ -178,17 +178,15 @@ Immediately after publishing a new release of Fleet or fleetd, close out the ass 1. **Update product group boards**: In GitHub Projects, go to each product group board tracking the current release and filter by the current milestone. -2. **Flag user stories for Product**: Select all items in "Ready for release" that have the `story` label. Apply the `:product` label so the Product Designer knows they need to confirm and celebrate. +2. **Confirm and celebrate**: Select all items in "Ready for release" that have the `story` label and move them to the "Confirm and celebrate" column on their product group's board. Product will close the issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. 3. **Confirm and close**: Make sure that all items with the `story` label have left the "Ready for release" column. Select all remaining items in the "Ready for release" column and move them to the "Closed" column. This will close the related GitHub issues. -4. **Confirm and celebrate**: On each product group's board, filter by the current milestone and move all `:product`-labeled stories to the "Confirm and celebrate" column. Product will close the issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. +4. **Close GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the current milestone. -5. **Close GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the current milestone. +5. Announce that the release milestone has been closed in #help-engineering. -6. Announce that the release milestone has been closed in #help-engineering. - -7. Visit the [confidential repo variables](https://github.com/fleetdm/confidential/settings/variables/actions) page and update the `QAWOLF_DEPLOY_TAG` repository variable to `main` so that the latest code is deployed to QA Wolf every morning. +6. Visit the [confidential repo variables](https://github.com/fleetdm/confidential/settings/variables/actions) page and update the `QAWOLF_DEPLOY_TAG` repository variable to `main` so that the latest code is deployed to QA Wolf every morning. ## Update the Fleet releases calendar diff --git a/tools/github-manage/pkg/ghapi/workflows.go b/tools/github-manage/pkg/ghapi/workflows.go index 43e785a294e..d0280dab880 100644 --- a/tools/github-manage/pkg/ghapi/workflows.go +++ b/tools/github-manage/pkg/ghapi/workflows.go @@ -183,7 +183,6 @@ func CreateBulkMilestoneCloseActions(issues []Issue) []Action { var actions []Action if len(storyIssues) > 0 { - actions = append(actions, CreateBulkAddLableAction(storyIssues, ":product")...) for _, issue := range storyIssues { // Stories already live on their product group's board (there's no // separate drafting board to move them onto) β€” resolve which board @@ -370,15 +369,11 @@ func BulkMilestoneClose(issues []Issue) error { } } - // Process story issues: add :product, set status to confirm and celebrate on - // their own product group board, remove :release + // Process story issues: set status to confirm and celebrate on their own + // product group board, remove :release if len(storyIssues) > 0 { logger.Infof("Processing %d story issues for milestone close", len(storyIssues)) - // Step 1: Add :product - if err := BulkAddLabel(storyIssues, ":product"); err != nil { - return err - } - // Step 2: Set status to confirm and celebrate on each issue's product group board + // Step 1: Set status to confirm and celebrate on each issue's product group board for _, issue := range storyIssues { projectID, ok := ProjectIDForLabels(issue.Labels) if !ok { @@ -391,7 +386,7 @@ func BulkMilestoneClose(issues []Issue) error { return err } } - // Step 3: Remove :release + // Step 2: Remove :release if err := BulkRemoveLabel(storyIssues, ":release"); err != nil { return err } diff --git a/tools/github-manage/pkg/tui/ui.go b/tools/github-manage/pkg/tui/ui.go index 4b96874071d..fcdd078ef34 100644 --- a/tools/github-manage/pkg/tui/ui.go +++ b/tools/github-manage/pkg/tui/ui.go @@ -632,14 +632,6 @@ func (m *model) executeWorkflow() tea.Cmd { for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ ID: i, - Description: fmt.Sprintf("Adding ':product' label to #%d issue", issue.Number), - Status: TaskPending, - Progress: 0.0, - }) - } - for i, issue := range selectedIssues { - m.tasks = append(m.tasks, WorkflowTask{ - ID: len(selectedIssues) + i, Description: fmt.Sprintf("Setting status to 'confirm and celebrate' for #%d issue on its product group board", issue.Number), Status: TaskPending, Progress: 0.0, @@ -647,7 +639,7 @@ func (m *model) executeWorkflow() tea.Cmd { } for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ - ID: (2 * len(selectedIssues)) + i, + ID: len(selectedIssues) + i, Description: fmt.Sprintf("Removing ':release' label from #%d issue", issue.Number), Status: TaskPending, Progress: 0.0, From 30a9820d65813494e1d8513b4a1d56798ab1999d Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:55:30 -0400 Subject: [PATCH 03/23] Update bug-report.md --- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 4c235a4c749..465da720009 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,7 @@ name: 🦟 Bug report about: Report a bug to help us improve. title: '' -labels: 'bug,:product,~inbox' +labels: 'bug,~inbox' assignees: '' --- From 22b3dcc354920ff04db02a448f34093aeb7c3a15 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:55:46 -0400 Subject: [PATCH 04/23] Apply suggestion from @noahtalerman --- .github/ISSUE_TEMPLATE/feature-request.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 0b96c34058b..2707e71e0d2 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -2,7 +2,7 @@ name: πŸ’‘Β  Feature request about: Propose a new feature or Fleet-maintained app. title: '' -labels: ':product,~inbox' +labels: '~inbox' assignees: '' --- From fae5edcc540bf9b7b8b3c2ffb76d2a2282b36013 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:56:47 -0400 Subject: [PATCH 05/23] Apply suggestion from @noahtalerman --- android/RELEASE.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android/RELEASE.md b/android/RELEASE.md index a6009e6439d..3aee9ac0582 100644 --- a/android/RELEASE.md +++ b/android/RELEASE.md @@ -148,7 +148,7 @@ This brings the version bump and CHANGELOG updates to main and removes only the After the release is published, close out the associated GitHub issues and milestone. For the canonical checklist, see [Conclude current milestone](https://fleetdm.com/handbook/engineering/releases#conclude-current-milestone): -1. **Confirm and celebrate**: For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, remove the `:release` label and move it to the "Confirm and celebrate" column on its product group's board. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. +1. **Prepare for confirm and celebrate**: For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, move it to the "Confirm and celebrate" column on its product group's board. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. 2. **Close associated bugs**: Close the remaining (non-story) issues in the `fleetd-android-v1.X.X` milestone. From 5e449a5a21ec0f28ce81cdff57b534ceea8e3235 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:58:08 -0400 Subject: [PATCH 06/23] Apply suggestion from @noahtalerman --- handbook/company/product-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index c5aff407d1f..e7849186a46 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -368,7 +368,7 @@ To deliver on this mission, we need a clear, repeatable process for turning an i To make a change to Fleet: - First, [write it down](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=~feature+fest%2C%3Aproduct&projects=&template=feature-request.md&title=) - - For every customer/prospect request, file a new GitHub issue. The feature request template automatically applies the `~inbox` label so the request is easy to find alongside other untriaged requests and bugs. Whether the request is the same as an existing request will be determined by the Head of Product Design and a subject matter expert (SME) in the next step. + - For every customer/prospect request, file a new GitHub issue. The feature request template automatically applies the `~inbox` label so the request is easy to find alongside other untriaged requests and bugs. Whether the request is the same as an existing request will be determined by the relevant product group's Product Designer and a subject matter expert (SME) in the next step. - Then, it will be looked at by Fleet's [Head of Product Design](https://fleetdm.com/handbook/product-design#team) and a SME [unpack the "why"](https://fleetdm.com/handbook/product-design#inbox-review). - For customer/prospect requests to be looked at, they must have a Gong snippet. - Then, it will be [prioritized](https://fleetdm.com/handbook/company/product-groups#feature-fest) and written up as one or more user stories. From 3d91fde4323c6defb16c15e90ae6a8d06586540d Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 11:59:56 -0400 Subject: [PATCH 07/23] Apply suggestion from @noahtalerman --- handbook/company/product-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index e7849186a46..21a70e6b3be 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -479,7 +479,7 @@ To successfully deliver a user story, the people working on it need to know what Every user story has a product and engineering checklist that is completed before the user story is estimated. This populates the user story with the requirements, wireframes, and test plan necessary for the product group to effectively specify, estimate, implement, and test the change. The Product Designer is the DRI for completing the product checklist, and the Engineering Manager (EM) is the DRI for completing the engineering checklist. -When the Product Designer has completed the product checklist, it is moved to the "User story review" column of the product group's board and reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. +When the Product Designer has completed the product checklist, it is reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. When a user story completes the review process, it is moved to the "Ready to spec" column on the product group's board and assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer before moving to the "Ready to estimate" column. From fe7bb91a84bf1ef7cb7034a12d5cebc6089939d2 Mon Sep 17 00:00:00 2001 From: Noah Talerman Date: Tue, 25 Aug 2026 12:03:39 -0400 Subject: [PATCH 08/23] Add changes --- handbook/company/product-groups.md | 6 +++--- handbook/product-design/README.md | 4 ++-- handbook/product-design/product-design.rituals.yml | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index 21a70e6b3be..f3619516b13 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -343,11 +343,11 @@ The product group walks the board **right-to-left**, starting at "Ready for rele ### User story reviews -User story reviews [happen weekly](https://fleetdm.com/handbook/product-design#rituals) between each product group's Product Designer (PD), Engineering Manager (EM), Tech Lead (TL) and Quality Assurance (QA) Engineer. During the call, contributors (PD and EM) present all user stories that are in the "User story review" column. The PD is the DRI for completing all product checklist items before bringing to review. For [engineer-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story), the EM is the DRI for completing all engineering checklist items before bringing to review. +User story reviews [happen weekly](https://fleetdm.com/handbook/product-design#rituals) between each product group's Product Designer (PD), Engineering Manager (EM), Tech Lead (TL) and Quality Assurance (QA) Engineer. During the call, contributors (PD and EM) present all user stories that have completed the product checklist and are ready for review. The PD is the DRI for completing all product checklist items before bringing to review. For [engineer-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story), the EM is the DRI for completing all engineering checklist items before bringing to review. The purpose of the review is to familiarize the EM, TL, and QA Engineer with the user story, and provide an opportunity to ask questions, clarify requirements, and highlight potential implementation issues. The first draft of the test plan produced by the Product Designer is reviewed and revised as needed during the call. The QA Engineer is the DRI for finalizing the test plan. -The purpose of the user story review is to align product, engineering, and QA on functionality and implementation details. Wireframe reviews occur daily during [design reviews](https://fleetdm.com/handbook/company/product-groups#design-reviews) where contributors are welcome to join and provide design feedback in the agenda document. However, sometimes there are design changes needed if a gap is discovered or an implementation issue is raised during user story review. If there are design changes, the user story is moved back to the "In progress" column for additional drafting. If there are no design changes, the story remains with the Engineering DRI to [complete the drafting process](#defining-done) before bringing to estimation. If no Engineering DRI is assigned, the ticket is assigned to the Engineering Manager. +The purpose of the user story review is to align product, engineering, and QA on functionality and implementation details. Wireframe reviews occur daily during [design reviews](https://fleetdm.com/handbook/company/product-groups#design-reviews) where contributors are welcome to join and provide design feedback in the agenda document. However, sometimes there are design changes needed if a gap is discovered or an implementation issue is raised during user story review. If there are design changes, the user story goes back into drafting to address the gap. If there are no design changes, the story remains with the Engineering DRI to [complete the drafting process](#defining-done) before bringing to estimation. If no Engineering DRI is assigned, the ticket is assigned to the Engineering Manager. ### Release demo (every 3 weeks) @@ -481,7 +481,7 @@ Every user story has a product and engineering checklist that is completed befor When the Product Designer has completed the product checklist, it is reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. -When a user story completes the review process, it is moved to the "Ready to spec" column on the product group's board and assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer before moving to the "Ready to estimate" column. +When a user story completes the review process, it is assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer before bringing the story to estimation. The story stays in the **Drafting** column throughout β€” the drafting process is tracked through the checklists and assignee, not by adding more columns to the board. #### Providing context diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 94383ca5252..5a62d49d30a 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -103,9 +103,9 @@ Additionally: ### Ensure story drafting is complete -Once a story is approved in [design review](https://fleetdm.com/handbook/company/product-groups#design-reviews), the Product Designer brings the story to [user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews). Afterwards, the Product Designer moves the user story to the "Ready to spec" column and assigns the appropriate Tech Lead. +Once a story is approved in [design review](https://fleetdm.com/handbook/company/product-groups#design-reviews), the Product Designer brings the story to [user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews). Afterwards, the Product Designer assigns the user story to the appropriate Tech Lead to write the spec. -The EM is responsible for moving the user story to the "Specified" and "Estimated" columns. +The EM is responsible for bringing the specified user story to estimation. The story stays in the "Drafting" column on the product group's board throughout β€” there's no separate column for each drafting step. Before assigning an EM, double-check that the "Product" section of the user story [checklist](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=story&projects=&template=story.md&title=) is complete (no TODOs). diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index 35215a3f0ec..4a6d42c9b27 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -69,7 +69,7 @@ task: "User story review" startedOn: "2025-01-16" frequency: "Weekly" - description: "Review user stories in the 'User story review' column on each product group's board with that group's Product Designer, Engineering Manager, and Quality Assurance Engineer." + description: "Review user stories that have completed the product checklist and are ready for review, with that group's Product Designer, Engineering Manager, and Quality Assurance Engineer." dri: "noahtalerman" - task: "Revise quarterly roadmap blog post" From 670598ddcbd2c9f0daa78018e3f0ede3743c0eee Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:04:43 -0400 Subject: [PATCH 09/23] Apply suggestion from @noahtalerman --- handbook/company/product-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index f3619516b13..3644798c0bb 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -481,7 +481,7 @@ Every user story has a product and engineering checklist that is completed befor When the Product Designer has completed the product checklist, it is reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. -When a user story completes the review process, it is assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer before bringing the story to estimation. The story stays in the **Drafting** column throughout β€” the drafting process is tracked through the checklists and assignee, not by adding more columns to the board. +When a user story completes the review process, it is assigned to the product group's Tech Lead. The Tech Lead is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer. #### Providing context From 6fe4b5fd44eac12f1f49e6c600f92d97a42b1e58 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:05:27 -0400 Subject: [PATCH 10/23] Apply suggestion from @noahtalerman --- handbook/company/product-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index 3644798c0bb..3663d37ac54 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -481,7 +481,7 @@ Every user story has a product and engineering checklist that is completed befor When the Product Designer has completed the product checklist, it is reviewed during the [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews) rituals. -When a user story completes the review process, it is assigned to the product group's Tech Lead. The Tech Lead is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer. +When a user story completes the review process, it is assigned to the product group's EM. The EM is responsible for completing the engineering checklist and finalizing the test plan with the QA Engineer. #### Providing context From 820cc614be91375d8cf4f2d75ca040e5fb0106ab Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:11:05 -0400 Subject: [PATCH 11/23] Apply suggestion from @noahtalerman --- handbook/company/product-groups.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index 3663d37ac54..dd449f09c2c 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -775,7 +775,7 @@ You can read our guide to diagnosing issues in Fleet on the [debugging page](htt Quickly confirming and reproducing bug reports is a [priority for Fleet](https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks). The [bug report template](https://github.com/fleetdm/fleet/issues/new?template=bug-report.md) automatically applies the `~inbox` label, and the bug is in the "inbox" state. Website bugs (label: `#g-website`) are triaged by the [website group](https://fleetdm.com/handbook/company/product-groups#website-group). -At this state, the QA Manager is responsible for going through bugs labeled `~inbox` and adding the correct product group label. This moves the bug to the Inbox column on the product group's board. The QA Manager then removes the `~inbox` label. +At this state, the QA Manager is responsible for going through bugs labeled `~inbox` and adding the bug to the correct product group's board. The QA Manager then removes the `~inbox` label. Then, it's the product groups Engineering Manager's (EM) responsibility to review bugs during standup. It's up to the Product Designer to decide if it's a bug and specify the expected behavior. From 38b71c8c3988ddaf478fb437af852ab539cf55e9 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:11:13 -0400 Subject: [PATCH 12/23] Apply suggestion from @noahtalerman --- handbook/engineering/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index 80113ff31ca..a6062334500 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -64,7 +64,7 @@ These categories compete for priority in the normal product pipeline so that pro The engineering output and architecture DRI reviews and triages engineering-initiated stories weekly on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and selects stories to prioritize for drafting by adding the `:product` label, placing it in the "Ready" column, and assigning an engineer. -1. The assigned engineer is responsible for completing the user story drafting process by completing the specs and [defining done](https://fleetdm.com/handbook/company/product-groups#defining-done). Move the issue into "In progress" on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and populate all TODOs in the issue description, define implementation details, and draft the first version of the test plan. +1. The assigned engineer is responsible for completing the user story drafting process by completing the specs and [defining done](https://fleetdm.com/handbook/company/product-groups#defining-done). Move the issue into "Drafting" on the correct product group's board and populate all TODOs in the issue description, define implementation details, and draft the first version of the test plan. 2. When all sections have been populated, move it to the "User story review" column on the Engineering board and assign to your EM. The EM will bring the story to [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews), and then to estimation before prioritizing into an upcoming release. From ed8db99171c79538ff9d9e684a2dbd515eb92f46 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:11:22 -0400 Subject: [PATCH 13/23] Apply suggestion from @noahtalerman --- handbook/product-design/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 5a62d49d30a..ccb93a6d291 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -103,7 +103,7 @@ Additionally: ### Ensure story drafting is complete -Once a story is approved in [design review](https://fleetdm.com/handbook/company/product-groups#design-reviews), the Product Designer brings the story to [user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews). Afterwards, the Product Designer assigns the user story to the appropriate Tech Lead to write the spec. +Once a story is approved in [design review](https://fleetdm.com/handbook/company/product-groups#design-reviews), the Product Designer brings the story to [user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews). Afterwards, the Product Designer assigns the user story to the appropriate Engineering Manager (EM) to help write specs. The EM is responsible for bringing the specified user story to estimation. The story stays in the "Drafting" column on the product group's board throughout β€” there's no separate column for each drafting step. From f081a97416491802d9e165b274fa19c12f400458 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:11:34 -0400 Subject: [PATCH 14/23] Apply suggestion from @noahtalerman --- handbook/engineering/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index a6062334500..e8e199d9b72 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -66,7 +66,7 @@ The engineering output and architecture DRI reviews and triages engineering-init 1. The assigned engineer is responsible for completing the user story drafting process by completing the specs and [defining done](https://fleetdm.com/handbook/company/product-groups#defining-done). Move the issue into "Drafting" on the correct product group's board and populate all TODOs in the issue description, define implementation details, and draft the first version of the test plan. -2. When all sections have been populated, move it to the "User story review" column on the Engineering board and assign to your EM. The EM will bring the story to [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews), and then to estimation before prioritizing into an upcoming release. +2. When all sections have been populated, assign the story to the product group's EM. The EM will bring the story to [weekly user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews), and then to estimation before prioritizing into an upcoming release. > We prefer the term engineering-initiated stories over technical debt because the user story format helps keep us focused on our users and contributors. From 63f268449b373eddf1f3450e0d43529633e094cb Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:11:43 -0400 Subject: [PATCH 15/23] Apply suggestion from @noahtalerman --- handbook/product-design/product-design.rituals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index 4a6d42c9b27..ca0e74641dd 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -2,7 +2,7 @@ task: "πŸ¦’πŸ“Š Product design review" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "1. Review each product group's board to confirm only active stories remain in the Drafting lane. Update milestones if necessary. 2. Prepare the '🎁 Feature fest' board." + description: "1. Review each product group's board to confirm only stories we're working on remain in the "Drafting" column. Update milestones if necessary. 2. Prepare the '🎁 Feature fest' board." moreInfoUrl: dri: "noahtalerman" - From 979b3e4c30079b35b7f587915f2bd1d8bd2e42b0 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:11:52 -0400 Subject: [PATCH 16/23] Apply suggestion from @noahtalerman --- handbook/product-design/product-design.rituals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index ca0e74641dd..b6b2e94e8fd 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -16,7 +16,7 @@ task: "Product design kickoff" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "1. Pull up all contributors' calendars to review time off and, if needed, update design review calendar events. 2. Create reference docs release branches for any milestones newly added to a product group's board 3. Each Product Designer pulls up the release planning project and walks the team through prioritized stories." + description: "1. Pull up all contributors' calendars to review time off and, if needed, update design review calendar events. 2. Create reference docs release branches for any milestones newly added 3. Each Product Designer pulls up the release planning project and walks the team through prioritized stories." moreInfoUrl: dri: "noahtalerman" - From 26a6d229041c9b9b9e44a105502b14e153e1df42 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 12:12:00 -0400 Subject: [PATCH 17/23] Apply suggestion from @noahtalerman --- handbook/product-design/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index ccb93a6d291..9a91fd3d836 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -105,7 +105,7 @@ Additionally: Once a story is approved in [design review](https://fleetdm.com/handbook/company/product-groups#design-reviews), the Product Designer brings the story to [user story review](https://fleetdm.com/handbook/company/product-groups#user-story-reviews). Afterwards, the Product Designer assigns the user story to the appropriate Engineering Manager (EM) to help write specs. -The EM is responsible for bringing the specified user story to estimation. The story stays in the "Drafting" column on the product group's board throughout β€” there's no separate column for each drafting step. +The EM is responsible for bringing the specified user story to "Ready". Before assigning an EM, double-check that the "Product" section of the user story [checklist](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=story&projects=&template=story.md&title=) is complete (no TODOs). From 1e7148d590ddb0626ced4b8b967fad3f56d7c572 Mon Sep 17 00:00:00 2001 From: Noah Talerman Date: Tue, 25 Aug 2026 14:12:11 -0400 Subject: [PATCH 18/23] Add changes --- .github/ISSUE_TEMPLATE/air-guitar.md | 2 +- handbook/company/communications.md | 2 +- handbook/company/product-groups.md | 10 +++++----- handbook/engineering/README.md | 4 ++-- handbook/product-design/product-design.rituals.yml | 12 ++++++------ 5 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/air-guitar.md b/.github/ISSUE_TEMPLATE/air-guitar.md index d9c007c0eb6..054f35a274b 100644 --- a/.github/ISSUE_TEMPLATE/air-guitar.md +++ b/.github/ISSUE_TEMPLATE/air-guitar.md @@ -2,7 +2,7 @@ name: 🎸  Air guitar about: Request wireframes to be designed right away title: '🎸 ____________' -labels: ['~air-guitar', ':product', 'story'] +labels: ['~air-guitar', 'story'] assignees: '' --- diff --git a/handbook/company/communications.md b/handbook/company/communications.md index 0dfbd98de91..a65979b381d 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -446,7 +446,7 @@ _**Note:**_ There are only a few "special" labels that are exceptions to this ru - `P-` A [priority level](https://fleetdm.com/handbook/product-groups#high-priority-user-stories-and-bugs). -> If you're adding multiple labels to an issue template, GitHub won't add both labels unless you put them in an array e.g. `labels: ['~air-guitar', ':product']`. To verify the syntax is correct, you can preview the template and make sure each label name is in a separate box. +> If you're adding multiple labels to an issue template, GitHub won't add both labels unless you put them in an array e.g. `labels: ['~air-guitar', 'story']`. To verify the syntax is correct, you can preview the template and make sure each label name is in a separate box. > ***Create an issue from a Slack message*** diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index f3619516b13..4632c6d341e 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -514,9 +514,9 @@ Anyone in the product group can initiate an air guitar session. 2. If the air guitar is prioritized, it's assigned to a Product Designer who brings it through the [drafting process](https://fleetdm.com/handbook/company/product-groups#drafting). However, keep in mind that these are conceptual and may or may not proceed to engineering. -3. When the wireframes are approved, assign the requester to the issue, at-mention them to let them know that the wireframes are ready, remove the `:product` label, and add the appropriate label(s) to move the issue to the requester's board (`:help-customers` for CSMs and `:help-gtm-ops` for AEs). The requester (CSM or AE) will then bring the wireframes to a call with the customer or prospect to collect feedback. It's up to the requester to record feedback and add Gong snippets to the issue. +3. When the wireframes are approved, assign the requester to the issue, at-mention them to let them know that the wireframes are ready, and add the appropriate label(s) to move the issue to the requester's board (`:help-customers` for CSMs and `:help-gtm-ops` for AEs). The requester (CSM or AE) will then bring the wireframes to a call with the customer or prospect to collect feedback. It's up to the requester to record feedback and add Gong snippets to the issue. -4. After feedback has been added to the issue, the requester adds the `:product` label and assigns the issue to the Head of Product Design to determine an outcome: +4. After feedback has been added to the issue, the requester at-mentions the Head of Product Design (HPD) in the issue and assigns the issue to the HPD to determine an outcome: - Move forward with the formal drafting process leading to engineering. - Keep it open for future consideration. @@ -685,11 +685,11 @@ Anyone in the company is invited to submit requests or simply listen in on the ### Making a request -To make a feature request or advocate for a feature request from a customer or community member, [create an issue](https://github.com/fleetdm/fleet/issues/new/choose) using the feature request template. If you found that an issue already exists, add the `:product` label to it. +To make a feature request or advocate for a feature request from a customer or community member, [create an issue](https://github.com/fleetdm/fleet/issues/new/choose) using the feature request template. If you found that an issue already exists, add the `~inbox` label to it. New requests are [triaged daily](https://fleetdm.com/handbook/product-design#triage-new-requests) by the Head of Product Design. If the request meets the [criteria for prioritization](#criteria-for-prioritization), the request will be added to the upcoming feature fest (`~feature fest` label). If it doesn't, the request will be put to the side and the requester will be notified. -> **Fast for Fleeties:** Fleeties do not have to wait for "Unpacking the why" to add a request to feature fest. If you think Fleet is missing something and have described in detail what you already tried, it can be moved directly to the [🎁 Feature fest board](https://github.com/orgs/fleetdm/projects/72). Just add the `~feature fest` label, remove the `:product` label, and remove the `~inbox` label if it's still present. +> **Fast for Fleeties:** Fleeties do not have to wait for "Unpacking the why" to add a request to feature fest. If you think Fleet is missing something and have described in detail what you already tried, it can be moved directly to the [🎁 Feature fest board](https://github.com/orgs/fleetdm/projects/72). Just add the `~feature fest` label, and remove the `~inbox` label if it's still present. ### Criteria for prioritization @@ -706,7 +706,7 @@ Features that are core to Fleet are prioritized over context (mission critical) If an issue has the `~feature fest` label, then it's a new feature request that will be weighed at the next πŸŽπŸ—£ Feature Fest meeting. -If an issue has the `:product` and `story` label, then it's a user story that is currently in progress ([drafting](https://fleetdm.com/handbook/company/development-groups#drafting)). The user story will include a link to the original feature request issue. +If an issue has the `story` label, then it's a user story that is currently in progress ([drafting](https://fleetdm.com/handbook/company/development-groups#drafting)). The user story will include a link to the original feature request issue. ### How feature requests are prioritized diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index 80113ff31ca..c53b11cf124 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -54,7 +54,7 @@ These categories compete for priority in the normal product pipeline so that pro **To file the story:** -1. Create a new engineering-initiated story using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=lukeheath&labels=story,~engineering-initiated&projects=&template=story.md&title=). Make sure the `~engineering-initiated` label is added, the `:product` label is removed, and the engineering output and architecture DRI (@lukeheath) is assigned. +1. Create a new engineering-initiated story using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=lukeheath&labels=story,~engineering-initiated&projects=&template=story.md&title=). Make sure the `~engineering-initiated` label is added and the engineering output and architecture DRI (@lukeheath) is assigned. 2. Remove the "Product" section and checklist from the issue description. @@ -62,7 +62,7 @@ These categories compete for priority in the normal product pipeline so that pro **To draft the story:** -The engineering output and architecture DRI reviews and triages engineering-initiated stories weekly on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and selects stories to prioritize for drafting by adding the `:product` label, placing it in the "Ready" column, and assigning an engineer. +The engineering output and architecture DRI reviews and triages engineering-initiated stories weekly on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and selects stories to prioritize for drafting by placing it in the "Ready" column and assigning an engineer. 1. The assigned engineer is responsible for completing the user story drafting process by completing the specs and [defining done](https://fleetdm.com/handbook/company/product-groups#defining-done). Move the issue into "In progress" on the [Engineering board](https://github.com/orgs/fleetdm/projects/73) and populate all TODOs in the issue description, define implementation details, and draft the first version of the test plan. diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index 4a6d42c9b27..3943e69742d 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -40,7 +40,7 @@ description: "Every quarter, Head of Product Design (HPD) meets with the CEO and Head of IT & Enablement (HoIT) to run through the current quarter objectives and the 3 biggest open opportunities in the product and the solutions we're envisioning for next quarter. Then, the HPD writes a short blog with the objectives, opportunities, and an embedded YouTube video in which HPD walks through this. Last, the HPD creates a :help-marketing request to post the blog on socials." dri: "noahtalerman" autoIssue: - labels: [ ":product" ] + labels: [ "~inbox" ] repo: "fleet" - task: "Release article post" @@ -49,7 +49,7 @@ description: "Every Fleet release, Head of Product Design (HPD) meets with Product Education Manager to run through the current release highlights, their user stories, and design decisions. Then, the HPD files an issue on the :help-marketing board to create a deep-dive article for each of the highlights and assigns the PEM." dri: "noahtalerman" autoIssue: - labels: [ ":product" ] + labels: [ "~inbox" ] repo: "fleet" - task: "πŸ¦’πŸ—£ Product office hours" @@ -78,7 +78,7 @@ description: "Go back and edit, for the current quarter's roadmap blog post, anything that isn't going to be true." dri: "noahtalerman" autoIssue: - labels: [ ":product" ] + labels: [ "~inbox" ] repo: "fleet" - task: "Regenerate messaging framework" @@ -116,7 +116,7 @@ moreInfoUrl: "" dri: "rachaelshaw" autoIssue: - labels: [ ":product" ] + labels: [ "~inbox" ] repo: "fleet" - task: "Create release documentation PR" @@ -126,7 +126,7 @@ moreInfoUrl: "" dri: "rachaelshaw" autoIssue: - labels: [ ":product" ] + labels: [ "~inbox" ] repo: "fleet" - task: "Update release documentation branches" @@ -136,5 +136,5 @@ moreInfoUrl: "" dri: "rachaelshaw" autoIssue: - labels: [ ":product" ] + labels: [ "~inbox" ] repo: "fleet" From 9815dd84227d1823573cac75bb3e2b728f20a912 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 25 Aug 2026 14:17:21 -0400 Subject: [PATCH 19/23] Apply suggestion from @noahtalerman --- handbook/product-design/product-design.rituals.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index 8d0d0c35101..0536c4cbd0b 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -2,7 +2,7 @@ task: "πŸ¦’πŸ“Š Product design review" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "1. Review each product group's board to confirm only stories we're working on remain in the "Drafting" column. Update milestones if necessary. 2. Prepare the '🎁 Feature fest' board." + description: "1. Review each product group's board to confirm only stories we're working on remain in the 'Drafting' column. Update milestones if necessary. 2. Prepare the '🎁 Feature fest' board." moreInfoUrl: dri: "noahtalerman" - From 68e7e1ac45092218b0beb66b1e59e3e2e8361ac3 Mon Sep 17 00:00:00 2001 From: Noah Talerman Date: Mon, 31 Aug 2026 13:50:12 -0700 Subject: [PATCH 20/23] Keep the drafting board as intake & outtake, not retirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Revert the ~inbox label, per-product-group confirm-and-celebrate column, and tools/github-manage automation changes back to the :product label + central board flow. The central board (project 67) stays alive, renamed "🦒 Product design intake & outtake", and keeps handling new-request triage and confirm-and-celebrate. Drafting in-progress work still lives on each product group's own board. No changes to tools/github-manage or issue templates. --- .github/ISSUE_TEMPLATE/air-guitar.md | 2 +- .github/ISSUE_TEMPLATE/bug-report.md | 2 +- .github/ISSUE_TEMPLATE/feature-request.md | 2 +- android/RELEASE.md | 6 +- handbook/company/communications.md | 2 +- handbook/company/product-groups.md | 17 +- handbook/engineering/README.md | 2 +- handbook/engineering/releases.md | 12 +- handbook/product-design/README.md | 2 +- .../product-design/product-design.rituals.yml | 14 +- tools/github-manage/pkg/ghapi/projects.go | 28 +-- tools/github-manage/pkg/ghapi/suite_test.go | 1 + tools/github-manage/pkg/ghapi/workflows.go | 197 ++++++++++++++++-- .../github-manage/pkg/ghapi/workflows_test.go | 54 +++++ tools/github-manage/pkg/tui/hotkeys.go | 2 +- tools/github-manage/pkg/tui/ui.go | 152 +++++++++++++- tools/github-manage/pkg/tui/view.go | 14 +- 17 files changed, 430 insertions(+), 79 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/air-guitar.md b/.github/ISSUE_TEMPLATE/air-guitar.md index 054f35a274b..d9c007c0eb6 100644 --- a/.github/ISSUE_TEMPLATE/air-guitar.md +++ b/.github/ISSUE_TEMPLATE/air-guitar.md @@ -2,7 +2,7 @@ name: 🎸  Air guitar about: Request wireframes to be designed right away title: '🎸 ____________' -labels: ['~air-guitar', 'story'] +labels: ['~air-guitar', ':product', 'story'] assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/bug-report.md b/.github/ISSUE_TEMPLATE/bug-report.md index 465da720009..b0019ef8ef6 100644 --- a/.github/ISSUE_TEMPLATE/bug-report.md +++ b/.github/ISSUE_TEMPLATE/bug-report.md @@ -2,7 +2,7 @@ name: 🦟 Bug report about: Report a bug to help us improve. title: '' -labels: 'bug,~inbox' +labels: 'bug,:product' assignees: '' --- diff --git a/.github/ISSUE_TEMPLATE/feature-request.md b/.github/ISSUE_TEMPLATE/feature-request.md index 2707e71e0d2..45ff43bf7cf 100644 --- a/.github/ISSUE_TEMPLATE/feature-request.md +++ b/.github/ISSUE_TEMPLATE/feature-request.md @@ -2,7 +2,7 @@ name: πŸ’‘Β  Feature request about: Propose a new feature or Fleet-maintained app. title: '' -labels: '~inbox' +labels: ':product' assignees: '' --- diff --git a/android/RELEASE.md b/android/RELEASE.md index 3aee9ac0582..8fb5add8bc9 100644 --- a/android/RELEASE.md +++ b/android/RELEASE.md @@ -148,9 +148,11 @@ This brings the version bump and CHANGELOG updates to main and removes only the After the release is published, close out the associated GitHub issues and milestone. For the canonical checklist, see [Conclude current milestone](https://fleetdm.com/handbook/engineering/releases#conclude-current-milestone): -1. **Prepare for confirm and celebrate**: For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, move it to the "Confirm and celebrate" column on its product group's board. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. +1. **Move associated stories to the intake & outtake board**: Stories may not be on the [🦒 Product design intake & outtake](https://github.com/orgs/fleetdm/projects/67) board (project 67) yet. For each `story`-labeled issue in the `fleetd-android-v1.X.X` milestone, apply the `:product` label and remove the `:release` label so it moves onto the board. -2. **Close associated bugs**: Close the remaining (non-story) issues in the `fleetd-android-v1.X.X` milestone. +2. **Move associated stories to "Confirm and celebrate"**: On the [🦒 Product design intake & outtake](https://github.com/orgs/fleetdm/projects/67) board, filter by the `fleetd-android-v1.X.X` milestone, and move all stories to the "Confirm and celebrate" column. Product will close these issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. + +3. **Close associated bugs**: Close the remaining (non-story) issues in the `fleetd-android-v1.X.X` milestone. 4. **Close the GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the `fleetd-android-v1.X.X` milestone. diff --git a/handbook/company/communications.md b/handbook/company/communications.md index a65979b381d..0dfbd98de91 100644 --- a/handbook/company/communications.md +++ b/handbook/company/communications.md @@ -446,7 +446,7 @@ _**Note:**_ There are only a few "special" labels that are exceptions to this ru - `P-` A [priority level](https://fleetdm.com/handbook/product-groups#high-priority-user-stories-and-bugs). -> If you're adding multiple labels to an issue template, GitHub won't add both labels unless you put them in an array e.g. `labels: ['~air-guitar', 'story']`. To verify the syntax is correct, you can preview the template and make sure each label name is in a separate box. +> If you're adding multiple labels to an issue template, GitHub won't add both labels unless you put them in an array e.g. `labels: ['~air-guitar', ':product']`. To verify the syntax is correct, you can preview the template and make sure each label name is in a separate box. > ***Create an issue from a Slack message*** diff --git a/handbook/company/product-groups.md b/handbook/company/product-groups.md index c5faada28ec..86db0d0d119 100644 --- a/handbook/company/product-groups.md +++ b/handbook/company/product-groups.md @@ -293,7 +293,6 @@ Each product group runs its own GitHub project board with the following columns, | πŸ₯ Ready for review | A pull request is open and awaiting code review. | | βœ”οΈ Awaiting QA | The change is merged and waiting for QA verification. | | βœ… Ready for release | QA has verified the change. Held until the next release boundary. | -| πŸŽ‰ Confirm & celebrate | The change has shipped. The Product Designer and Manager of Customer Support and Solutions Architecture [confirm and celebrate](https://fleetdm.com/handbook/product-design#confirm-and-celebrate) before the issue is closed. | | Done | Released. | There are no formal WIP limits today, but the group should watch for buildup in any one column. @@ -368,7 +367,7 @@ To deliver on this mission, we need a clear, repeatable process for turning an i To make a change to Fleet: - First, [write it down](https://github.com/fleetdm/fleet/issues/new?assignees=&labels=~feature+fest%2C%3Aproduct&projects=&template=feature-request.md&title=) - - For every customer/prospect request, file a new GitHub issue. The feature request template automatically applies the `~inbox` label so the request is easy to find alongside other untriaged requests and bugs. Whether the request is the same as an existing request will be determined by the relevant product group's Product Designer and a subject matter expert (SME) in the next step. + - For every customer/prospect request, file a new GitHub issue. Whether the request is the same as an existing request will be determined by the Head of Product Design and a subject matter expert (SME) in the next step. - Then, it will be looked at by Fleet's [Head of Product Design](https://fleetdm.com/handbook/product-design#team) and a SME [unpack the "why"](https://fleetdm.com/handbook/product-design#inbox-review). - For customer/prospect requests to be looked at, they must have a Gong snippet. - Then, it will be [prioritized](https://fleetdm.com/handbook/company/product-groups#feature-fest) and written up as one or more user stories. @@ -514,9 +513,9 @@ Anyone in the product group can initiate an air guitar session. 2. If the air guitar is prioritized, it's assigned to a Product Designer who brings it through the [drafting process](https://fleetdm.com/handbook/company/product-groups#drafting). However, keep in mind that these are conceptual and may or may not proceed to engineering. -3. When the wireframes are approved, assign the requester to the issue, at-mention them to let them know that the wireframes are ready, and add the appropriate label(s) to move the issue to the requester's board (`:help-customers` for CSMs and `:help-gtm-ops` for AEs). The requester (CSM or AE) will then bring the wireframes to a call with the customer or prospect to collect feedback. It's up to the requester to record feedback and add Gong snippets to the issue. +3. When the wireframes are approved, assign the requester to the issue, at-mention them to let them know that the wireframes are ready, remove the `:product` label, and add the appropriate label(s) to move the issue to the requester's board (`:help-customers` for CSMs and `:help-gtm-ops` for AEs). The requester (CSM or AE) will then bring the wireframes to a call with the customer or prospect to collect feedback. It's up to the requester to record feedback and add Gong snippets to the issue. -4. After feedback has been added to the issue, the requester at-mentions the Head of Product Design (HPD) in the issue and assigns the issue to the HPD to determine an outcome: +4. After feedback has been added to the issue, the requester adds the `:product` label and assigns the issue to the Head of Product Design to determine an outcome: - Move forward with the formal drafting process leading to engineering. - Keep it open for future consideration. @@ -685,11 +684,11 @@ Anyone in the company is invited to submit requests or simply listen in on the ### Making a request -To make a feature request or advocate for a feature request from a customer or community member, [create an issue](https://github.com/fleetdm/fleet/issues/new/choose) using the feature request template. If you found that an issue already exists, add the `~inbox` label to it. +To make a feature request or advocate for a feature request from a customer or community member, [create an issue](https://github.com/fleetdm/fleet/issues/new/choose) using the feature request template. If you found that an issue already exists, add the `:product` label to it. New requests are [triaged daily](https://fleetdm.com/handbook/product-design#triage-new-requests) by the Head of Product Design. If the request meets the [criteria for prioritization](#criteria-for-prioritization), the request will be added to the upcoming feature fest (`~feature fest` label). If it doesn't, the request will be put to the side and the requester will be notified. -> **Fast for Fleeties:** Fleeties do not have to wait for "Unpacking the why" to add a request to feature fest. If you think Fleet is missing something and have described in detail what you already tried, it can be moved directly to the [🎁 Feature fest board](https://github.com/orgs/fleetdm/projects/72). Just add the `~feature fest` label, and remove the `~inbox` label if it's still present. +> **Fast for Fleeties:** Fleeties do not have to wait for "Unpacking the why" to add a request to feature fest. If you think Fleet is missing something and have described in detail what you already tried, it can be moved directly to the [🎁 Feature fest board](https://github.com/orgs/fleetdm/projects/72). Just add the `~feature fest` label, remove the `:product` label, and remove the issue from the 🦒 Product design intake & outtake project. ### Criteria for prioritization @@ -706,7 +705,7 @@ Features that are core to Fleet are prioritized over context (mission critical) If an issue has the `~feature fest` label, then it's a new feature request that will be weighed at the next πŸŽπŸ—£ Feature Fest meeting. -If an issue has the `story` label, then it's a user story that is currently in progress ([drafting](https://fleetdm.com/handbook/company/development-groups#drafting)). The user story will include a link to the original feature request issue. +If an issue has the `:product` and `story` label, then it's a user story that is currently in progress ([drafting](https://fleetdm.com/handbook/company/development-groups#drafting)). The user story will include a link to the original feature request issue. ### How feature requests are prioritized @@ -773,9 +772,9 @@ You can read our guide to diagnosing issues in Fleet on the [debugging page](htt #### Inbox -Quickly confirming and reproducing bug reports is a [priority for Fleet](https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks). The [bug report template](https://github.com/fleetdm/fleet/issues/new?template=bug-report.md) automatically applies the `~inbox` label, and the bug is in the "inbox" state. Website bugs (label: `#g-website`) are triaged by the [website group](https://fleetdm.com/handbook/company/product-groups#website-group). +Quickly confirming and reproducing bug reports is a [priority for Fleet](https://fleetdm.com/handbook/company/why-this-way#why-make-it-obvious-when-stuff-breaks). When a new bug is created using the [bug report template](https://github.com/fleetdm/fleet/issues/new?template=bug-report.md), it is in the "inbox" state. Website bugs (label: `#g-website`) are triaged by the [website group](https://fleetdm.com/handbook/company/product-groups#website-group). -At this state, the QA Manager is responsible for going through bugs labeled `~inbox` and adding the bug to the correct product group's board. The QA Manager then removes the `~inbox` label. +At this state, the QA Manager is responsible for going through the inbox and adding the correct product group label. This moves the bug to the inbox on the product group's board. Then, it's the product groups Engineering Manager's (EM) responsibility to review bugs during standup. It's up to the Product Designer to decide if it's a bug and specify the expected behavior. diff --git a/handbook/engineering/README.md b/handbook/engineering/README.md index f6733cf580c..9ada12bee65 100644 --- a/handbook/engineering/README.md +++ b/handbook/engineering/README.md @@ -54,7 +54,7 @@ These categories compete for priority in the normal product pipeline so that pro **To file the story:** -1. Create a new engineering-initiated story using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=lukeheath&labels=story,~engineering-initiated&projects=&template=story.md&title=). Make sure the `~engineering-initiated` label is added and the engineering output and architecture DRI (@lukeheath) is assigned. +1. Create a new engineering-initiated story using the [new story template](https://github.com/fleetdm/fleet/issues/new?assignees=lukeheath&labels=story,~engineering-initiated&projects=&template=story.md&title=). Make sure the `~engineering-initiated` label is added, the `:product` label is removed, and the engineering output and architecture DRI (@lukeheath) is assigned. 2. Remove the "Product" section and checklist from the issue description. diff --git a/handbook/engineering/releases.md b/handbook/engineering/releases.md index 2b99bdadc07..86be97081a8 100644 --- a/handbook/engineering/releases.md +++ b/handbook/engineering/releases.md @@ -178,15 +178,17 @@ Immediately after publishing a new release of Fleet or fleetd, close out the ass 1. **Update product group boards**: In GitHub Projects, go to each product group board tracking the current release and filter by the current milestone. -2. **Confirm and celebrate**: Select all items in "Ready for release" that have the `story` label and move them to the "Confirm and celebrate" column on their product group's board. Product will close the issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. +2. **Move user stories to the intake & outtake board**: Select all items in "Ready for release" that have the `story` label. Apply the `:product` label. These items will move back to the 🦒 Product design intake & outtake board. 3. **Confirm and close**: Make sure that all items with the `story` label have left the "Ready for release" column. Select all remaining items in the "Ready for release" column and move them to the "Closed" column. This will close the related GitHub issues. -4. **Close GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the current milestone. +4. **Confirm and celebrate**: Open the [🦒 Product design intake & outtake](https://github.com/orgs/fleetdm/projects/67) board. Filter by the current milestone and move all stories to the "Confirm and celebrate" column. Product will close the issues during their [confirm and celebrate ritual](https://fleetdm.com/handbook/product#rituals). [Engineering-initiated stories](https://fleetdm.com/handbook/engineering#create-an-engineering-initiated-story) (`~engineering-initiated` label) can be closed without confirm and celebrate. -5. Announce that the release milestone has been closed in #help-engineering. +5. **Close GitHub milestone**: Visit [GitHub's milestone page](https://github.com/fleetdm/fleet/milestones) and close the current milestone. -6. Visit the [confidential repo variables](https://github.com/fleetdm/confidential/settings/variables/actions) page and update the `QAWOLF_DEPLOY_TAG` repository variable to `main` so that the latest code is deployed to QA Wolf every morning. +6. Announce that the release milestone has been closed in #help-engineering. + +7. Visit the [confidential repo variables](https://github.com/fleetdm/confidential/settings/variables/actions) page and update the `QAWOLF_DEPLOY_TAG` repository variable to `main` so that the latest code is deployed to QA Wolf every morning. ## Update the Fleet releases calendar @@ -212,7 +214,7 @@ Some of our code does not go through a scheduled release process and is released In these cases there are two differences in our pull request process: - QA is done before merging the code change to the main branch. -- Tickets are not moved to "Ready for release". Bugs are closed, and user stories are moved to the "Confirm and celebrate" column on their product group's board. +- Tickets are not moved to "Ready for release". Bugs are closed, and user stories are moved to the 🦒 Product design intake & outtake board's "Confirm and celebrate" column. diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 9a91fd3d836..577eeb93137 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -34,7 +34,7 @@ Fleet's roadmap flows in this order (from highest to lowest fidelity): ### Triage new requests -The Head of Product Design is responsible for going through new feature requests and bugs labeled `~inbox`. For each one, they add the correct [product group](https://fleetdm.com/handbook/company/product-groups#continuous-flow) label and remove `~inbox`. Adding the product group label moves the issue to the [Inbox column](https://fleetdm.com/handbook/company/product-groups#board-columns) of that product group's board. +The Head of Product Design is responsible for going through the inbox on the [🦒 Product design intake & outtake board](https://github.com/orgs/fleetdm/projects/67) and adding the correct [product group](https://fleetdm.com/handbook/company/product-groups#continuous-flow) label. Once labeled, each Product Designer (PD) is responsible for reviewing the inbox on their product group's board and deciding whether each new request contributes to Fleet's [product maturity](https://fleetdm.com/handbook/company/product-maturity-assessment) goals for the current calendar year. If yes, the PD adds the `~product-maturity` label so the request is reviewed at the next [unpacking the why](#unpacking-the-why) call. If a request meets a different [criteria for prioritization](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization), the PD removes the "Unpacked" checkbox in the feature request issue and either prioritizes a [user story or quick win](https://fleetdm.com/handbook/company/product-groups#work-items) to bring through [fast draft or full draft](https://fleetdm.com/handbook/company/product-groups#drafting-tracks-full-draft-vs-fast-draft), or removes it from the product group board and adds it to the [feature fest](https://fleetdm.com/handbook/company/product-groups#feature-fest) board. diff --git a/handbook/product-design/product-design.rituals.yml b/handbook/product-design/product-design.rituals.yml index 0536c4cbd0b..228d7e65b8b 100644 --- a/handbook/product-design/product-design.rituals.yml +++ b/handbook/product-design/product-design.rituals.yml @@ -16,7 +16,7 @@ task: "Product design kickoff" # 2024-03-06 TODO: Link to responsibility or corresponding "how to" info e.g. https://fleetdm.com/handbook/company/product-groups#making-changes startedOn: "2024-03-07" frequency: "Triweekly" - description: "1. Pull up all contributors' calendars to review time off and, if needed, update design review calendar events. 2. Create reference docs release branches for any milestones newly added 3. Each Product Designer pulls up the release planning project and walks the team through prioritized stories." + description: "1. Pull up all contributors' calendars to review time off and, if needed, update design review calendar events. 2. Create reference docs release branches for any milestones newly added to the 🦒 Product design intake & outtake board 3. Each Product Designer pulls up the release planning project and walks the team through prioritized stories." moreInfoUrl: dri: "noahtalerman" - @@ -40,7 +40,7 @@ description: "Every quarter, Head of Product Design (HPD) meets with the CEO and Head of IT & Enablement (HoIT) to run through the current quarter objectives and the 3 biggest open opportunities in the product and the solutions we're envisioning for next quarter. Then, the HPD writes a short blog with the objectives, opportunities, and an embedded YouTube video in which HPD walks through this. Last, the HPD creates a :help-marketing request to post the blog on socials." dri: "noahtalerman" autoIssue: - labels: [ "~inbox" ] + labels: [ ":product" ] repo: "fleet" - task: "Release article post" @@ -49,7 +49,7 @@ description: "Every Fleet release, Head of Product Design (HPD) meets with Product Education Manager to run through the current release highlights, their user stories, and design decisions. Then, the HPD files an issue on the :help-marketing board to create a deep-dive article for each of the highlights and assigns the PEM." dri: "noahtalerman" autoIssue: - labels: [ "~inbox" ] + labels: [ ":product" ] repo: "fleet" - task: "πŸ¦’πŸ—£ Product office hours" @@ -78,7 +78,7 @@ description: "Go back and edit, for the current quarter's roadmap blog post, anything that isn't going to be true." dri: "noahtalerman" autoIssue: - labels: [ "~inbox" ] + labels: [ ":product" ] repo: "fleet" - task: "Regenerate messaging framework" @@ -116,7 +116,7 @@ moreInfoUrl: "" dri: "rachaelshaw" autoIssue: - labels: [ "~inbox" ] + labels: [ ":product" ] repo: "fleet" - task: "Create release documentation PR" @@ -126,7 +126,7 @@ moreInfoUrl: "" dri: "rachaelshaw" autoIssue: - labels: [ "~inbox" ] + labels: [ ":product" ] repo: "fleet" - task: "Update release documentation branches" @@ -136,5 +136,5 @@ moreInfoUrl: "" dri: "rachaelshaw" autoIssue: - labels: [ "~inbox" ] + labels: [ ":product" ] repo: "fleet" diff --git a/tools/github-manage/pkg/ghapi/projects.go b/tools/github-manage/pkg/ghapi/projects.go index c5ad08b1b4a..461acb9d613 100644 --- a/tools/github-manage/pkg/ghapi/projects.go +++ b/tools/github-manage/pkg/ghapi/projects.go @@ -28,29 +28,13 @@ var Aliases = map[string]int{ "releases": 87, } -// ProjectLabels maps product group project IDs to their corresponding GitHub label. +// ProjectLabels maps project IDs to their corresponding label filters for the drafting project var ProjectLabels = map[int]string{ - 58: "#g-apple-at-work", // Apple @ Work - 70: "#g-auto-patching", // Auto Patching - 71: "#g-orchestration", // Orchestration - 92: "#g-website", // Website - 97: "#g-supply-chain", // Supply Chain - 106: "#g-power-to-pc", // Power to the PC - 112: "#g-byod", // BYOD -} - -// ProjectIDForLabels returns the product group project ID matching one of the given -// issue labels (e.g. "#g-byod"), and whether a match was found. -func ProjectIDForLabels(labels []Label) (int, bool) { - for _, l := range labels { - name := strings.ToLower(strings.TrimSpace(l.Name)) - for projectID, label := range ProjectLabels { - if strings.ToLower(label) == name { - return projectID, true - } - } - } - return 0, false + 58: "#g-mdm", // mdm project + 70: "#g-software", // g-software project + 71: "#g-orchestration", // g-orchestration project + 97: "#g-supply-chain", // g-supply-chain project + 112: "#g-byod", // g-byod project } // ResolveProjectID resolves a project identifier (alias or numeric string) to a project ID. diff --git a/tools/github-manage/pkg/ghapi/suite_test.go b/tools/github-manage/pkg/ghapi/suite_test.go index 626847c8720..dc8bef69113 100644 --- a/tools/github-manage/pkg/ghapi/suite_test.go +++ b/tools/github-manage/pkg/ghapi/suite_test.go @@ -44,6 +44,7 @@ func TestSuite(t *testing.T) { t.Run("Workflows Tests", func(t *testing.T) { t.Run("BulkAddLabel", TestBulkAddLabel) t.Run("BulkRemoveLabel", TestBulkRemoveLabel) + t.Run("BulkSprintKickoff", TestBulkSprintKickoff) t.Run("BulkMilestoneClose", TestBulkMilestoneClose) t.Run("WorkflowFunctionsSignatures", TestWorkflowFunctionsSignatures) t.Run("WorkflowsWithValidIssues", TestWorkflowsWithValidIssues) diff --git a/tools/github-manage/pkg/ghapi/workflows.go b/tools/github-manage/pkg/ghapi/workflows.go index d0280dab880..67b8cc023d2 100644 --- a/tools/github-manage/pkg/ghapi/workflows.go +++ b/tools/github-manage/pkg/ghapi/workflows.go @@ -152,6 +152,20 @@ func CreateBulkMoveToCurrentSprintIfNotReadyQA(issues []Issue, projectID int) [] return CreateBulkSetSprintAction(filtered, projectID) } +func CreateBulkSprintKickoffActions(issues []Issue, sourceProjectID, projectID int) []Action { + logger.Infof("Creating sprint kickoff actions for %d issues (source project: %d, target project: %d)", len(issues), sourceProjectID, projectID) + + actions := CreateBulkAddIssueToProjectAction(issues, projectID) + actions = append(actions, CreateBulkAddLableAction(issues, ":release")...) + actions = append(actions, CreateBulkSyncEstimateAction(issues, sourceProjectID, projectID)...) + actions = append(actions, CreateBulkSetSprintAction(issues, projectID)...) + actions = append(actions, CreateBulkRemoveLabelAction(issues, ":product")...) + actions = append(actions, CreateBulkRemoveIssueFromProjectAction(issues, Aliases["draft"])...) + + logger.Infof("Created %d sprint kickoff actions", len(actions)) + return actions +} + func CreateBulkMilestoneCloseActions(issues []Issue) []Action { logger.Infof("Creating milestone close actions for %d issues", len(issues)) // Split issues by type @@ -183,17 +197,9 @@ func CreateBulkMilestoneCloseActions(issues []Issue) []Action { var actions []Action if len(storyIssues) > 0 { - for _, issue := range storyIssues { - // Stories already live on their product group's board (there's no - // separate drafting board to move them onto) β€” resolve which board - // that is from the issue's own #g-* label and set status there. - projectID, ok := ProjectIDForLabels(issue.Labels) - if !ok { - logger.Errorf("Skipping 'confirm and celebrate' status for issue #%d: no product group label found", issue.Number) - continue - } - actions = append(actions, CreateBulkSetStatusAction([]Issue{issue}, projectID, "confirm and celebrate")...) - } + actions = append(actions, CreateBulkAddIssueToProjectAction(storyIssues, Aliases["draft"])...) + actions = append(actions, CreateBulkAddLableAction(storyIssues, ":product")...) + actions = append(actions, CreateBulkSetStatusAction(storyIssues, Aliases["draft"], "confirm and")...) actions = append(actions, CreateBulkRemoveLabelAction(storyIssues, ":release")...) } for _, is := range closeIssues { @@ -204,6 +210,20 @@ func CreateBulkMilestoneCloseActions(issues []Issue) []Action { return actions } +func CreateBulkKickOutOfSprintActions(issues []Issue, sourceProjectID int) []Action { + logger.Infof("Creating kick out of sprint actions for %d issues (source project: %d)", len(issues), sourceProjectID) + + actions := CreateBulkAddIssueToProjectAction(issues, Aliases["draft"]) + actions = append(actions, CreateBulkSetStatusAction(issues, Aliases["draft"], "estimated")...) + actions = append(actions, CreateBulkSyncEstimateAction(issues, sourceProjectID, Aliases["draft"])...) + actions = append(actions, CreateBulkAddLableAction(issues, ":product")...) + actions = append(actions, CreateBulkRemoveLabelAction(issues, ":release")...) + actions = append(actions, CreateBulkRemoveIssueFromProjectAction(issues, sourceProjectID)...) + + logger.Infof("Created %d kick out of sprint actions", len(actions)) + return actions +} + // AsyncManager takes a list of actions and a channel to process them assynchronously. // This will allow to send status back on the channel for live updates. the channel must return index of the action // and the status of the action. @@ -338,12 +358,79 @@ func BulkRemoveLabel(issues []Issue, label string) error { return nil } +// BulkSprintKickoff performs the sprint kickoff workflow for multiple issues. +// This includes adding issues to the target project, adding release labels, syncing estimates, and setting sprints. +func BulkSprintKickoff(issues []Issue, sourceProjectID, projectID int) error { + logger.Infof("Starting sprint kickoff workflow for %d issues (source: %d, target: %d)", len(issues), sourceProjectID, projectID) + + // Add ticket to the target product group project + logger.Info("Step 1/6: Adding issues to target project") + for _, issue := range issues { + err := AddIssueToProject(issue.Number, projectID) + if err != nil { + logger.Errorf("Failed to add issue #%d to project %d: %v", issue.Number, projectID, err) + return err + } + logger.Debugf("Added issue #%d to project %d", issue.Number, projectID) + } + + // Add the `:release` label to each issue + logger.Info("Step 2/6: Adding release labels") + err := BulkAddLabel(issues, ":release") + if err != nil { + return err + } + + // Sync the Estimate field from drafting project to the target product group project + logger.Info("Step 3/6: Syncing estimates") + for _, issue := range issues { + err := SyncEstimateField(issue.Number, sourceProjectID, projectID) + if err != nil { + logger.Errorf("Failed to sync estimate for issue #%d: %v", issue.Number, err) + return err + } + logger.Debugf("Synced estimate for issue #%d", issue.Number) + } + + // Set the sprint to the current sprint + logger.Info("Step 4/6: Setting current sprint") + for _, issue := range issues { + err := SetCurrentSprint(issue.Number, projectID) + if err != nil { + logger.Errorf("Failed to set current sprint for issue #%d: %v", issue.Number, err) + return err + } + logger.Debugf("Set current sprint for issue #%d", issue.Number) + } + + // Remove the `:product` label from each issue + logger.Info("Step 5/6: Removing product labels") + err = BulkRemoveLabel(issues, ":product") + if err != nil { + return err + } + + // Remove from the drafting project + logger.Info("Step 6/6: Removing from drafting project") + draftingProjectID := Aliases["draft"] + for _, issue := range issues { + err := RemoveIssueFromProject(issue.Number, draftingProjectID) + if err != nil { + logger.Errorf("Failed to remove issue #%d from drafting project: %v", issue.Number, err) + return err + } + logger.Debugf("Removed issue #%d from drafting project", issue.Number) + } + + logger.Info("Sprint kickoff workflow completed successfully") + return nil +} + // BulkMilestoneClose performs the milestone close workflow for multiple issues. -// Stories already live on their product group's board, so this sets status -// directly on that board (resolved per-issue from its #g-* label) instead of -// moving anything to a separate drafting project. +// This includes moving issues back to the drafting project and removing them from product group projects. func BulkMilestoneClose(issues []Issue) error { logger.Infof("Starting milestone close workflow for %d issues", len(issues)) + draftingProjectID := Aliases["draft"] var storyIssues []Issue var closeIssues []Issue for _, is := range issues { @@ -369,24 +456,30 @@ func BulkMilestoneClose(issues []Issue) error { } } - // Process story issues: set status to confirm and celebrate on their own - // product group board, remove :release + // Process story issues: move to drafting, add :product, set status, remove :release if len(storyIssues) > 0 { logger.Infof("Processing %d story issues for milestone close", len(storyIssues)) - // Step 1: Set status to confirm and celebrate on each issue's product group board + // Step 1: Add to drafting for _, issue := range storyIssues { - projectID, ok := ProjectIDForLabels(issue.Labels) - if !ok { - logger.Errorf("Skipping issue #%d: no product group label found, cannot resolve its board", issue.Number) - continue + err := AddIssueToProject(issue.Number, draftingProjectID) + if err != nil { + logger.Errorf("Failed to add issue #%d to drafting project: %v", issue.Number, err) + return err } - err := SetIssueStatus(issue.Number, projectID, "confirm and celebrate") + } + // Step 2: Add :product + if err := BulkAddLabel(storyIssues, ":product"); err != nil { + return err + } + // Step 3: Set status to confirm and celebrate + for _, issue := range storyIssues { + err := SetIssueStatus(issue.Number, draftingProjectID, "confirm and celebrate") if err != nil { logger.Errorf("Failed to set status for issue #%d: %v", issue.Number, err) return err } } - // Step 2: Remove :release + // Step 4: Remove :release if err := BulkRemoveLabel(storyIssues, ":release"); err != nil { return err } @@ -406,3 +499,61 @@ func BulkMilestoneClose(issues []Issue) error { logger.Info("Milestone close workflow completed successfully") return nil } + +// BulkKickOutOfSprint performs the kick out of sprint workflow for multiple issues. +// This includes moving issues back to the drafting project, setting status to estimated, +// syncing estimates from source project, and updating labels. +func BulkKickOutOfSprint(issues []Issue, sourceProjectID int) error { + logger.Infof("Starting kick out of sprint workflow for %d issues (source: %d)", len(issues), sourceProjectID) + + // Add issues to the drafting project + logger.Info("Step 1/5: Adding issues to drafting project") + draftingProjectID := Aliases["draft"] + for _, issue := range issues { + err := AddIssueToProject(issue.Number, draftingProjectID) + if err != nil { + logger.Errorf("Failed to add issue #%d to drafting project: %v", issue.Number, err) + return err + } + logger.Debugf("Added issue #%d to drafting project", issue.Number) + } + + // Set the status to "estimated" + logger.Info("Step 2/5: Setting status to 'estimated'") + for _, issue := range issues { + err := SetIssueStatus(issue.Number, draftingProjectID, "estimated") + if err != nil { + logger.Errorf("Failed to set status for issue #%d: %v", issue.Number, err) + return err + } + logger.Debugf("Set status for issue #%d", issue.Number) + } + + // Sync the Estimate field from source project to the drafting project + logger.Info("Step 3/5: Syncing estimates from source project") + for _, issue := range issues { + err := SyncEstimateField(issue.Number, sourceProjectID, draftingProjectID) + if err != nil { + logger.Errorf("Failed to sync estimate for issue #%d: %v", issue.Number, err) + return err + } + logger.Debugf("Synced estimate for issue #%d", issue.Number) + } + + // Add the `:product` label to each issue + logger.Info("Step 4/5: Adding product labels") + err := BulkAddLabel(issues, ":product") + if err != nil { + return err + } + + // Remove the `:release` label from each issue + logger.Info("Step 5/5: Removing release labels") + err = BulkRemoveLabel(issues, ":release") + if err != nil { + return err + } + + logger.Info("Kick out of sprint workflow completed successfully") + return nil +} diff --git a/tools/github-manage/pkg/ghapi/workflows_test.go b/tools/github-manage/pkg/ghapi/workflows_test.go index 5ebc4c8513a..a242a2a0fa9 100644 --- a/tools/github-manage/pkg/ghapi/workflows_test.go +++ b/tools/github-manage/pkg/ghapi/workflows_test.go @@ -103,6 +103,46 @@ func TestBulkRemoveLabel(t *testing.T) { } } +func TestBulkSprintKickoff(t *testing.T) { + tests := []struct { + name string + issues []Issue + projectID int + }{ + { + name: "empty issues slice", + issues: []Issue{}, + projectID: 58, + }, + { + name: "single issue", + issues: []Issue{ + {Number: 123, Title: "Test Issue"}, + }, + projectID: 67, + }, + { + name: "multiple issues", + issues: []Issue{ + {Number: 123, Title: "Test Issue 1"}, + {Number: 456, Title: "Test Issue 2"}, + }, + projectID: 70, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Skip("BulkSprintKickoff requires GitHub CLI setup and mocking for proper testing") + // Since BulkSprintKickoff depends on multiple GitHub CLI commands, + err := BulkSprintKickoff(tt.issues, tt.projectID, tt.projectID) + if err != nil { + t.Errorf("BulkSprintKickoff should return nil (stub implementation), got: %v", err) + } + }) + } +} + func TestBulkMilestoneClose(t *testing.T) { tests := []struct { name string @@ -157,6 +197,12 @@ func TestWorkflowFunctionsSignatures(t *testing.T) { // Expected for empty slice } + // Test BulkSprintKickoff signature + err = BulkSprintKickoff(issues, 123, 123) + if err != nil { + t.Errorf("BulkSprintKickoff unexpected error: %v", err) + } + // Test BulkMilestoneClose signature err = BulkMilestoneClose(issues) if err != nil { @@ -187,6 +233,14 @@ func TestWorkflowsWithValidIssues(t *testing.T) { }, } + t.Run("BulkSprintKickoff with valid issues", func(t *testing.T) { + t.Skip("BulkSprintKickoff requires GitHub CLI setup and mocking for proper testing") + err := BulkSprintKickoff(issues, 58, 58) + if err != nil { + t.Errorf("BulkSprintKickoff should return nil, got: %v", err) + } + }) + t.Run("BulkMilestoneClose with valid issues", func(t *testing.T) { t.Skip("BulkMilestoneClose requires GitHub CLI setup and mocking for proper testing") err := BulkMilestoneClose(issues) diff --git a/tools/github-manage/pkg/tui/hotkeys.go b/tools/github-manage/pkg/tui/hotkeys.go index f73683e2753..2a5ae6231ca 100644 --- a/tools/github-manage/pkg/tui/hotkeys.go +++ b/tools/github-manage/pkg/tui/hotkeys.go @@ -246,7 +246,7 @@ func (m *model) HandleHotkeys(msg tea.Msg) (tea.Model, tea.Cmd) { m.labelInput = "" case BulkDemoSummary: return m, m.executeWorkflow() - case BulkMoveToCurrentSprint: + case BulkSprintKickoff, BulkKickOutOfSprint, BulkMoveToCurrentSprint: if m.projectID != 0 { // Use the provided project ID return m, m.executeWorkflow() diff --git a/tools/github-manage/pkg/tui/ui.go b/tools/github-manage/pkg/tui/ui.go index fcdd078ef34..3aa2ede9fd8 100644 --- a/tools/github-manage/pkg/tui/ui.go +++ b/tools/github-manage/pkg/tui/ui.go @@ -47,7 +47,9 @@ type WorkflowType int const ( BulkAddLabel WorkflowType = iota BulkRemoveLabel + BulkSprintKickoff BulkMilestoneClose + BulkKickOutOfSprint BulkDemoSummary BulkMoveToCurrentSprint ) @@ -55,7 +57,9 @@ const ( var WorkflowTypeValues = []string{ "Bulk Add Label", "Bulk Remove Label", + "Bulk Sprint Kickoff", "Bulk Milestone Close", + "Bulk Kick Out Of Sprint", "Bulk Demo Summary", "Move to current sprint", } @@ -627,12 +631,126 @@ func (m *model) executeWorkflow() tea.Cmd { Progress: 0.0, }) } + case BulkSprintKickoff: + projectID := m.projectID + if projectID == 0 && m.projectInput != "" { + // Try to resolve project ID + resolvedID, err := ghapi.ResolveProjectID(m.projectInput) + if err != nil { + return func() tea.Msg { + return workflowCompleteMsg{ + success: false, + message: fmt.Sprintf("Failed to resolve project ID: %v", err), + } + } + } + projectID = resolvedID + } + + actions = ghapi.CreateBulkSprintKickoffActions(selectedIssues, ghapi.Aliases["draft"], projectID) + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: i, + Description: fmt.Sprintf("Adding #%d issue to project %d", issue.Number, projectID), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: len(selectedIssues) + i, + Description: fmt.Sprintf("Adding ':release' label to #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (2 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Syncing estimate fields for #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (3 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Setting current sprint for #%d issue in project %d", issue.Number, projectID), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (4 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Removing ':product' label from #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (5 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Removing #%d issue from drafting project", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } case BulkMilestoneClose: actions = ghapi.CreateBulkMilestoneCloseActions(selectedIssues) + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: len(selectedIssues) + i, + Description: fmt.Sprintf("Adding #%d issue to drafting project", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: len(selectedIssues) + i, + Description: fmt.Sprintf("Adding ':product' label to #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: len(selectedIssues) + i, + Description: fmt.Sprintf("Setting status to 'confirm and celebrate' for #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: len(selectedIssues) + i, + Description: fmt.Sprintf("Removing ':release' label from #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + case BulkKickOutOfSprint: + projectID := m.projectID + if projectID == 0 && m.projectInput != "" { + // Try to resolve project ID + resolvedID, err := ghapi.ResolveProjectID(m.projectInput) + if err != nil { + return func() tea.Msg { + return workflowCompleteMsg{ + success: false, + message: fmt.Sprintf("Failed to resolve project ID: %v", err), + } + } + } + projectID = resolvedID + } + + actions = ghapi.CreateBulkKickOutOfSprintActions(selectedIssues, projectID) for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ ID: i, - Description: fmt.Sprintf("Setting status to 'confirm and celebrate' for #%d issue on its product group board", issue.Number), + Description: fmt.Sprintf("Adding #%d issue to drafting project", issue.Number), Status: TaskPending, Progress: 0.0, }) @@ -640,11 +758,43 @@ func (m *model) executeWorkflow() tea.Cmd { for i, issue := range selectedIssues { m.tasks = append(m.tasks, WorkflowTask{ ID: len(selectedIssues) + i, + Description: fmt.Sprintf("Setting status to 'estimated' for #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (2 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Syncing estimate from project %d for #%d issue", projectID, issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (3 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Adding ':product' label to #%d issue", issue.Number), + Status: TaskPending, + Progress: 0.0, + }) + } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (4 * len(selectedIssues)) + i, Description: fmt.Sprintf("Removing ':release' label from #%d issue", issue.Number), Status: TaskPending, Progress: 0.0, }) } + for i, issue := range selectedIssues { + m.tasks = append(m.tasks, WorkflowTask{ + ID: (4 * len(selectedIssues)) + i, + Description: fmt.Sprintf("Removing #%d issue from project %d", issue.Number, projectID), + Status: TaskPending, + Progress: 0.0, + }) + } case BulkMoveToCurrentSprint: projectID := m.projectID if projectID == 0 && m.projectInput != "" { diff --git a/tools/github-manage/pkg/tui/view.go b/tools/github-manage/pkg/tui/view.go index 7a84a69909a..bcb8173d578 100644 --- a/tools/github-manage/pkg/tui/view.go +++ b/tools/github-manage/pkg/tui/view.go @@ -395,8 +395,12 @@ func (m model) selectedWorkflowDescription() string { desc = "Add a label to all selected issues. You'll be prompted to type the label name." case BulkRemoveLabel: desc = "Remove a label from all selected issues. You'll be prompted to type the label name." + case BulkSprintKickoff: + desc = "Add selected issues to a project and set initial sprint kickoff fields (status, estimate sync, labels)." case BulkMilestoneClose: - desc = "For selected stories: sets status to 'confirm and celebrate' on the issue's own product group board, removes :release, and adds :product. For selected bugs and ~sub-task: closes the issues. Does not affect the milestone object itself." + desc = "For selected stories: adds to drafting, sets status to 'confirm and celebrate', removes :release, and adds :product. For selected bugs and ~sub-task: closes the issues. Does not affect the milestone object itself." + case BulkKickOutOfSprint: + desc = "Remove selected issues from a project and reset sprint-related fields (status, labels)." case BulkDemoSummary: desc = "Generate a markdown summary of selected issues grouped by feature and bug, with assignees." case BulkMoveToCurrentSprint: @@ -410,7 +414,7 @@ func (m model) selectedWorkflowDescription() string { switch w { case BulkAddLabel, BulkRemoveLabel: hints = append(hints, "Input required: label name") - case BulkMoveToCurrentSprint: + case BulkSprintKickoff, BulkKickOutOfSprint, BulkMoveToCurrentSprint: hints = append(hints, "Input required: project ID or alias") if m.projectID != 0 { hints = append(hints, fmt.Sprintf("Current project in context: %d", m.projectID)) @@ -494,8 +498,12 @@ func (m model) RenderLabelInput() string { } func (m model) RenderProjectInput() string { - workflowTitle := "Move To Current Sprint" + workflowTitle := "Sprint Kickoff" promptText := "Target Project (ID or alias):" + if m.workflowType == BulkKickOutOfSprint { + workflowTitle = "Kick Out Of Sprint" + promptText = "Source Project (ID or alias):" + } s := fmt.Sprintf("\n--- %s ---\n", workflowTitle) s += fmt.Sprintf("%s %s_\n", promptText, m.projectInput) s += "Press 'enter' to execute, 'esc' to cancel.\n" From 0cafc3a6713c4262ac1681790b03629ba14cc8f4 Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:56:13 -0700 Subject: [PATCH 21/23] Apply suggestion from @noahtalerman --- handbook/product-design/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 577eeb93137..4e328bec799 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -36,7 +36,7 @@ Fleet's roadmap flows in this order (from highest to lowest fidelity): The Head of Product Design is responsible for going through the inbox on the [🦒 Product design intake & outtake board](https://github.com/orgs/fleetdm/projects/67) and adding the correct [product group](https://fleetdm.com/handbook/company/product-groups#continuous-flow) label. -Once labeled, each Product Designer (PD) is responsible for reviewing the inbox on their product group's board and deciding whether each new request contributes to Fleet's [product maturity](https://fleetdm.com/handbook/company/product-maturity-assessment) goals for the current calendar year. If yes, the PD adds the `~product-maturity` label so the request is reviewed at the next [unpacking the why](#unpacking-the-why) call. If a request meets a different [criteria for prioritization](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization), the PD removes the "Unpacked" checkbox in the feature request issue and either prioritizes a [user story or quick win](https://fleetdm.com/handbook/company/product-groups#work-items) to bring through [fast draft or full draft](https://fleetdm.com/handbook/company/product-groups#drafting-tracks-full-draft-vs-fast-draft), or removes it from the product group board and adds it to the [feature fest](https://fleetdm.com/handbook/company/product-groups#feature-fest) board. +Once labeled, each Product Designer (PD) is responsible for reviewing the inbox and deciding whether each new request contributes to their [product group's goal](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization). If yes, the PD adds the `~product-maturity` label so the request is reviewed at the next [unpacking the why](#unpacking-the-why) call. If a request meets a different [criteria for prioritization](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization), the PD removes the issue from the drafting board and either prioritizes a [user story or quick win](https://fleetdm.com/handbook/company/product-groups#work-items) to bring through [drafting](https://fleetdm.com/handbook/company/product-groups#drafting), or adds the issue to the [feature fest](https://fleetdm.com/handbook/company/product-groups#feature-fest) board. ### Unpacking the why From 822c740a8e3fee8d5dcb989575c5f63fee607e6e Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Mon, 31 Aug 2026 13:57:23 -0700 Subject: [PATCH 22/23] Apply suggestion from @noahtalerman --- handbook/product-design/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 4e328bec799..0bbc6464a3c 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -36,7 +36,7 @@ Fleet's roadmap flows in this order (from highest to lowest fidelity): The Head of Product Design is responsible for going through the inbox on the [🦒 Product design intake & outtake board](https://github.com/orgs/fleetdm/projects/67) and adding the correct [product group](https://fleetdm.com/handbook/company/product-groups#continuous-flow) label. -Once labeled, each Product Designer (PD) is responsible for reviewing the inbox and deciding whether each new request contributes to their [product group's goal](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization). If yes, the PD adds the `~product-maturity` label so the request is reviewed at the next [unpacking the why](#unpacking-the-why) call. If a request meets a different [criteria for prioritization](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization), the PD removes the issue from the drafting board and either prioritizes a [user story or quick win](https://fleetdm.com/handbook/company/product-groups#work-items) to bring through [drafting](https://fleetdm.com/handbook/company/product-groups#drafting), or adds the issue to the [feature fest](https://fleetdm.com/handbook/company/product-groups#feature-fest) board. +Once labeled, each Product Designer (PD) is responsible for reviewing the inbox and deciding whether each new request contributes to their [product group's goal](https://fleetdm.com/handbook/company/product-groups#current-product-groups). If yes, the PD adds the `~product-maturity` label so the request is reviewed at the next [unpacking the why](#unpacking-the-why) call. If a request meets a different [criteria for prioritization](https://fleetdm.com/handbook/company/product-groups#criteria-for-prioritization), the PD removes the issue from the drafting board and either prioritizes a [user story or quick win](https://fleetdm.com/handbook/company/product-groups#work-items) to bring through [drafting](https://fleetdm.com/handbook/company/product-groups#drafting), or adds the issue to the [feature fest](https://fleetdm.com/handbook/company/product-groups#feature-fest) board. ### Unpacking the why From 5b30a220037301d24c6ee4a0320051abdf92e09b Mon Sep 17 00:00:00 2001 From: Noah Talerman <47070608+noahtalerman@users.noreply.github.com> Date: Tue, 1 Sep 2026 15:09:47 -0700 Subject: [PATCH 23/23] Apply suggestion from @noahtalerman --- handbook/product-design/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/handbook/product-design/README.md b/handbook/product-design/README.md index 0bbc6464a3c..380eb89913c 100644 --- a/handbook/product-design/README.md +++ b/handbook/product-design/README.md @@ -55,7 +55,10 @@ At Fleet, like [GitLab](https://about.gitlab.com/handbook/product-development-fl 2. Create a new file inside the [Fleet product](https://www.figma.com/files/project/17318630/%F0%9F%94%9C%F0%9F%93%A6-Fleet-EE%C2%AE-(product)?fuid=1234929285759903870) Figma project by duplicating "\[TEMPLATE\] Starter file" (pinned to the top of the project). The starter file includes three predefined pages: "Cover," "Ready," and "Scratchpad." - **Cover**: This page has a component with the issue number and issue name. - **Ready**: Use this page to communicate design reviews and development. - - **Scratchpad**: Use this page to keep "work in progress" designs that might be useful in the future. +1. Take the top user story that is assigned to you in the "Drafting" column of [your product group's board](https://fleetdm.com/handbook/company/product-groups#current-product-groups) and create a new file inside the [Fleet product](https://www.figma.com/files/project/17318630/%F0%9F%94%9C%F0%9F%93%A6-Fleet-EE%C2%AE-(product)?fuid=1234929285759903870) Figma project by duplicating "\[TEMPLATE\] Starter file" (pinned to the top of the project). The starter file includes three predefined pages: "Cover," "Ready," and "Scratchpad." +- **Cover**: This page has a component with the issue number and issue name. +- **Ready**: Use this page to communicate design reviews and development. +- **Scratchpad**: Use this page to keep "work in progress" designs that might be useful in the future. 3. If the story requires API or YAML file changes, open a pull request (PR) to the reference docs with the proposed design. Pay attention to existing conventions (URL structure, parameter names, response format) and aim to be consistent. Your PR should follow these guidelines: - Make a PR against the docs release branch for the version you expect this feature to be in. Docs release branches are named using the format `docs-vX.X.X`, so if you're designing for Fleet 4.61.0, you would make a PR to `docs-v4.61.0`.