Esempio n. 1
0
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
}
Esempio n. 2
0
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
}
Esempio n. 3
0
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
}
Esempio n. 4
0
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
}
Esempio n. 5
0
// 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
}