func (release *nextRelease) PromptUserToConfirmStart() (bool, error) { // Collect the issues to be added to the current release. task := "Collect the issues that modified trunk since the last release" log.Run(task) ids, err := releases.ListStoryIdsToBeAssigned(release.tracker) if err != nil { return false, errs.NewError(task, err) } // Fetch the additional issues from JIRA. task = "Fetch the collected issues from JIRA" log.Run(task) issues, err := listStoriesById(newClient(release.tracker.config), ids) if len(issues) == 0 && err != nil { return false, errs.NewError(task, err) } if len(issues) != len(ids) { log.Warn("Some issues were dropped since they were not found in JIRA") } // Drop the issues that were already assigned to the right version. releaseLabel := release.trunkVersion.ReleaseTagString() filteredIssues := make([]*jira.Issue, 0, len(issues)) IssueLoop: for _, issue := range issues { // Add only the parent tasks, i.e. skip sub-tasks. if issue.Fields.Parent != nil { continue } // Add only the issues that have not been assigned to the release yet. for _, label := range issue.Fields.Labels { if label == releaseLabel { continue IssueLoop } } filteredIssues = append(filteredIssues, issue) } issues = filteredIssues // Present the issues to the user. if len(issues) != 0 { fmt.Println("\nThe following issues are going to be added to the release:\n") err := prompt.ListStories(toCommonStories(issues, release.tracker), os.Stdout) if err != nil { return false, err } } // Ask the user to confirm. ok, err := prompt.Confirm( fmt.Sprintf( "\nAre you sure you want to start release %v?", release.trunkVersion.BaseString())) if err == nil { release.additionalIssues = issues } return ok, err }
func (release *nextRelease) PromptUserToConfirmStart() (bool, error) { // Fetch the stories already assigned to the release. var ( ver = release.trunkVersion verString = ver.BaseString() verLabel = ver.ReleaseTagString() ) task := fmt.Sprintf("Fetch the stories already assigned to release %v", verString) log.Run(task) assignedStories, err := release.tracker.storiesByRelease(ver) if err != nil { return false, errs.NewError(task, err) } // Collect the story IDs associated with the commits that // modified trunk since the last release. task = "Collect the stories that modified trunk since the last release" log.Run(task) storyIds, err := releases.ListStoryIdsToBeAssigned(release.tracker) if err != nil { return false, errs.NewError(task, err) } // Drop the stories that are already assigned. idSet := make(map[string]struct{}, len(assignedStories)) for _, story := range assignedStories { idSet[strconv.Itoa(story.Id)] = struct{}{} } ids := make([]string, 0, len(storyIds)) for _, id := range storyIds { if _, ok := idSet[id]; !ok { ids = append(ids, id) } } storyIds = ids // Fetch the collected stories from Pivotal Tracker, if necessary. var additionalStories []*pivotal.Story if len(storyIds) != 0 { task = "Fetch the collected stories from Pivotal Tracker" log.Run(task) var err error additionalStories, err = release.tracker.storiesById(storyIds) if len(additionalStories) == 0 && err != nil { return false, errs.NewError(task, err) } if len(additionalStories) != len(storyIds) { log.Warn("Some stories were dropped since they were not found in PT") log.NewLine("or they were filtered out by a story include label.") } // Drop stories already assigned to another release. notAssigned := make([]*pivotal.Story, 0, len(additionalStories)) NotAssignedLoop: for _, story := range additionalStories { for _, label := range story.Labels { if isReleaseLabel(label.Name) { log.Warn(fmt.Sprintf( "Skipping story %v: modified trunk, but already labeled '%v'", story.Id, label.Name)) continue NotAssignedLoop } } notAssigned = append(notAssigned, story) } additionalStories = notAssigned } // Check the Point Me label. task = "Make sure there are no unpointed stories" log.Run(task) pmLabel := release.tracker.config.PointMeLabel // Fetch the already assigned but unpointed stories. pmStories, err := release.tracker.searchStories( "label:\"%v\" AND label:\"%v\"", verLabel, pmLabel) if err != nil { return false, errs.NewError(task, err) } // Also add these that are to be added but are unpointed. for _, story := range additionalStories { if labeled(story, pmLabel) { pmStories = append(pmStories, story) } } // In case there are some unpointed stories, stop the release. if len(pmStories) != 0 { fmt.Println("\nThe following stories are still yet to be pointed:\n") err := storyprompt.ListStories(toCommonStories(pmStories, release.tracker), os.Stdout) if err != nil { return false, err } fmt.Println() return false, errs.NewError(task, errors.New("unpointed stories detected")) } // Print the summary into the console. summary := []struct { header string stories []*pivotal.Story }{ { "The following stories were manually assigned to the release:", assignedStories, }, { "The following stories were added automatically (modified trunk):", additionalStories, }, } for _, item := range summary { if len(item.stories) != 0 { fmt.Println() fmt.Println(item.header) fmt.Println() err := storyprompt.ListStories(toCommonStories(item.stories, release.tracker), os.Stdout) if err != nil { return false, err } } } // Ask the user to confirm. ok, err := prompt.Confirm( fmt.Sprintf( "\nAre you sure you want to start release %v?", release.trunkVersion.BaseString()), false) if err == nil { release.additionalStories = additionalStories } return ok, err }
func (release *nextRelease) PromptUserToConfirmStart() (bool, error) { var ( client = release.client productId = release.tracker.config.ProductId() itemReleaseTag = getItemReleaseTag(release.trunkVersion) ) // Collect the commits that modified trunk since the last release. task := "Collect the stories that modified trunk" log.Run(task) ids, err := releases.ListStoryIdsToBeAssigned(release.tracker) if err != nil { return false, errs.NewError(task, err) } // Get the story ids associated with these commits. numbers := make([]int, 0, len(ids)) for _, id := range ids { number, err := strconv.Atoi(id) if err != nil { return false, errs.NewError(task, fmt.Errorf("invalid item number: %v", id), nil) } numbers = append(numbers, number) } // Fetch the collected items from Sprintly, if necessary. var additional []sprintly.Item if len(numbers) != 0 { var err error // listItemsByNumber lists children as well, so there is no way we can miss a sub-item. additional, err = listItemsByNumber(client, productId, numbers) if err != nil { return false, err } // Drop the issues that are already assigned to the right release. unassignedItems := make([]sprintly.Item, 0, len(additional)) for _, item := range additional { if tagged(&item, itemReleaseTag) { continue } unassignedItems = append(unassignedItems, item) } additional = unassignedItems } // Make sure there are no unrated items. task = "Make sure there are no unrated items" log.Run(task) // Check the additional items and collect the unrated ones. unrated := make([]sprintly.Item, 0) for _, item := range additional { if item.Score == sprintly.ItemScoreUnset { unrated = append(unrated, item) } } // Fetch the items that were assigned manually. assignedItems, err := listItemsByTag(client, productId, []string{itemReleaseTag}) if err != nil { return false, errs.NewError(task, err) } // Check the manually assigned items and collect the unrated ones. for _, item := range assignedItems { if item.Score == sprintly.ItemScoreUnset { unrated = append(unrated, item) } // Also, since the sub-items of the assigned items are returned as well, // they may be missing the release tag, so let's register them to be tagged. if !tagged(&item, itemReleaseTag) { additional = append(additional, item) } } // In case there are some unrated items, abort the release process. if len(unrated) != 0 { fmt.Println("\nThe following items have not been rated yet:\n") err := prompt.ListStories(toCommonStories(unrated), os.Stdout) if err != nil { return false, err } fmt.Println() return false, errs.NewError(task, errors.New("unrated items detected"), nil) } // Print the items to be added to the release. if len(additional) != 0 { fmt.Println("\nThe following items are going to be added to the release:\n") err := prompt.ListStories(toCommonStories(additional), os.Stdout) if err != nil { return false, err } } // Ask the user to confirm. ok, err := prompt.Confirm( fmt.Sprintf("\nAre you sure you want to start release %v?", release.trunkVersion)) if err == nil { release.additionalItems = additional } return ok, err }
func (release *nextRelease) PromptUserToConfirmStart() (bool, error) { var ( config = release.tracker.config client = pivotal.NewClient(config.UserToken()) releaseLabel = getReleaseLabel(release.trunkVersion) ) // Collect the commits that modified trunk since the last release. task := "Collect the stories that modified trunk" log.Run(task) ids, err := releases.ListStoryIdsToBeAssigned(release.tracker) if err != nil { return false, errs.NewError(task, err) } // Fetch the collected stories from Pivotal Tracker, if necessary. var additional []*pivotal.Story if len(ids) != 0 { task = "Fetch the collected stories from Pivotal Tracker" log.Run(task) var err error additional, err = listStoriesById(client, config.ProjectId(), ids) if len(additional) == 0 && err != nil { return false, errs.NewError(task, err) } if len(additional) != len(ids) { log.Warn("Some stories were dropped since they were not found in PT") } // Drop the issues that are already assigned to the right release. unassignedStories := make([]*pivotal.Story, 0, len(additional)) for _, story := range additional { if labeled(story, releaseLabel) { continue } unassignedStories = append(unassignedStories, story) } additional = unassignedStories } // Check the Point Me label. task = "Make sure there are no unpointed stories" log.Run(task) pmLabel := config.PointMeLabel() // Fetch the already assigned but unpointed stories. pmStories, err := searchStories(client, config.ProjectId(), "label:\"%v\" AND label:\"%v\"", releaseLabel, pmLabel) if err != nil { return false, errs.NewError(task, err) } // Also add these that are to be added but are unpointed. for _, story := range additional { if labeled(story, pmLabel) { pmStories = append(pmStories, story) } } // In case there are some unpointed stories, stop the release. if len(pmStories) != 0 { fmt.Println("\nThe following stories are still yet to be pointed:\n") err := prompt.ListStories(toCommonStories(pmStories, release.tracker), os.Stdout) if err != nil { return false, err } fmt.Println() return false, errs.NewError(task, errors.New("unpointed stories detected")) } // Print the stories to be added to the release. if len(additional) != 0 { fmt.Println("\nThe following stories are going to be added to the release:\n") err := prompt.ListStories(toCommonStories(additional, release.tracker), os.Stdout) if err != nil { return false, err } } // Ask the user to confirm. ok, err := prompt.Confirm( fmt.Sprintf( "\nAre you sure you want to start release %v?", release.trunkVersion.BaseString())) if err == nil { release.additionalStories = additional } return ok, err }
// PromptUserToConfirm is a part of common.NextRelease interface. func (release *nextRelease) PromptUserToConfirmStart() (bool, error) { // Fetch the stories already assigned to the release. var ( ver = release.trunkVersion verString = ver.BaseString() ) task := fmt.Sprintf("Fetch GitHub issues already assigned to release %v", verString) log.Run(task) assignedIssues, err := release.tracker.issuesByRelease(ver) if err != nil { return false, errs.NewError(task, err) } // Collect the issues that modified trunk since the last release. task = "Collect the issues that modified trunk since the last release" log.Run(task) issueNumsString, err := releases.ListStoryIdsToBeAssigned(release.tracker) if err != nil { return false, errs.NewError(task, err) } // Turn []string into []int. issueNums := make([]int, len(issueNumsString)) for i, numString := range issueNumsString { // numString is #ISSUE_NUMBER. num, err := strconv.Atoi(numString[1:]) if err != nil { panic(err) } issueNums[i] = num } // Drop the stories that are already assigned. numSet := make(map[int]struct{}, len(assignedIssues)) for _, issue := range assignedIssues { numSet[*issue.Number] = struct{}{} } nums := make([]int, 0, len(issueNums)) for _, num := range issueNums { if _, ok := numSet[num]; !ok { nums = append(nums, num) } } issueNums = nums // Fetch the collected issues from GitHub, if necessary. var additionalIssues []*github.Issue if len(issueNums) != 0 { task = "Fetch the collected issues from GitHub" log.Run(task) var err error additionalIssues, err = release.tracker.issuesByNumber(issueNums) if err != nil { return false, errs.NewError(task, err) } // Drop stories already assigned to another release. notAssigned := make([]*github.Issue, 0, len(additionalIssues)) for _, issue := range additionalIssues { switch { case issue.Milestone == nil: notAssigned = append(notAssigned, issue) default: log.Warn(fmt.Sprintf( "Skipping issue #%v: modified trunk, but already assigned to milestone '%v'", *issue.Number, *issue.Milestone.Title)) } } additionalIssues = notAssigned } // Print the summary into the console. summary := []struct { header string issues []*github.Issue }{ { "The following issues were manually assigned to the release:", assignedIssues, }, { "The following issues were added automatically (modified trunk):", additionalIssues, }, } for _, item := range summary { if len(item.issues) != 0 { fmt.Println() fmt.Println(item.header) fmt.Println() err := storyprompt.ListStories( toCommonStories(item.issues, release.tracker), os.Stdout) if err != nil { return false, err } } } // Ask the user to confirm. ok, err := prompt.Confirm( fmt.Sprintf("\nAre you sure you want to start release %v?", verString), false) if err == nil { release.additionalIssues = additionalIssues } return ok, err }